intrude import gamnit::flat
-# 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 `on_create`.
+# 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.
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
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.
#
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
# `GLDrawMode` used to display this mesh, defaults to `gl_TRIANGLES`
fun draw_mode: GLDrawMode do return gl_TRIANGLES
-
- # 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