--- /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.
+
+# Particle effects
+#
+# Particles are managed by instances of `ParticleSystem` that
+# are configured for a specific kind of particle.
+# For instance, a particle system can be created for a max of 100 particles,
+# with a smoke effect and a precise texture, as in:
+#
+# ~~~
+# var smoke = new ParticleSystem(100, app.smoke_program, new Texture("smoke.png"))
+# ~~~
+#
+# The system must be registered in `app.particle_systems` to be drawn on screen.
+#
+# Particles are added to a system with their configuration, as in:
+#
+# ~~~
+# var position = new Point3d[Float](0.0, 0.0, 0.0)
+# var scale = 2.0
+# var time_to_live = 1.0 # in seconds
+# smoke.add(position, scale, time_to_live)
+# ~~~
+module particles
+
+import depth_core
+
+redef class App
+
+ # Graphics program to display blowing up particles
+ var explosion_program = new ExplosionProgram
+
+ # Graphics program to display particles slowly drifting upwards
+ var smoke_program = new SmokeProgram
+
+ # Enabled particle emitters
+ #
+ # To be populated by the client program.
+ var particle_systems = new Array[ParticleSystem]
+end
+
+# Particle system using `program` and `texture` to draw each particles
+#
+# Each instance draws a maximum of `n_particles`.
+# If full, new particles replace the oldest particles.
+# Expired particle are still sent to the CPU but should be discarded by the vertex shader.
+class ParticleSystem
+
+ # Maximum number of particles
+ var n_particles: Int
+
+ private var total_particles = 0
+
+ # Program to draw the particles
+ var program: ParticleProgram
+
+ # Texture to apply on particles, if any
+ var texture: nullable Texture
+
+ # Clock used to set `ots` and `program::t`
+ #
+ # TODO control this value from the game logic to allow pausing and slowing time.
+ private var clock = new Clock
+
+ # Coordinates of each particle effects
+ private var centers = new Array[Float]
+
+ # Creation time of each particles
+ private var ots = new Array[Float]
+
+ # Scale of each particles
+ private var scales = new Array[Float]
+
+ # Time-to-live of each particle
+ private var ttls = new Array[Float]
+
+ # Add a particle at `center` with `scale`, living for `ttl` from `time_offset`
+ #
+ # `time_offset` is added to the creation time, it can be used to delay the
+ # apparition of a particle using a positive value.
+ #
+ # See the doc of the precise class of `program`, or the general `ParticleProgram`
+ # for information on the effect of these parameters.
+ fun add(center: Point3d[Float], scale: Float, ttl: Float, time_offset: nullable Float)
+ do
+ var i = total_particles % n_particles
+ total_particles += 1
+
+ centers[i*3 ] = center.x
+ centers[i*3+1] = center.y
+ centers[i*3+2] = center.z
+
+ ttls[i] = ttl
+ scales[i] = scale
+
+ time_offset = time_offset or else 0.0
+ ots[i] = clock.total.to_f + time_offset
+ end
+
+ # Draw all particles of this emitter
+ fun draw
+ do
+ if ots.is_empty then return
+
+ var program = program
+ program.use
+
+ var texture = texture
+ if texture != null then
+ glActiveTexture gl_TEXTURE0
+ glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
+ program.use_texture.uniform true
+ program.texture.uniform 0
+ else
+ program.use_texture.uniform false
+ end
+
+ program.scale.array_enabled = true
+ program.scale.array(scales, 1)
+
+ program.center.array_enabled = true
+ program.center.array(centers, 3)
+
+ program.color.array_enabled = false
+ program.color.uniform(1.0, 1.0, 1.0, 1.0)
+
+ program.ot.array_enabled = true
+ program.ot.array(ots, 1)
+
+ program.ttl.array_enabled = true
+ program.ttl.array(ttls, 1)
+
+ program.t.uniform clock.total.to_f
+ program.mvp.uniform app.world_camera.mvp_matrix
+
+ glDrawArrays(gl_POINTS, 0, ots.length)
+ end
+end
+
+# Particle drawing program using `gl_POINTS`
+#
+# This program should be subclassed to create custom particle effects.
+# Either `vertex_shader_source` and `vertex_shader_core` can be refined.
+abstract class ParticleProgram
+ super GamnitProgramFromSource
+
+ redef var vertex_shader_source = """
+ // Coordinates of particle effects
+ attribute vec4 center;
+
+ // Particles color tint
+ attribute vec4 color;
+ varying vec4 v_color;
+
+ // Per particle scaling
+ attribute float scale;
+
+ // Model view projection matrix
+ uniform mat4 mvp;
+
+ // Time-to-live of each particle
+ attribute float ttl;
+
+ // Creation time of each particle
+ attribute float ot;
+
+ // Current time
+ uniform float t;
+
+ void main()
+ {
+ // Pass varyings to the fragment shader
+ v_color = color;
+
+ float dt = t - ot;
+ float pt = dt/ttl;
+
+ // Discard expired or not yet created particles
+ if (dt > ttl || dt < 0.0) {
+ gl_PointSize = 0.0;
+ return;
+ }
+
+ {{{vertex_shader_core}}}
+ }
+ """
+
+ # Core GLSL code for `vertex_shader_source`
+ #
+ # Refine this function to easily tweak the position, size and color of particles.
+ #
+ # Reminder: Each execution of the vertex shader applies to a single particle.
+ #
+ # ## Input variables:
+ # * `center`: reference coordinates of the particle effect.
+ # This if often the center of the particle itself,
+ # but it can also be reference coordinates for a moving particle.
+ # * `mvp`: model-view-projection matrix.
+ # * `color`: color tint of the particle.
+ #
+ # * `t`: global seconds counter since the creation of this particle emitter.
+ # * `ot`: creation time of the particle, in seconds, in reference to `t`.
+ # * `dt`: seconds since creation of the particle.
+ # * `ttl`: time-to-live of the particle, in seconds.
+ # * `pt`: advancement of this particle in its lifetime, in `[0.0 .. 1.0]`.
+ #
+ # ## Output variables:
+ # * `gl_Position`: position of the particle in camera coordinates.
+ # * `gl_PointSize`: size of the particle in camera coordinates.
+ # Set to `0.0` to discard the particle.
+ # * `v_color`: tint applied to the particle.
+ # Assigned by default to the value of `color`.
+ #
+ # ## Reference implementation
+ #
+ # The default implementation apply the model-view-projection matrix on the position
+ # and scales according to the distance from the camera.
+ # Most particle effects should apply the same base logic as the default implementation.
+ # Here it is for reference:
+ #
+ # ~~~glsl
+ # gl_Position = center * mvp;
+ # gl_PointSize = scale / gl_Position.z;
+ # ~~~
+ fun vertex_shader_core: String do return """
+ gl_Position = center * mvp;
+ gl_PointSize = scale / gl_Position.z;
+ """
+
+ redef var fragment_shader_source = """
+ precision mediump float;
+
+ // Input from the vertex shader
+ varying vec4 v_color;
+
+ // Does this particle use a texture?
+ uniform bool use_texture;
+
+ // Texture to apply on this particle
+ uniform sampler2D texture;
+
+ void main()
+ {
+ if (use_texture) {
+ gl_FragColor = texture2D(texture, gl_PointCoord) * v_color;
+ if (gl_FragColor.a <= 0.01) discard;
+ } else {
+ gl_FragColor = v_color;
+ }
+ }
+ """ @ glsl_fragment_shader
+
+ # Coordinates of particle effects
+ var center = attributes["center"].as(AttributeVec4) is lazy
+
+ # Should this program use the texture `texture`?
+ var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
+
+ # Visible texture unit
+ var texture = uniforms["texture"].as(UniformSampler2D) is lazy
+
+ # Color tint per vertex
+ var color = attributes["color"].as(AttributeVec4) 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
+
+ # Creation time of each particle
+ var ot = attributes["ot"].as(AttributeFloat) is lazy
+
+ # Current time
+ var t = uniforms["t"].as(UniformFloat) is lazy
+
+ # Time-to-live of each particle
+ var ttl = attributes["ttl"].as(AttributeFloat) is lazy
+end
+
+# Graphics program to display blowing up particles
+class ExplosionProgram
+ super ParticleProgram
+
+ redef fun vertex_shader_core do return """
+ gl_Position = center * mvp;
+ gl_PointSize = scale / gl_Position.z * pt;
+
+ if (pt > 0.8) v_color.a = (1.0-pt)/0.2;
+ """
+end
+
+# Graphics program to display particles slowly drifting upwards
+class SmokeProgram
+ super ParticleProgram
+
+ redef fun vertex_shader_core do return """
+ vec4 c = center;
+ c.y += dt * 1.0;
+ c.x += dt * 0.1;
+
+ gl_Position = c * mvp;
+ gl_PointSize = scale / gl_Position.z * (pt+0.1);
+
+ if (pt < 0.1)
+ v_color.a = pt / 0.1;
+ else
+ v_color.a = 1.0 - pt*0.9;
+ """
+end