+! Copyright (C) 2010 Erik Charlebois
+! See http://factorcode.org/license.txt for BSD license.
+USING: help.markup help.crossref help.stylesheet help.topics help.syntax
+definitions io prettyprint summary arrays math sequences vocabs strings
+see xml.data hashtables assocs game.models.obj.private game.models
+game.models.util io.pathnames ;
+IN: game.models.obj
+
+ABOUT: "game.models.obj"
+
+ARTICLE: "game.models.obj" "Conversion of Wavefront OBJ assets"
+"The " { $vocab-link "game.models.obj" } " vocabulary implements words for converting Wavefront OBJ assets to data suitable for use with OpenGL." ;
+
+HELP: material
+{ $class-description "Tuple describing the GPU state that needs to be applied prior to rendering geometry tagged with this material." } ;
+
+HELP: cm
+{ $values { "current-material" material } }
+{ $description "Convenience word for accessing the current material while parsing primitives." } ;
+
+HELP: md
+{ $values { "material-dictionary" assoc } }
+{ $description "Convenience word for accessing the material dictionary while parsing primitives. " } ;
+
+HELP: strings>floats
+{ $values { "strings" sequence } { "floats" sequence } }
+{ $description "Convert a sequence of strings to a sequence of floats." } ;
+
+HELP: strings>faces
+{ $values { "strings" sequence } { "faces" sequence } }
+{ $description "Convert a sequence of '/'-delimited strings into a sequence of sequences of numbers. Each number is an index into the vertex, texture or normal tables, respectively." } ;
+
+HELP: split-string
+{ $values { "string" string } { "strings" sequence } }
+{ $description "Split the given string on whitespace." } ;
+
+HELP: line>mtl
+{ $values { "line" string } }
+{ $description "Process a line from a material file within the current parsing context." } ;
+
+HELP: read-mtl
+{ $values { "file" pathname } { "material-dictionary" assoc } }
+{ $description "Read the specified material file and generate a material dictionary keyed by material name." } ;
+
+HELP: obj-vertex-format
+{ $class-description "Vertex format used for rendering OBJ geometry." } ;
+
+HELP: triangle>aos
+{ $values { "x" sequence } { "y" sequence } }
+{ $description "Convert a sequence of vertex, texture and normal indices into a sequence of vertex, texture and normal values." } ;
+
+HELP: quad>aos
+{ $values { "x" sequence } { "y" sequence } { "z" sequence } }
+{ $description "Convert a sequence of vertex, texture and normal indices into two sequences of vertex, texture and normal values. This splits a quad into two triangles." } ;
+
+HELP: face>aos
+{ $values { "x" sequence } { "y" sequence } }
+{ $description "Convert a face line to a sequence of vertex attributes." } ;
+
+HELP: push*
+{ $values { "elt" "an object" } { "seq" sequence } { "seq" sequence } }
+{ $description "Push the value onto the sequence, keeping the sequence on the stack." } ;
+
+HELP: push-current-model
+{ $description "Push the current model being built onto the models list and initialize a fresh empty model." } ;
+
+HELP: line>obj
+{ $values { "line" string } }
+{ $description "Process a line from the object file within the current parsing context." } ;
+
-! Copyright (C) 2010 Your name.
+! Copyright (C) 2010 Erik Charlebois
! See http://factorcode.org/license.txt for BSD license.
USING: io io.encodings.ascii math.parser sequences splitting kernel
assocs io.files combinators math.order math namespaces
arrays sequences.deep accessors
specialized-arrays.instances.alien.c-types.float
specialized-arrays.instances.alien.c-types.uint
-game.models.util gpu.shaders images game.models.loader ;
+game.models game.models.util gpu.shaders images
+game.models.loader prettyprint ;
IN: game.models.obj
SINGLETON: obj-models
"obj" ascii obj-models register-models-class
<PRIVATE
-SYMBOLS: v vt vn i ;
+SYMBOLS: vp vt vn current-model current-material material-dictionary models ;
-VERTEX-FORMAT: obj-vertex-format
- { "POSITION" float-components 3 f }
- { "TEXCOORD" float-components 2 f }
- { "NORMAL" float-components 3 f } ;
+TUPLE: material
+ { name initial: f }
+ { ambient-reflectivity initial: { 1.0 1.0 1.0 } }
+ { diffuse-reflectivity initial: { 1.0 1.0 1.0 } }
+ { specular-reflectivity initial: { 1.0 1.0 1.0 } }
+ { transmission-filter initial: { 1.0 1.0 1.0 } }
+ { dissolve initial: 1.0 }
+ { specular-exponent initial: 10.0 }
+ { refraction-index initial: 1.5 }
+ { ambient-map initial: f }
+ { diffuse-map initial: f }
+ { specular-map initial: f }
+ { specular-exponent-map initial: f }
+ { dissolve-map initial: f }
+ { displacement-map initial: f }
+ { bump-map initial: f }
+ { reflection-map initial: f } ;
+
+: cm ( -- current-material ) current-material get ; inline
+: md ( -- material-dictionary ) material-dictionary get ; inline
-: string>floats ( x -- y )
+: strings>floats ( strings -- floats )
[ string>float ] map ;
-: string>faces ( x -- y )
+: strings>faces ( strings -- faces )
[ "/" split [ string>number ] map ] map ;
-: 3face>aos ( x -- y )
- dup length {
- { 3
- [
- first3
- [ 1 - v get nth ]
- [ 1 - vt get nth ]
- [ 1 - vn get nth ] tri* 3array flatten
- ] }
- { 2
- [
- first2
- [ 1 - v get nth ]
- [ 1 - vt get nth ] bi* 2array flatten
- ] }
+: split-string ( string -- strings )
+ " \t\n" split harvest ;
+
+: line>mtl ( line -- )
+ " \t\n" split harvest
+ [
+ [ rest ] [ first ] bi
+ {
+ { "newmtl" [ first
+ [ material new swap >>name current-material set ]
+ [ cm swap md set-at ] bi
+ ] }
+ { "Ka" [ 3 head [ string>float ] map cm (>>ambient-reflectivity) ] }
+ { "Kd" [ 3 head [ string>float ] map cm (>>diffuse-reflectivity) ] }
+ { "Ks" [ 3 head [ string>float ] map cm (>>specular-reflectivity) ] }
+ { "Tf" [ 3 head [ string>float ] map cm (>>transmission-filter) ] }
+ { "d" [ first string>float cm (>>dissolve) ] }
+ { "Ns" [ first string>float cm (>>specular-exponent) ] }
+ { "Ni" [ first string>float cm (>>refraction-index) ] }
+ { "map_Ka" [ first cm (>>ambient-map) ] }
+ { "map_Kd" [ first cm (>>diffuse-map) ] }
+ { "map_Ks" [ first cm (>>specular-map) ] }
+ { "map_Ns" [ first cm (>>specular-exponent-map) ] }
+ { "map_d" [ first cm (>>dissolve-map) ] }
+ { "map_bump" [ first cm (>>bump-map) ] }
+ { "bump" [ first cm (>>bump-map) ] }
+ { "disp" [ first cm (>>displacement-map) ] }
+ { "refl" [ first cm (>>reflection-map) ] }
+ [ 2drop ]
+ } case
+ ] unless-empty ;
+
+: read-mtl ( file -- material-dictionary )
+ [
+ f current-material set
+ H{ } clone material-dictionary set
+ ] H{ } make-assoc
+ [
+ ascii file-lines [ line>mtl ] each
+ md
+ ] bind ;
+
+VERTEX-FORMAT: obj-vertex-format
+ { "POSITION" float-components 3 f }
+ { "TEXCOORD" float-components 2 f }
+ { "NORMAL" float-components 3 f } ;
+
+: triangle>aos ( x -- y )
+ dup length
+ {
+ { 3 [
+ first3
+ [ 1 - vp get nth ]
+ [ 1 - vt get nth ]
+ [ 1 - vn get nth ] tri* 3array flatten
+ ] }
+ { 2 [
+ first2
+ [ 1 - vp get nth ]
+ [ 1 - vt get nth ] bi* 2array flatten
+ ] }
} case ;
+: quad>aos ( x -- y z )
+ [ 3 head [ triangle>aos 1array ] map ]
+ [ [ 2 swap nth ]
+ [ 3 swap nth ]
+ [ 0 swap nth ] tri 3array
+ [ triangle>aos 1array ] map ]
+ bi ;
-: 4face>aos ( x -- y z )
- [ 3 head [ 3face>aos 1array ] map ]
- [ [ 0 swap nth ] [ 2 swap nth ] [ 3 swap nth ] tri 3array [ 3face>aos 1array ] map ]
- bi
- ;
-
-: faces>aos ( x -- y )
+: face>aos ( x -- y )
dup length
{
- { 3 [ [ 3face>aos 1array ] map 1array ] }
- { 4 [ 4face>aos 2array ] }
+ { 3 [ [ triangle>aos 1array ] map 1array ] }
+ { 4 [ quad>aos 2array ] }
} case ;
-: push* ( x z -- y )
+: push* ( elt seq -- seq )
[ push ] keep ;
+: push-current-model ( -- )
+ current-model get [
+ [ dseq>> flatten >float-array ]
+ [ iseq>> flatten >uint-array ]
+ bi obj-vertex-format current-material get model boa models get push
+ V{ } V{ } H{ } <indexed-seq> current-model set
+ ] unless-empty ;
+
: line>obj ( line -- )
- " \t\n" split harvest dup
- length 1 >
+ split-string
[
[ rest ] [ first ] bi
{
- { "#" [ drop ] }
- { "v" [ string>floats 3 head v [ push* ] change ] }
- { "vt" [ string>floats 2 head vt [ push* ] change ] }
- { "vn" [ string>floats 3 head vn [ push* ] change ] }
- { "f" [ string>faces faces>aos [ [ i [ push* ] change ] each ] each ] }
- { "o" [ drop ] }
- { "g" [ drop ] }
- { "s" [ drop ] }
- { "mtllib" [ drop ] }
- { "usemtl" [ drop ] }
+ { "mtllib" [ first read-mtl material-dictionary set ] }
+ { "v" [ strings>floats 3 head vp [ push* ] change ] }
+ { "vt" [ strings>floats 2 head vt [ push* ] change ] }
+ { "vn" [ strings>floats 3 head vn [ push* ] change ] }
+ { "usemtl" [ push-current-model first md at current-material set ] }
+ { "f" [ strings>faces face>aos [ [ current-model [ push* ] change ] each ] each ] }
+ [ 2drop ]
} case
- ]
- [ drop ] if ;
+ ] unless-empty ;
PRIVATE>
M: obj-models stream>models
drop
[
- V{ }
- [ clone v set ]
- [ clone vt set ]
- [ clone vn set ] tri
- V{ } V{ } H{ } <indexed-seq> i set
+ V{ } clone vp set
+ V{ } clone vt set
+ V{ } clone vn set
+ V{ } clone models set
+ V{ } V{ } H{ } <indexed-seq> current-model set
+ f current-material set
+ f material-dictionary set
] H{ } make-assoc
[
- [ line>obj ] each-stream-line i get
- ] bind
- [ dseq>> flatten >float-array ]
- [ iseq>> flatten >uint-array ] bi obj-vertex-format model boa 1array ;
+ [ line>obj ] each-stream-line push-current-model
+ models get
+ ] bind ;
math.matrices math.vectors.simd math.parser math.vectors
method-chains namespaces sequences splitting threads ui ui.gadgets
ui.gadgets.worlds ui.pixel-formats specialized-arrays
-specialized-vectors literals fry xml
-xml.traversal sequences.deep destructors math.bitwise opengl.gl
-game.models.obj game.models.loader game.models.collada ;
+specialized-vectors literals fry
+sequences.deep destructors math.bitwise opengl.gl
+game.models game.models.obj game.models.loader game.models.collada
+prettyprint images.tga ;
FROM: alien.c-types => float ;
SPECIALIZED-ARRAY: float
SPECIALIZED-VECTOR: uint
IN: model-viewer
-GLSL-SHADER: model-vertex-shader vertex-shader
-uniform mat4 mv_matrix, p_matrix;
-uniform vec3 light_position;
+GLSL-SHADER: obj-vertex-shader vertex-shader
+uniform mat4 mv_matrix;
+uniform mat4 p_matrix;
attribute vec3 POSITION;
+attribute vec3 TEXCOORD;
attribute vec3 NORMAL;
-attribute vec2 TEXCOORD;
-varying vec2 texit;
-varying vec3 norm;
+varying vec2 texcoord_fs;
+varying vec3 normal_fs;
+varying vec3 world_pos_fs;
void main()
{
vec4 position = mv_matrix * vec4(POSITION, 1.0);
- gl_Position = p_matrix * position;
- texit = TEXCOORD;
- norm = NORMAL;
+ gl_Position = p_matrix * position;
+ world_pos_fs = POSITION;
+ texcoord_fs = TEXCOORD;
+ normal_fs = NORMAL;
}
;
-GLSL-SHADER: model-fragment-shader fragment-shader
-varying vec2 texit;
-varying vec3 norm;
-void main()
-{
- gl_FragColor = vec4(texit, 0, 1) + vec4(norm, 1);
-}
-;
-
-GLSL-PROGRAM: model-program
- model-vertex-shader model-fragment-shader ;
-
-GLSL-SHADER: debug-vertex-shader vertex-shader
+GLSL-SHADER: obj-fragment-shader fragment-shader
uniform mat4 mv_matrix, p_matrix;
-uniform vec3 light_position;
-
-attribute vec3 POSITION;
-attribute vec3 COLOR;
-varying vec4 color;
-
-void main()
-{
- gl_Position = p_matrix * mv_matrix * vec4(POSITION, 1.0);
- color = vec4(COLOR, 1);
-}
-;
-
-GLSL-SHADER: debug-fragment-shader fragment-shader
-varying vec4 color;
+uniform sampler2D map_Ka;
+uniform sampler2D map_bump;
+uniform vec3 Ka;
+uniform vec3 view_pos;
+uniform vec3 light;
+varying vec2 texcoord_fs;
+varying vec3 normal_fs;
+varying vec3 world_pos_fs;
void main()
{
- gl_FragColor = color;
+ vec4 d = texture2D(map_Ka, texcoord_fs.xy);
+ vec3 b = texture2D(map_bump, texcoord_fs.xy).xyz;
+ vec3 n = normal_fs;
+ vec3 v = normalize(view_pos - world_pos_fs);
+ vec3 l = normalize(light);
+ vec3 h = normalize(v + l);
+ float cosTh = saturate(dot(n, l));
+ gl_FragColor = d * cosTh
+ + d * 0.5 * cosTh * pow(saturate(dot(n, h)), 10.0) ;
}
;
-GLSL-PROGRAM: debug-program debug-vertex-shader debug-fragment-shader ;
+GLSL-PROGRAM: obj-program
+ obj-vertex-shader obj-fragment-shader ;
UNIFORM-TUPLE: model-uniforms < mvp-uniforms
- { "light-position" vec3-uniform f } ;
+ { "map_Ka" texture-uniform f }
+ { "map_bump" texture-uniform f }
+ { "Ka" vec3-uniform f }
+ { "light" vec3-uniform f }
+ { "view_pos" vec3-uniform f }
+ ;
TUPLE: model-state
models
vertex-arrays
- index-vectors ;
+ index-vectors
+ textures
+ bumps
+ kas ;
-TUPLE: model-world < wasd-world
- { model-state model-state } ;
+TUPLE: model-world < wasd-world model-path model-state ;
-VERTEX-FORMAT: model-vertex
- { "POSITION" float-components 3 f }
- { "NORMAL" float-components 3 f }
- { "TEXCOORD" float-components 2 f } ;
+TUPLE: vbo
+ vertex-buffer
+ index-buffer index-count vertex-format texture bump ka ;
-VERTEX-FORMAT: debug-vertex
- { "POSITION" float-components 3 f }
- { "COLOR" float-components 3 f } ;
-
-TUPLE: vbo vertex-buffer index-buffer index-count vertex-format ;
+: white-image ( -- image )
+ { 1 1 } BGR ubyte-components f
+ B{ 255 255 255 } image boa ;
+: up-image ( -- image )
+ { 1 1 } BGR ubyte-components f
+ B{ 0 0 0 } image boa ;
+
+: make-texture ( pathname alt -- texture )
+ swap [ nip load-image ] [ ] if*
+ [
+ [ component-order>> ]
+ [ component-type>> ] bi
+ T{ texture-parameters
+ { wrap repeat-texcoord }
+ { min-filter filter-linear }
+ { min-mipmap-filter f } }
+ <texture-2d>
+ ]
+ [
+ 0 swap [ allocate-texture-image ] 3keep 2drop
+ ] bi ;
+
: <model-buffers> ( models -- buffers )
[
{
[ index-buffer>> underlying>> static-upload draw-usage index-buffer byte-array>buffer ]
[ index-buffer>> length ]
[ vertex-format>> ]
+ [ material>> ambient-map>> white-image make-texture ]
+ [ material>> bump-map>> up-image make-texture ]
+ [ material>> ambient-reflectivity>> ]
} cleave vbo boa
] map ;
: fill-model-state ( model-state -- )
dup models>> <model-buffers>
- [
+ {
[
- [ vertex-buffer>> model-program <program-instance> ]
- [ vertex-format>> ] bi buffer>vertex-array
- ] map >>vertex-arrays drop
- ]
- [
+ [
+ [ vertex-buffer>> obj-program <program-instance> ]
+ [ vertex-format>> ] bi buffer>vertex-array
+ ] map >>vertex-arrays drop
+ ]
[
- [ index-buffer>> ] [ index-count>> ] bi
- '[ _ 0 <buffer-ptr> _ uint-indexes <index-elements> ] call
- ] map >>index-vectors drop
- ] 2bi ;
-
-: model-files ( -- files )
- { "C:/Users/erikc/Downloads/test2.dae"
- "C:/Users/erikc/Downloads/Sponza.obj" } ;
-
-: <model-state> ( -- model-state )
- model-state new
- model-files [ load-models ] [ append ] map-reduce >>models ;
-
-M: model-world begin-game-world
- init-gpu
- { 0.0 0.0 2.0 } 0 0 set-wasd-view
- <model-state> [ fill-model-state drop ] [ >>model-state drop ] 2bi ;
-
-: <model-uniforms> ( world -- uniforms )
- [ wasd-mv-matrix ] [ wasd-p-matrix ] bi
- { -10000.0 10000.0 10000.0 } ! light position
- model-uniforms boa ;
-
-: draw-line ( world from to color -- )
- [ 3 head ] tri@ dup -rot append -rot append swap append >float-array
- underlying>> stream-upload draw-usage vertex-buffer byte-array>buffer
- debug-program <program-instance> debug-vertex buffer>vertex-array
-
- { 0 1 } >uint-array stream-upload draw-usage index-buffer byte-array>buffer
- 2 '[ _ 0 <buffer-ptr> _ uint-indexes <index-elements> ] call
-
- rot <model-uniforms>
-
- {
- { "primitive-mode" [ 3drop lines-mode ] }
- { "uniforms" [ nip nip ] }
- { "vertex-array" [ drop drop ] }
- { "indexes" [ drop nip ] }
- } 3<render-set> render ;
-
-: draw-lines ( world lines -- )
- 3 <groups> [ first3 draw-line ] with each ; inline
-
-: draw-axes ( world -- )
- { { 0 0 0 } { 1 0 0 } { 1 0 0 }
- { 0 0 0 } { 0 1 0 } { 0 1 0 }
- { 0 0 0 } { 0 0 1 } { 0 0 1 } } draw-lines ;
-
-: draw-model ( world -- )
+ [
+ [ index-buffer>> ] [ index-count>> ] bi
+ '[ _ 0 <buffer-ptr> _ uint-indexes <index-elements> ] call
+ ] map >>index-vectors drop
+ ]
+ [ [ texture>> ] map >>textures drop ]
+ [ [ bump>> ] map >>bumps drop ]
+ [ [ ka>> ] map >>kas drop ]
+ } 2cleave ;
+
+: <model-state> ( model-world -- model-state )
+ model-path>> 1array model-state new swap
+ [ load-models ] [ append ] map-reduce >>models ;
+
+:: <model-uniforms> ( world -- uniforms )
+ world model-state>>
+ [ textures>> ] [ bumps>> ] [ kas>> ] tri
+ [| texture bump ka |
+ world wasd-mv-matrix
+ world wasd-p-matrix
+ texture bump ka
+ { 0.5 0.5 0.5 }
+ world location>>
+ model-uniforms boa
+ ] 3map ;
+
+: clear-screen ( -- )
0 0 0 0 glClearColor
1 glClearDepth
HEX: ffffffff glClearStencil
- { GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT GL_STENCIL_BUFFER_BIT } flags glClear
-
- [
- triangle-fill dup t <triangle-state> set-gpu-state
- face-ccw cull-back <triangle-cull-state> set-gpu-state
-
- cmp-less <depth-state> set-gpu-state
- [ model-state>> vertex-arrays>> ]
- [ model-state>> index-vectors>> ]
- [ <model-uniforms> ]
- tri
- [
- {
- { "primitive-mode" [ 3drop triangles-mode ] }
- { "uniforms" [ nip nip ] }
- { "vertex-array" [ drop drop ] }
- { "indexes" [ drop nip ] }
- } 3<render-set> render
- ] curry 2each
- ]
+ { GL_COLOR_BUFFER_BIT
+ GL_DEPTH_BUFFER_BIT
+ GL_STENCIL_BUFFER_BIT } flags glClear ;
+
+: draw-model ( world -- )
+ clear-screen
+ face-ccw cull-back <triangle-cull-state> set-gpu-state
+ cmp-less <depth-state> set-gpu-state
+ [ model-state>> vertex-arrays>> ]
+ [ model-state>> index-vectors>> ]
+ [ <model-uniforms> ]
+ tri
[
- cmp-always <depth-state> set-gpu-state
- draw-axes
- ]
- bi ;
+ {
+ { "primitive-mode" [ 3drop triangles-mode ] }
+ { "uniforms" [ nip nip ] }
+ { "vertex-array" [ drop drop ] }
+ { "indexes" [ drop nip ] }
+ } 3<render-set> render
+ ] 3each ;
-M: model-world draw-world*
- draw-model ;
+TUPLE: model-attributes < game-attributes model-path ;
+M: model-world draw-world* draw-model ;
M: model-world wasd-movement-speed drop 1/4. ;
M: model-world wasd-near-plane drop 1/32. ;
M: model-world wasd-far-plane drop 1024.0 ;
+M: model-world begin-game-world
+ init-gpu
+ { 0.0 0.0 2.0 } 0 0 set-wasd-view
+ [ <model-state> [ fill-model-state ] keep ] [ (>>model-state) ] bi ;
+M: model-world apply-world-attributes
+ {
+ [ model-path>> >>model-path ]
+ [ call-next-method ]
+ } cleave ;
-GAME: model-viewer {
- { world-class model-world }
- { title "Model Viewer" }
- { pixel-format-attributes { windowed double-buffered } }
- { grab-input? t }
- { use-game-input? t }
- { pref-dim { 1024 768 } }
- { tick-interval-micros $[ 60 fps ] }
- } ;
+:: open-model-viewer ( model-path -- )
+ [
+ f
+ T{ model-attributes
+ { world-class model-world }
+ { grab-input? t }
+ { title "Model Viewer" }
+ { pixel-format-attributes
+ { windowed double-buffered }
+ }
+ { pref-dim { 1024 768 } }
+ { tick-interval-micros 16666 }
+ { use-game-input? t }
+ { model-path model-path }
+ }
+ clone
+ open-window
+ ] with-ui ;