]> gitweb.factorcode.org Git - factor.git/commitdiff
Add material loading for OBJ format, generalize model-viewer and removed hard-coded...
authorerikc <erikcharlebois@gmail.com>
Fri, 5 Feb 2010 23:51:53 +0000 (15:51 -0800)
committererikc <erikcharlebois@gmail.com>
Fri, 5 Feb 2010 23:51:53 +0000 (15:51 -0800)
extra/game/models/collada/collada-docs.factor
extra/game/models/collada/collada.factor
extra/game/models/models-docs.factor
extra/game/models/models.factor
extra/game/models/obj/obj-docs.factor
extra/game/models/obj/obj.factor
extra/game/models/util/util.factor
extra/model-viewer/model-viewer.factor

index 402f5eddc1e302c1376f575c171db9f562c3152c..5be2e19790041dcdbbfa1216cda231e0f64490f3 100644 (file)
@@ -2,7 +2,8 @@
 ! 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.collada.private game.models.util ;
+see xml.data hashtables assocs game.models.collada.private game.models
+game.models.util ;
 IN: game.models.collada
 
 ABOUT: "game.models.collada"
index 3de255bae8a777c9d8445b0ac80916dae61a5b50..9d8fad764c3393c668254e8091dc888dae206cdc 100644 (file)
@@ -5,7 +5,7 @@ locals math math.parser sequences sequences.deep
 specialized-arrays.instances.alien.c-types.float
 specialized-arrays.instances.alien.c-types.uint splitting xml
 xml.data xml.traversal math.order
-namespaces combinators images gpu.shaders io make
+namespaces combinators images gpu.shaders io make game.models
 game.models.util io.encodings.ascii game.models.loader ;
 IN: game.models.collada
 
@@ -152,7 +152,7 @@ VERTEX-FORMAT: collada-vertex-format
         soa>aos 
         [ flatten >float-array ]
         [ flatten >uint-array ]
-        bi* collada-vertex-format model boa
+        bi* collada-vertex-format model boa
     ] bi ;
     
 : mesh>triangles ( sources vertices mesh-tag -- models )
index 907c32e29490f4aeb19115ccfb253cd9b58ab1ba..174d64a15684b92c665dd0e2f2218865322b7f36 100644 (file)
@@ -6,4 +6,4 @@ see ;
 IN: game.models
 
 HELP: model
-{ $class-description "Tuple of a packed attribute buffer, index buffer and vertex format suitable for a single OpenGL draw call." } ;
+{ $class-description "Tuple of a packed attribute buffer, index buffer, vertex format and material suitable for a single OpenGL draw call." } ;
index 5575f5fb80f8b55bb2469850c5e8d4755a2c24d1..2d297f80b9876ec0eb9bdde2b268b575bb9604a2 100644 (file)
@@ -3,5 +3,5 @@
 USING: ;
 IN: game.models
 
-TUPLE: model attribute-buffer index-buffer vertex-format ;
+TUPLE: model attribute-buffer index-buffer vertex-format material ;
 
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..adea0ef34bbd7efa5e796d11aca09de0a50543c4 100644 (file)
@@ -0,0 +1,70 @@
+! 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." } ;
+
index 94927c9db8509b7be0b8eef4bdae5a1d19ae0451..57eddcec6cc714b67fb5ba36804d64500be0694e 100644 (file)
-! 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 ;
 
index 76f93f8365308c169b9a6d4421a80c6d5f9baacb..438ab82356b51c1306f44846cee88df6c9642e67 100644 (file)
@@ -3,8 +3,6 @@
 USING: sequences accessors kernel locals assocs ;
 IN: game.models.util
 
-TUPLE: model attribute-buffer index-buffer vertex-format ;
-
 TUPLE: indexed-seq dseq iseq rassoc ;
 INSTANCE: indexed-seq sequence
 
index 641e4fe76395d0566a50e249a6994cc909bef00a..22a80a136e6bdbe3c60cbf0793e71d545080c4c8 100644 (file)
@@ -8,93 +8,109 @@ io io.encodings.ascii io.files io.files.temp kernel locals math
 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 )
     [
         {
@@ -102,110 +118,104 @@ TUPLE: vbo vertex-buffer index-buffer index-count vertex-format ;
             [ 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 ;