Merge: gamnit: new services and a lot of bug fixes and performance improvements
[nit.git] / lib / gamnit / depth / depth_core.nit
index 03a3970..e7e42de 100644 (file)
 # Base entities of the depth 3D game framework
 module depth_core
 
-intrude import gamnit::flat
+import gamnit::flat_core
 
-# Visible entity in the game world, represented by its `model` modified by the other attributes
+# Visible 3D entity in the game world
+#
+# Similar to `gamnit::Sprite` which is in 2D.
+#
+# Each actor associates a `model` to the position `center`.
+# The appearance is modified by `rotation`, `scale` and `alpha`,
+# as well as the attributes of `model` itself.
+#
+# ~~~
+# import gamnit::depth
+#
+# # Load model from the assets folder
+# var model = new Model("path/in/assets.obj")
+#
+# # Create and configure an actor
+# var actor = new Actor(model, new Point3d[Float](0.0, 0.0, 0.0))
+# actor.scale = 2.0
+#
+# # Add to the visible game world
+# app.actors.add actor
+# ~~~
 class Actor
 
-       # Model used to dray this actor
+       # Model used to draw 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
+       # Rotation around the X axis (+ looks up, - looks down)
+       #
+       # Positive values look up, and negative look down.
+       #
+       # All actor rotations follow the right hand rule.
+       # The default orientation of the model should look towards -Z.
+       var pitch = 0.0 is writable
+
+       # Rotation around the Y axis (+ turns left, - turns right)
+       #
+       # Positive values turn `self` to the left, and negative values to the right.
+       #
+       # All actor rotations follow the right hand rule.
+       # The default orientation of the model should look towards -Z.
+       var yaw = 0.0 is writable
+
+       # Rotation around the Z axis (looking to -Z: + turns counterclockwise, - clockwise)
+       #
+       # From the default camera point of view, looking down on the Z axis,
+       # positive values turn `self` counterclockwise, and negative values clockwise.
+       #
+       # All actor rotations follow the right hand rule.
+       # The default orientation of the model should look towards -Z.
+       var roll = 0.0 is writable
 
-       # Scale applied to this sprite
+       # Scale applied to the model
        var scale = 1.0 is writable
 
-       # Transparency applied to the texture on draw
+       # Transparency applied to the model on draw
+       #
+       # This value may be ignored by some materials.
+       # Non-opaque values may result in artifacts as there is no specialized
+       # support for transparent models and the depth buffer.
        var alpha = 1.0 is writable
 end
 
-# Entire 3D model defined by its `leaves`, an association of `Mesh` to `Material`
+# 3D model composed of `Mesh` and `Material`, loaded from the assets folder by default
+#
+# Instances can be created at any time and must be loaded after or at the end of `create_scene`.
+# If loading fails, the model is replaced by `placeholder_model`.
+#
+# ~~~
+# import gamnit::depth
+#
+# var model = new Model("path/in/assets.obj")
+# model.load
+# ~~~
+#
+# The most simple model is `LeafModel`, composed of a single `Mesh` and `Material`.
+# It can be easily created programmatically to display simple geometries.
+# Whereas `CompositeModel` is composed of one or many `LeafModel` and is usually
+# loaded from the assets folder as a `ModelAsset`.
+# Instances of `ModelAsset` must be in the format OBJ and MAT,
+# and their texture in PNG or JPG.
 abstract class Model
 
        # Load this model in memory
        fun load do end
 
+       # Errors raised at loading
+       var errors = new Array[Error]
+
        # 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
+
+       # Sub-models with names, usually declared in the asset file
+       var named_parts = new Map[Text, Model]
 end
 
 # Model composed of one or many other `LeafModel`
@@ -56,7 +125,7 @@ class CompositeModel
        redef var leaves = new Array[LeafModel]
 end
 
-# Single model with a `mesh` and `material`
+# Basic model with a single `mesh` and `material`
 #
 # Only leaves are actually drawn by the `material`.
 class LeafModel
@@ -71,23 +140,55 @@ class LeafModel
        redef var leaves = [self]
 end
 
-# Material for a model or how to draw the model
+# Material for models, or how to draw the model
+#
+# To create a simple basic blueish material, use `new Material`.
+#
+# Each class of material is associated to a `GLProgram` and its GPU shaders.
+# The simple material `SmoothMaterial` allows to set an ambient, diffuse and specular color.
+# To which `TextureMaterial` adds three textures, for each kind of light.
+# The `NormalsMaterial` may be useful for debugging, it show the orientation of
+# the normal vectors as colors.
+#
+# ~~~
+# import gamnit::depth
+#
+# var blueish_material = new Material
+# var redish_material = new SmoothMaterial([0.3, 0.0, 0.0],
+#                                          [0.6, 0.0, 0.0],
+#                                          [1.0, 1.0, 1.0])
+# var normals_material = new NormalsMaterial
+# ~~~
 abstract class Material
 
-       # Draw `actor`
+       # Draw a `model` from `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
+       fun draw(actor: Actor, model: LeafModel, camera: Camera) do end
 end
 
 # Mesh with all geometry data
+#
+# May be created via `Plane`, `Cube` or `UVSphere`,
+# or loaded from the assets folder indirectly with a `Model`.
+#
+# ~~~
+# import gamnit::depth
+#
+# var plane = new Plane
+# var cube = new Cube
+# var sphere = new UVSphere(1.0, 32, 16)
+# ~~~
 class Mesh
 
-       # Vertices coordinates
+       # Number for vertices
+       fun n_vertices: Int do return vertices.length / 3
+
+       # Vertices relative coordinates, 3 floats per vertex
        var vertices = new Array[Float] is lazy, writable
 
        # Indices to draw triangles with `glDrawElements`
@@ -97,151 +198,43 @@ class Mesh
 
        private var indices_c = new CUInt16Array.from(indices) is lazy, writable
 
-       # Normals on each vertex
+       # Normals, 3 floats per vertex
        var normals = new Array[Float] is lazy, writable
 
-       # Coordinates on the texture per vertex
+       # Coordinates on the texture, 2 floats 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
+       # `GLDrawMode` used to display this mesh, defaults to `gl_TRIANGLES`
+       fun draw_mode: GLDrawMode do return gl_TRIANGLES
 end
 
 # Source of light
-#
-# Instances of this class define a light source position and type.
-class Light
-
-       # TODO point light, spotlight, directional light, etc.
+abstract class Light
 
        # Center of this light source in world coordinates
        var position = new Point3d[Float](0.0, 1000.0, 0.0)
 end
 
+# Basic light projected from a single point
+class PointLight
+       super Light
+end
+
+# Source of light casting shadows
+abstract class LightCastingShadows
+       super Light
+
+       # View from the camera, used for shadow mapping, implemented by sub-classes
+       fun camera: Camera is abstract
+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
+       var light: Light = new PointLight is writable
 
        # TODO move `actors & light` to a scene object
        # TODO support more than 1 light