* `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.
--- /dev/null
+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.
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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