From 456f663f78be842dc042cf2aaca5e2ce2ba3720a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 13 Jan 2016 13:02:27 -0500 Subject: [PATCH] lib/gamnit: intro gamnit depth MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Alexis Laferrière --- lib/gamnit/REAME.md | 3 + lib/gamnit/depth/README.md | 26 +++ lib/gamnit/depth/depth.nit | 61 ++++++ lib/gamnit/depth/depth_core.nit | 248 ++++++++++++++++++++++ lib/gamnit/depth/more_materials.nit | 385 ++++++++++++++++++++++++++++++++++ lib/gamnit/depth/more_meshes.nit | 102 +++++++++ lib/gamnit/depth/more_models.nit | 386 +++++++++++++++++++++++++++++++++++ 7 files changed, 1211 insertions(+) create mode 100644 lib/gamnit/depth/README.md create mode 100644 lib/gamnit/depth/depth.nit create mode 100644 lib/gamnit/depth/depth_core.nit create mode 100644 lib/gamnit/depth/more_materials.nit create mode 100644 lib/gamnit/depth/more_meshes.nit create mode 100644 lib/gamnit/depth/more_models.nit diff --git a/lib/gamnit/REAME.md b/lib/gamnit/REAME.md index 7802ab2..33c2501 100644 --- a/lib/gamnit/REAME.md +++ b/lib/gamnit/REAME.md @@ -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 index 0000000..61e9c40 --- /dev/null +++ b/lib/gamnit/depth/README.md @@ -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 index 0000000..e17b5af --- /dev/null +++ b/lib/gamnit/depth/depth.nit @@ -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 index 0000000..03a3970 --- /dev/null +++ b/lib/gamnit/depth/depth_core.nit @@ -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 index 0000000..40fe1fa --- /dev/null +++ b/lib/gamnit/depth/more_materials.nit @@ -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 index 0000000..9cbc830 --- /dev/null +++ b/lib/gamnit/depth/more_meshes.nit @@ -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 index 0000000..a126223 --- /dev/null +++ b/lib/gamnit/depth/more_models.nit @@ -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 -- 1.7.9.5