lib/gamnit: intro gamnit depth
authorAlexis Laferrière <alexis.laf@xymus.net>
Wed, 13 Jan 2016 18:02:27 +0000 (13:02 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Mon, 18 Jan 2016 19:59:07 +0000 (14:59 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/gamnit/REAME.md
lib/gamnit/depth/README.md [new file with mode: 0644]
lib/gamnit/depth/depth.nit [new file with mode: 0644]
lib/gamnit/depth/depth_core.nit [new file with mode: 0644]
lib/gamnit/depth/more_materials.nit [new file with mode: 0644]
lib/gamnit/depth/more_meshes.nit [new file with mode: 0644]
lib/gamnit/depth/more_models.nit [new file with mode: 0644]

index 7802ab2..33c2501 100644 (file)
@@ -11,6 +11,9 @@ It is modular, different parts of the framework are available through different
 * `flat` provides an easy to use API for 2D games based on sprites.
   Clients of this API must redefine `App::update` to update the game logic and fill `App::sprites` with objects to draw.
 
+* `depth` defines an API for 3D games.
+  It is based on a list of `actors`, with `Model` composed of `Mesh` and `Material`.
+
 * `cameras` provides cameras classes to produce MVP matrices which can be fed to shaders.
 
 * `limit_fps` redefines existing services of gamnit to limit the framerate to a client-defined value.
diff --git a/lib/gamnit/depth/README.md b/lib/gamnit/depth/README.md
new file mode 100644 (file)
index 0000000..61e9c40
--- /dev/null
@@ -0,0 +1,26 @@
+gamnit depth, a framework to create portable 3D games in Nit.
+
+This framework is based on a list of `Actor`, in `app::actors`, which are drawn to the screen at each frame. Each actor is composed of a model and other information specific to this instance: position in the world, rotation and scaling. Each `Model` is either a composite of models or it is composed of a `Mesh` defining its geometry and a `Material` defining how to draw the model. `Material` can be subclassed to use custom shaders.
+
+# Assets
+
+gamnit depth is built upon to portability framework _app.nit_ which provides a simple system to package and use asset files. Every file in the `assets/` folder at the root of a projet is packaged with the program at compilation for mobiles devices. These files can be loaded during execution using the many subclasses of `Asset`.
+
+~~~
+var my_texture = new Texture("textures/texture.png")
+var my_sound = new Sound("sounds/my_sound.mp3")
+var my_model = new Model("models/my_model.obj")
+var my_text = new TextAsset("simple_text_file.txt")
+~~~
+
+# In relation to gamnit _flat_
+
+gamnit flat is a framework for 2D games based on simple sprites and two drawing contexts: UI and world.
+
+The UI context works well with _depth_. It should be used to display simple 2D UI elements and to create menus with ease.
+
+However, the world context is difficultly compatible with _depth_. Only the `world_camera` from the _flat_ framework is used to display the world objects in the _depth_ framework as well.
+
+# Examples
+
+Take a look at the `model_viewer` project for a basic usage of the _depth_ framework combined with the _flat_ framework for the UI. Becaus of its simple goal, this projet has no game logic and only manipulates graphical objects. This projet is located in the `contrib` folder of the Nit repository.
diff --git a/lib/gamnit/depth/depth.nit b/lib/gamnit/depth/depth.nit
new file mode 100644 (file)
index 0000000..e17b5af
--- /dev/null
@@ -0,0 +1,61 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Framework for 3D games in Nit
+module depth
+
+intrude import more_materials
+import more_models
+
+redef class App
+
+       redef fun on_create
+       do
+               super
+
+               # Move the camera back a bit
+               world_camera.reset_height(10.0)
+               world_camera.near = 0.1
+
+               # Prepare programs
+               var program = versatile_program
+               program.compile_and_link
+               var gamnit_error = program.error
+               assert gamnit_error == null else print_error gamnit_error
+
+               program = normals_program
+               normals_program.compile_and_link
+               gamnit_error = program.error
+               assert gamnit_error == null else print_error gamnit_error
+       end
+
+       # Draw all element in `actors`
+       redef fun frame_core_draw(display)
+       do
+               super
+
+               # Update cameras on both our programs
+               versatile_program.use
+               versatile_program.mvp.uniform world_camera.mvp_matrix
+
+               normals_program.use
+               normals_program.mvp.uniform app.world_camera.mvp_matrix
+
+               for actor in actors do
+                       for leaf in actor.model.leaves do
+                               leaf.material.draw(actor, leaf)
+                       end
+               end
+       end
+end
diff --git a/lib/gamnit/depth/depth_core.nit b/lib/gamnit/depth/depth_core.nit
new file mode 100644 (file)
index 0000000..03a3970
--- /dev/null
@@ -0,0 +1,248 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Base entities of the depth 3D game framework
+module depth_core
+
+intrude import gamnit::flat
+
+# Visible entity in the game world, represented by its `model` modified by the other attributes
+class Actor
+
+       # Model used to dray this actor
+       var model: Model
+
+       # Position of this sprite in world coordinates
+       var center: Point3d[Float] is writable
+
+       # Rotation on the Z axis
+       var rotation = 0.0 is writable
+
+       # Scale applied to this sprite
+       var scale = 1.0 is writable
+
+       # Transparency applied to the texture on draw
+       var alpha = 1.0 is writable
+end
+
+# Entire 3D model defined by its `leaves`, an association of `Mesh` to `Material`
+abstract class Model
+
+       # Load this model in memory
+       fun load do end
+
+       # All `LeafModel` composing this model
+       #
+       # Usually, there is one `LeafModel` per material.
+       # At each frame, each material is asked to draw all the live `LeafModel` instaces.
+       fun leaves: Array[LeafModel] is abstract
+end
+
+# Model composed of one or many other `LeafModel`
+class CompositeModel
+       super Model
+
+       redef var leaves = new Array[LeafModel]
+end
+
+# Single model with a `mesh` and `material`
+#
+# Only leaves are actually drawn by the `material`.
+class LeafModel
+       super Model
+
+       # Mesh forming this model
+       var mesh: Mesh
+
+       # Material applied on this model
+       var material: Material
+
+       redef var leaves = [self]
+end
+
+# Material for a model or how to draw the model
+abstract class Material
+
+       # Draw `actor`
+       #
+       # This method should be refined by subclasses as the default implementation is a no-op.
+       #
+       # This method is called on many materials for many `actor` and `model` at each frame.
+       # It is expected to use a `GLProgram` and call an equivalent to `glDrawArrays`.
+       # However, it should not call `glClear` nor `GamnitDisplay::flip`.
+       fun draw(actor: Actor, model: LeafModel) do end
+end
+
+# Mesh with all geometry data
+class Mesh
+
+       # Vertices coordinates
+       var vertices = new Array[Float] is lazy, writable
+
+       # Indices to draw triangles with `glDrawElements`
+       #
+       # If `not_empty`, use `glDrawElements`, otherwise use `glDrawArrays`.
+       var indices = new Array[Int] is lazy, writable
+
+       private var indices_c = new CUInt16Array.from(indices) is lazy, writable
+
+       # Normals on each vertex
+       var normals = new Array[Float] is lazy, writable
+
+       # Coordinates on the texture per vertex
+       var texture_coords = new Array[Float] is lazy, writable
+
+       # Create an UV sphere of `radius` with `n_meridians` and `n_parallels`
+       init uv_sphere(radius: Float, n_meridians, n_parallels: Int)
+       do
+               var w = n_meridians
+               var h = n_parallels
+
+               var vertices = new Array[Float].with_capacity(w*h*3)
+               self.vertices = vertices
+
+               var texture_coords = new Array[Float].with_capacity(w*h*2)
+               self.texture_coords = texture_coords
+
+               var normals = new Array[Float].with_capacity(w*h*3)
+               self.normals = normals
+
+               # Build vertices
+               for m in [0..w[ do
+                       for p in [0..h[ do
+                               var u = m.to_f * 2.0 * pi / (w-1).to_f
+                               var v = p.to_f * pi / (h-1).to_f
+
+                               vertices.add radius * u.cos * v.sin
+                               vertices.add radius * v.cos
+                               vertices.add radius * u.sin * v.sin
+
+                               texture_coords.add (1.0 - m.to_f/(w-1).to_f)
+                               texture_coords.add(p.to_f/(h-1).to_f)
+
+                               normals.add u.cos * v.sin
+                               normals.add v.cos
+                               normals.add u.sin * v.sin
+                       end
+               end
+
+               # Build faces
+               var indices = new Array[Int].with_capacity((w-1)*(h-1)*6)
+               self.indices = indices
+               for m in [0..w-1[ do
+                       for p in [0..h-1[ do
+                               var a = m*h + p
+
+                               indices.add a
+                               indices.add a+h
+                               indices.add a+1
+
+                               indices.add a+h
+                               indices.add a+h+1
+                               indices.add a+1
+                       end
+               end
+       end
+
+       # Dimensions of this geometry using the min and max of all points on each axis
+       var dimensions: Point3d[Float] is lazy, writable do
+               assert vertices.length % 3 == 0
+
+               var minx = inf
+               var miny = inf
+               var minz = inf
+               var maxx = -inf
+               var maxy = -inf
+               var maxz = -inf
+
+               var i = 0
+               while i < vertices.length do
+                       var x = vertices[i]
+                       i += 1
+                       var y = vertices[i]
+                       i += 1
+                       var z = vertices[i]
+                       i += 1
+
+                       minx = minx.min(x)
+                       miny = miny.min(y)
+                       minz = minz.min(z)
+
+                       maxx = maxx.max(x)
+                       maxy = maxy.max(y)
+                       maxz = maxz.max(z)
+               end
+
+               return new Point3d[Float](maxx-minx, maxy-miny, maxz-minz)
+       end
+
+       # Center of the geometry
+       var center: Point3d[Float] is lazy, writable do
+               assert vertices.length % 3 == 0
+
+               var minx = inf
+               var miny = inf
+               var minz = inf
+               var maxx = -inf
+               var maxy = -inf
+               var maxz = -inf
+
+               var i = 0
+               while i < vertices.length do
+                       var x = vertices[i]
+                       i += 1
+                       var y = vertices[i]
+                       i += 1
+                       var z = vertices[i]
+                       i += 1
+
+                       minx = minx.min(x)
+                       miny = miny.min(y)
+                       minz = minz.min(z)
+
+                       maxx = maxx.max(x)
+                       maxy = maxy.max(y)
+                       maxz = maxz.max(z)
+               end
+
+               var center = new Point3d[Float](
+                       (minx+maxx)/2.0,
+                       (miny+maxy)/2.0,
+                       (minz+maxz)/2.0)
+               return center
+       end
+end
+
+# Source of light
+#
+# Instances of this class define a light source position and type.
+class Light
+
+       # TODO point light, spotlight, directional light, etc.
+
+       # Center of this light source in world coordinates
+       var position = new Point3d[Float](0.0, 1000.0, 0.0)
+end
+
+redef class App
+
+       # Live actors to be drawn on screen
+       var actors = new Array[Actor]
+
+       # Single light of the scene
+       var light = new Light
+
+       # TODO move `actors & light` to a scene object
+       # TODO support more than 1 light
+end
diff --git a/lib/gamnit/depth/more_materials.nit b/lib/gamnit/depth/more_materials.nit
new file mode 100644 (file)
index 0000000..40fe1fa
--- /dev/null
@@ -0,0 +1,385 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Various material implementations
+module more_materials
+
+intrude import depth_core
+intrude import flat
+
+# Simple material with static colors used for debugging or display abstract objects
+class SmoothMaterial
+       super Material
+
+       # Get the default blueish material
+       init default do init(
+               [0.0, 0.0, 0.3, 1.0],
+               [0.0, 0.0, 0.6, 1.0],
+               [1.0, 1.0, 1.0, 1.0])
+
+       # Ambient color, always visible
+       var ambient_color: Array[Float]
+
+       # Diffuse color when covered by a light source
+       var diffuse_color: Array[Float]
+
+       # Specular color affecting reflections
+       var specular_color: Array[Float]
+
+       redef fun draw(actor, model)
+       do
+               var program = app.versatile_program
+               program.use
+
+               var mesh = model.mesh
+
+               # Actor specs
+               program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0)
+               program.scale.uniform actor.scale
+               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+
+               # From mesh
+               program.coord.array_enabled = true
+               program.coord.array(mesh.vertices, 3)
+
+               program.normal.array_enabled = true
+               program.normal.array(mesh.normals, 3)
+
+               # No textures
+               program.use_map_ambient.uniform false
+               program.use_map_diffuse.uniform false
+               program.use_map_specular.uniform false
+               program.tex_coord.array_enabled = false
+
+               # Lights
+               program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
+
+               # Camera
+               program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+
+               # Colors from the material
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1], ambient_color[2], ambient_color[3]*actor.alpha)
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3]*actor.alpha)
+               program.specular_color.uniform(specular_color[0], specular_color[1], specular_color[2], specular_color[3]*actor.alpha)
+
+               # Execute draw
+               if mesh.indices.is_empty then
+                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+               else
+                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+               end
+       end
+end
+
+# Material with potential `diffuse_texture` and `specular_texture`
+class TexturedMaterial
+       super SmoothMaterial
+
+       # Texture applied to the ambient_color
+       var ambient_texture: nullable Texture = null is writable
+
+       # Texture applied to the diffuse color
+       var diffuse_texture: nullable Texture = null is writable
+
+       # Texture applied to the specular color
+       var specular_texture: nullable Texture = null is writable
+
+       redef fun draw(actor, model)
+       do
+               var mesh = model.mesh
+
+               var program = app.versatile_program
+               program.use
+
+               var need_tex_coord = false
+
+               var texture = ambient_texture
+               if texture != null then
+                       glActiveTexture gl_TEXTURE0
+                       glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
+                       program.use_map_ambient.uniform true
+                       program.map_ambient.uniform 0
+                       need_tex_coord = true
+               else
+                       program.use_map_ambient.uniform false
+               end
+
+               texture = diffuse_texture
+               if texture != null then
+                       glActiveTexture gl_TEXTURE1
+                       glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
+                       program.use_map_diffuse.uniform true
+                       program.map_diffuse.uniform 1
+                       need_tex_coord = true
+               else
+                       program.use_map_diffuse.uniform false
+               end
+
+               texture = specular_texture
+               if texture != null then
+                       glActiveTexture gl_TEXTURE2
+                       glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
+                       program.use_map_specular.uniform true
+                       program.map_specular.uniform 2
+                       need_tex_coord = true
+               else
+                       program.use_map_specular.uniform false
+               end
+
+               program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0)
+               program.scale.uniform actor.scale
+
+               program.tex_coord.array_enabled = need_tex_coord
+               program.tex_coord.array(mesh.texture_coords, 2)
+
+               program.coord.array_enabled = true
+               program.coord.array(mesh.vertices, 3)
+               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1], ambient_color[2], ambient_color[3]*actor.alpha)
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3]*actor.alpha)
+               program.specular_color.uniform(specular_color[0], specular_color[1], specular_color[2], specular_color[3]*actor.alpha)
+
+               program.normal.array_enabled = true
+               program.normal.array(mesh.normals, 3)
+
+               program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
+               program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+
+               if mesh.indices.is_empty then
+                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+               else
+                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+               end
+       end
+end
+
+# Simple material using the normals of the surface as color
+#
+# Each axis composing the normals are translated to color values.
+# This material is useful for debugging normals or display models in a colorful way.
+class NormalsMaterial
+       super Material
+
+       redef fun draw(actor, model)
+       do
+               var program = app.normals_program
+               program.use
+               program.mvp.uniform app.world_camera.mvp_matrix
+
+               var mesh = model.mesh
+
+               # TODO apply normal map
+
+               program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0)
+               program.scale.uniform actor.scale
+
+               program.tex_coord.array_enabled = true
+               program.tex_coord.array(mesh.texture_coords, 2)
+
+               program.coord.array_enabled = true
+               program.coord.array(mesh.vertices, 3)
+               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+
+               program.normal.array_enabled = true
+               program.normal.array(mesh.normals, 3)
+
+               if mesh.indices.is_empty then
+                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+               else
+                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+               end
+       end
+end
+
+# Graphic program to display 3D models with Lamber diffuse lighting
+class LambertProgram
+       super GamnitProgramFromSource
+
+       redef var vertex_shader_source = """
+               // Vertex coordinates
+               attribute vec4 coord;
+
+               // Vertex translation
+               attribute vec4 translation;
+
+               // Vertex scaling
+               attribute float scale;
+
+               // Vertex coordinates on textures
+               attribute vec2 tex_coord;
+
+               // Vertex normal
+               attribute vec3 normal;
+
+               // Model view projection matrix
+               uniform mat4 mvp;
+
+               uniform mat4 rotation;
+
+               // Lights config
+               uniform vec3 light_center;
+
+               // Coordinates of the camera
+               uniform vec3 camera;
+
+               // Output for the fragment shader
+               varying vec2 v_tex_coord;
+               varying vec3 v_normal;
+               varying vec4 v_light_center;
+               varying vec4 v_camera;
+
+               void main()
+               {
+                       // Pass varyings to the fragment shader
+                       v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
+                       v_normal = normalize(vec4(normal, 0.0) * rotation * mvp).xyz;
+
+                       gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
+
+                       // TODO compute v_light_center and v_camera on the CPU side and pass as uniforms
+                       v_light_center = vec4(light_center, 0.0) * mvp;
+                       v_camera = vec4(camera, 0.0) * mvp;
+               }
+               """ @ glsl_vertex_shader
+
+       redef var fragment_shader_source = """
+               precision mediump float;
+
+               // Input from the vertex shader
+               varying vec2 v_tex_coord;
+               varying vec3 v_normal;
+               varying vec4 v_light_center;
+               varying vec4 v_camera;
+
+               // Colors
+               uniform vec4 ambient_color;
+               uniform vec4 diffuse_color;
+               uniform vec4 specular_color;
+
+               // Ambient map
+               uniform bool use_map_ambient;
+               uniform sampler2D map_ambient;
+
+               // Diffuse map
+               uniform bool use_map_diffuse;
+               uniform sampler2D map_diffuse;
+
+               // Specular map
+               uniform bool use_map_specular;
+               uniform sampler2D map_specular;
+
+               // Bump map
+               uniform bool use_map_bump;
+               uniform sampler2D map_bump;
+
+               // Normal map
+               uniform bool use_map_normal;
+               uniform sampler2D map_normal;
+
+               void main()
+               {
+                       // Lambert diffusion
+                       vec3 light_dir = normalize(v_light_center.xyz);
+                       float lambert = max(dot(light_dir, v_normal), 0.0);
+
+                       if (use_map_ambient)
+                               gl_FragColor = ambient_color + texture2D(map_ambient, v_tex_coord);
+                       else
+                               gl_FragColor = ambient_color;
+
+                       if (use_map_diffuse)
+                               gl_FragColor += lambert * diffuse_color * texture2D(map_diffuse, v_tex_coord);
+                       else
+                               gl_FragColor += lambert * diffuse_color;
+               }
+               """ @ glsl_fragment_shader
+
+       # Vertices coordinates
+       var coord = attributes["coord"].as(AttributeVec4) is lazy
+
+       # Should this program use the texture `map_ambient`?
+       var use_map_ambient = uniforms["use_map_ambient"].as(UniformBool) is lazy
+
+       # Ambient texture unit
+       var map_ambient = uniforms["map_ambient"].as(UniformSampler2D) is lazy
+
+       # Should this program use the texture `map_diffuse`?
+       var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
+
+       # Diffuser texture unit
+       var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
+
+       # Should this program use the texture `map_specular`?
+       var use_map_specular = uniforms["use_map_specular"].as(UniformBool) is lazy
+
+       # Specularity texture unit
+       var map_specular = uniforms["map_specular"].as(UniformSampler2D) is lazy
+
+       # Normal per vertex
+       var normal = attributes["normal"].as(AttributeVec3) is lazy
+
+       # Coordinates on the textures, per vertex
+       var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
+
+       # Ambient color
+       var ambient_color = uniforms["ambient_color"].as(UniformVec4) is lazy
+
+       # Diffuse color
+       var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
+
+       # Specular color
+       var specular_color = uniforms["specular_color"].as(UniformVec4) is lazy
+
+       # Center position of the light
+       var light_center = uniforms["light_center"].as(UniformVec3) is lazy
+
+       # Camera position
+       var camera = uniforms["camera"].as(UniformVec3) is lazy
+
+       # Translation applied to each vertex
+       var translation = attributes["translation"].as(AttributeVec4) is lazy
+
+       # Rotation matrix
+       var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+
+       # Scaling per vertex
+       var scale = attributes["scale"].as(AttributeFloat) is lazy
+
+       # Model view projection matrix
+       var mvp = uniforms["mvp"].as(UniformMat4) is lazy
+end
+
+# Program to color objects from their normal vectors
+class NormalProgram
+       super LambertProgram
+
+       redef var fragment_shader_source = """
+               precision mediump float;
+
+               // Input from the vertex shader
+               varying vec3 v_normal;
+
+               void main()
+               {
+                       gl_FragColor = vec4(v_normal*0.5 + 0.5, 1.0);
+               }
+               """ @ glsl_fragment_shader
+end
+
+redef class App
+       private var versatile_program = new LambertProgram is lazy
+
+       private var normals_program = new NormalProgram is lazy
+end
diff --git a/lib/gamnit/depth/more_meshes.nit b/lib/gamnit/depth/more_meshes.nit
new file mode 100644 (file)
index 0000000..9cbc830
--- /dev/null
@@ -0,0 +1,102 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# More simple geometric meshes
+module more_meshes
+
+import geometry
+import depth_core
+
+# Simple flat mesh, sits on the axes X and Z, normal on Y
+class Plane
+       super Mesh
+
+       # TODO allow for complex rotation, either at creation or in Actor
+
+       redef var vertices is lazy do
+               var a = [-0.5, 0.0, -0.5]
+               var b = [ 0.5, 0.0, -0.5]
+               var c = [-0.5, 0.0,  0.5]
+               var d = [ 0.5, 0.0,  0.5]
+
+               var vertices = new Array[Float]
+               for v in [c, d, a, b] do vertices.add_all v
+               return vertices
+       end
+
+       redef var normals: Array[Float] is lazy do
+               var normals = new Array[Float]
+               for i in 4.times do normals.add_all([0.0, 1.0, 0.0])
+               return normals
+       end
+
+       redef var texture_coords: Array[Float] is lazy do
+               var offset_left = 0.0
+               var offset_top = 0.0
+               var offset_right = 1.0
+               var offset_bottom = 1.0
+               var a = [offset_left,  offset_bottom]
+               var b = [offset_right, offset_bottom]
+               var c = [offset_left,  offset_top]
+               var d = [offset_right, offset_top]
+
+               var texture_coords = new Array[Float]
+               for v in [c, d, a, b] do texture_coords.add_all v
+               return texture_coords
+       end
+
+       redef var center = new Point3d[Float](0.0, 0.0, 0.0) is lazy
+end
+
+# Cube, with 6 faces
+class Cube
+       super Mesh
+
+       redef var vertices is lazy do
+               var a = [-0.5, -0.5, -0.5]
+               var b = [ 0.5, -0.5, -0.5]
+               var c = [-0.5,  0.5, -0.5]
+               var d = [ 0.5,  0.5, -0.5]
+
+               var e = [-0.5, -0.5,  0.5]
+               var f = [ 0.5, -0.5,  0.5]
+               var g = [-0.5,  0.5,  0.5]
+               var h = [ 0.5,  0.5,  0.5]
+
+               var vertices = new Array[Float]
+               for v in [a, c, d, a, d, b, # front
+                         f, h, g, f, g, e, # back
+                         b, d, h, b, h, f, # right
+                         e, g, c, e, c, a, # left
+                         e, a, b, e, b, f, # bottom
+                         c, g, h, c, h, d  # top
+                                 ] do vertices.add_all v
+               return vertices
+       end
+
+       redef var normals is lazy do
+               var normals = new Array[Float]
+               var faces_normals = [
+                       [0.0, 0.0, -1.0],
+                       [0.0, 0.0,  1.0],
+                       [ 1.0, 0.0, 0.0],
+                       [-1.0, 0.0, 0.0],
+                       [0.0, -1.0, 0.0],
+                       [0.0,  1.0, 0.0]]
+               for f in faces_normals do for i in 6.times do normals.add_all f
+               return normals
+       end
+
+       redef var center = new Point3d[Float](0.0, 0.0, 0.0) is lazy
+end
diff --git a/lib/gamnit/depth/more_models.nit b/lib/gamnit/depth/more_models.nit
new file mode 100644 (file)
index 0000000..a126223
--- /dev/null
@@ -0,0 +1,386 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Services to load models from the assets folder
+module more_models
+
+intrude import depth_core
+
+import gamnit::obj
+import gamnit::mtl
+
+import more_materials
+import more_meshes
+
+redef class Model
+       # Prepare to load a model from the assets folder
+       new(path: Text) do return new ModelAsset(path.to_s)
+end
+
+# Model loaded from a file in the asset folder
+#
+# In case of error, `error` is set accordingly.
+# If the error is on the mesh, `mesh` is set to a default `new Mesh.cube`.
+# If the material is missing or it failed to load, `material` is set to a `new SimpleMaterial.default`.
+class ModelAsset
+       super Model
+       super Asset
+
+       init do models.add self
+
+       redef fun load
+       do
+               var ext = path.file_extension
+               if ext == "obj" then
+                       load_obj_file
+               else
+                       print_error "Model failed to load: Extension '{ext or else "null"}' unrecognized"
+               end
+
+               if leaves.is_empty then
+                       # Nothing was loaded, use a cube with the default material
+                       var leaf = new LeafModel(new Cube, new SmoothMaterial.default)
+                       leaves.add leaf
+               end
+       end
+
+       private fun load_obj_file
+       do
+               # Read .obj description from assets
+               var text_asset = new TextAsset(path)
+               var content = text_asset.to_s
+               if content.is_empty then
+                       print_error "Model failed to load: Asset empty at '{self.path}'"
+                       leaves.add new LeafModel(new Cube, new SmoothMaterial.default)
+                       return
+               end
+
+               # Parse .obj description
+               var parser = new ObjFileParser(content)
+               var obj_def = parser.parse
+               if obj_def == null then
+                       print_error "Model failed to load: .obj format error on '{self.path}'"
+                       leaves.add new LeafModel(new Cube, new SmoothMaterial.default)
+                       return
+               end
+
+               # Check for errors
+               if debug_gamnit then assert obj_def.is_coherent
+
+               # Build models
+               var converter = new ModelFromObj(path, obj_def)
+               converter.models leaves
+       end
+
+       redef var leaves = new Array[LeafModel]
+end
+
+# Short-lived service to convert an `ObjDef` to `models`
+#
+# Limitations: This service only support faces with 3 or 4 vertices.
+# Faces with more vertices should be triangulated by the modeling tool.
+private class ModelFromObj
+
+       # Path to the .obj file in the assets folder, used to find .mtl files
+       var path: String
+
+       # Parsed .obj definition
+       var obj_def: ObjDef
+
+       fun models(array: Array[LeafModel])
+       do
+               # Sort faces by material
+               var mtl_to_faces = new MultiHashMap[String, ObjFace]
+               for face in obj_def.faces do
+                       var mtl_lib_name = face.material_lib
+                       var mtl_name = face.material_name
+
+                       var full_name = ""
+                       if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+
+                       mtl_to_faces[full_name].add face
+               end
+
+               # Load material libs
+               # TODO do not load each libs more than once
+               var mtl_libs = new Map[String, Map[String, MtlDef]]
+               var lib_names = obj_def.material_libs
+               for name in lib_names do
+                       var lib_path = self.path.dirname / name
+                       var lib_asset = new TextAsset(lib_path)
+                       lib_asset.load
+
+                       var error = lib_asset.error
+                       if error != null then
+                               print_error error.to_s
+                               continue
+                       end
+
+                       var mtl_parser = new MtlFileParser(lib_asset.to_s)
+                       var mtl_lib = mtl_parser.parse
+                       mtl_libs[name] = mtl_lib
+               end
+
+               # Create 1 mesh per material, and prepare materials
+               var mesh_to_mtl = new Map[Mesh, nullable MtlDef]
+               var texture_names = new Set[String]
+               for full_name, faces in mtl_to_faces do
+
+                       # Create mesh
+                       var mesh = new Mesh
+                       mesh.vertices = vertices(faces)
+                       mesh.normals = normals(faces)
+                       mesh.texture_coords = texture_coords(faces)
+
+                       # Material
+                       var mtl_def = null
+
+                       var mtl_lib_name = faces.first.material_lib
+                       var mtl_name = faces.first.material_name
+                       if mtl_lib_name != null and mtl_name != null then
+                               var mtl_lib = mtl_libs[mtl_lib_name]
+                               var mtl = mtl_lib.get_or_null(mtl_name)
+                               if mtl != null then
+                                       mtl_def = mtl
+
+                                       for e in mtl.maps do
+                                               texture_names.add self.path.dirname / e
+                                       end
+                               else
+                                       print_error "mtl '{mtl_name}' not found in '{mtl_lib_name}'"
+                               end
+                       end
+
+                       mesh_to_mtl[mesh] = mtl_def
+               end
+
+               # Load textures need for these materials
+               for name in texture_names do
+                       if not asset_textures_by_name.keys.has(name) then
+                               var tex = new GamnitAssetTexture(name)
+                               asset_textures_by_name[name] = tex
+                       end
+               end
+
+               # Create final `Materials` from defs and textures
+               var materials = new Map[MtlDef, Material]
+               for mtl in mesh_to_mtl.values do
+                       if mtl == null then continue
+
+                       var ambient = mtl.ambient.to_a
+                       ambient.add 1.0
+
+                       var diffuse = mtl.diffuse.to_a
+                       diffuse.add 1.0
+
+                       var specular = mtl.specular.to_a
+                       specular.add 1.0
+
+                       var material = new TexturedMaterial(ambient, diffuse, specular)
+                       materials[mtl] = material
+
+                       var tex_name = mtl.map_ambient
+                       if tex_name != null then
+                               tex_name = self.path.dirname / tex_name
+                               material.ambient_texture = asset_textures_by_name[tex_name]
+                       end
+
+                       tex_name = mtl.map_diffuse
+                       if tex_name != null then
+                               tex_name = self.path.dirname / tex_name
+                               material.diffuse_texture = asset_textures_by_name[tex_name]
+                       end
+
+                       tex_name = mtl.map_specular
+                       if tex_name != null then
+                               tex_name = self.path.dirname / tex_name
+                               material.specular_texture = asset_textures_by_name[tex_name]
+                       end
+               end
+
+               # Create models and store them
+               for mesh, mtl_def in mesh_to_mtl do
+                       var material = materials.get_or_null(mtl_def)
+                       if material == null then material = new SmoothMaterial.default
+
+                       var model = new LeafModel(mesh, material)
+                       array.add model
+               end
+       end
+
+       # Compute the vertices coordinates of `faces` in a flat `Array[Float]`
+       fun vertices(faces: Array[ObjFace]): Array[Float] do
+               var obj_def = obj_def
+
+               var vertices = new Array[Float]
+               for face in faces do
+
+                       # 1st triangle
+                       var count = 0
+                       for e in face.vertices do
+                               var i = e.vertex_point_index - 1
+                               var v = obj_def.vertex_points[i]
+
+                               vertices.add v.x
+                               vertices.add v.y
+                               vertices.add v.z
+
+                               if count == 2 then break
+                               count += 1
+                       end
+
+                       # If square, 2nd triangle
+                       #
+                       # This may not support all vertices ordering.
+                       if face.vertices.length > 3 then
+                               for e in [face.vertices[0], face.vertices[2], face.vertices[3]] do
+                                       var i = e.vertex_point_index - 1
+                                       var v = obj_def.vertex_points[i]
+
+                                       vertices.add v.x
+                                       vertices.add v.y
+                                       vertices.add v.z
+                               end
+                       end
+
+                       # TODO use polygon triangulation to support larger polygons
+               end
+               return vertices
+       end
+
+       # Compute the normals of `faces` in a flat `Array[Float]`
+       fun normals(faces: Array[ObjFace]): Array[Float] do
+               var obj_def = obj_def
+
+               var normals = new Array[Float]
+               for face in faces do
+                       # 1st triangle
+                       var count = 0
+                       for e in face.vertices do
+                               var i = e.normal_index
+                               if i == null then
+                                       compute_and_append_normal(normals, face)
+                               else
+                                       var v = obj_def.normals[i-1]
+                                       normals.add v.x
+                                       normals.add v.y
+                                       normals.add v.z
+                               end
+
+                               if count == 2 then break
+                               count += 1
+                       end
+
+                       # If square, 2nd triangle
+                       #
+                       # This may not support all vertices ordering.
+                       if face.vertices.length > 3 then
+                               for e in [face.vertices[0], face.vertices[2], face.vertices[3]] do
+                                       var i = e.normal_index
+                                       if i == null then
+                                               compute_and_append_normal(normals, face)
+                                       else
+                                               var v = obj_def.normals[i-1]
+                                               normals.add v.x
+                                               normals.add v.y
+                                               normals.add v.z
+                                       end
+                               end
+                       end
+               end
+               return normals
+       end
+
+       # Compute the normal of `face` and append it as 3 floats to `seq`
+       #
+       # Resulting normals are not normalized.
+       fun compute_and_append_normal(seq: Sequence[Float], face: ObjFace)
+       do
+               var i1 = face.vertices[0].vertex_point_index
+               var i2 = face.vertices[1].vertex_point_index
+               var i3 = face.vertices[2].vertex_point_index
+
+               var v1 = obj_def.vertex_points[i1-1]
+               var v2 = obj_def.vertex_points[i2-1]
+               var v3 = obj_def.vertex_points[i3-1]
+
+               var vx = v2.x - v1.x
+               var vy = v2.y - v1.y
+               var vz = v2.z - v1.z
+               var wx = v3.x - v1.x
+               var wy = v3.y - v1.y
+               var wz = v3.z - v1.z
+
+               var nx = (vy*wz) - (vz*wy)
+               var ny = (vz*wx) - (vx*wz)
+               var nz = (vx*wy) - (vy*wx)
+
+               # Append to `seq`
+               seq.add nx
+               seq.add ny
+               seq.add nz
+       end
+
+       # Compute the texture coordinates of `faces` in a flat `Array[Float]`
+       fun texture_coords(faces: Array[ObjFace]): Array[Float] do
+               var obj_def = obj_def
+
+               var coords = new Array[Float]
+               for face in faces do
+
+                       # 1st triangle
+                       var count = 0
+                       for e in face.vertices do
+                               var i = e.texture_coord_index
+                               if i == null then
+                                       coords.add 0.0
+                                       coords.add 0.0
+                               else
+                                       var tc = obj_def.texture_coords[i-1]
+                                       coords.add tc.u
+                                       coords.add tc.v
+                               end
+
+                               if count == 2 then break
+                               count += 1
+                       end
+
+                       # If square, 2nd triangle
+                       #
+                       # This may not support all vertices ordering.
+                       if face.vertices.length > 3 then
+                               for e in [face.vertices[0], face.vertices[2], face.vertices[3]] do
+                                       var i = e.texture_coord_index
+                                       if i == null then
+                                               coords.add 0.0
+                                               coords.add 0.0
+                                       else
+                                       var tc = obj_def.texture_coords[i-1]
+                                       coords.add tc.u
+                                       coords.add tc.v
+                                       end
+                               end
+                       end
+               end
+               return coords
+       end
+end
+
+redef class Sys
+       # Textures loaded from .mtl files for models
+       var asset_textures_by_name = new Map[String, GamnitAssetTexture]
+
+       # All instantiated asset models
+       var models = new Set[ModelAsset]
+end