# 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. # Graphical effects module effects intrude import geometry::points_and_lines import mnit::opengles1 import moles # Position, or vector class Pos super Point[Float] # Addition of `self` and `other` fun +(other: Pos): Pos do return new Pos(other.x + x, other.y + y) end redef class PointerEvent # Convert to `Pos` fun to_pos: Pos do return new Pos(x, y) end redef class Float # Pretty alias to `pow` private fun ^(other: Float): Float do return self.pow(other) end # Graphical effect abstract class Effect # Time of creation since (appropriate) app lauch var created_at: Float = app.clock.total.to_f # Is this effect dead? var dead = false # Time to live of this effect, in seconds var ttl: Float = ttl_base + ttl_rand.rand is lazy # Time to live base value private var ttl_base: Float # Variation range to add to `ttl_base` private var ttl_rand: Float private fun update_and_draw(display: Display, t: Float) do end end # Full screen flash class Flash super Effect # Red value var r: Float # Green value var g: Float # Blue value var b: Float # Start alpha value var a: Float # Reduction in alpha value per seconds var da: Float redef fun update_and_draw(display, t) do var dt = t - created_at var a = a - da * dt if a <= 0.0 then dead = true return end native_flash(r, g, b, a, display.width.to_f, display.height.to_f) end private fun native_flash(r, g, b, a, w, h: Float) `{ GLfloat colors[] = { r, g, b, a, r, g, b, a, r, g, b, a, r, g, b, a, }; GLfloat coords[] = { 0.0, 0.0, 0.0, 0.0, h, 0.0, w, 0.0, 0.0, w, h, 0.0, }; glLoadIdentity(); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glVertexPointer(3, GL_FLOAT, 0, coords); glColorPointer(4, GL_FLOAT, 0, colors); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisable(GL_BLEND); if ((mnit_opengles_error_code = glGetError()) != GL_NO_ERROR) { fprintf(stderr, "Error drawing: %i\n", mnit_opengles_error_code); } `} end # Visible particle abstract class Particle super Effect # Effect image var img: Image end # Particle moving along a cubic Bézier curve class CubicBezierParticle super Particle # Points on this curve, from the start to the end with the handles in the middle # # Require: `points.length == 4` var points: Array[Pos] redef fun update_and_draw(display, t) do assert points.length == 4 var dt = t - created_at var p = dt / ttl var i = 1.0-p var bx = i*i*i * points[0].x + 3.0*i*i*p * points[1].x + 3.0*i*p*p * points[2].x + p*p*p*points[3].x var by = i*i*i * points[0].y + 3.0*i*i*p * points[1].y + 3.0*i*p*p * points[2].y + p*p*p*points[3].y img.scale = display_scale if display isa Opengles1Display then display.color(1.0, 1.0, 1.0, p) display.blit_centered(img, bx, by) if display isa Opengles1Display then display.reset_color if dt > ttl then dead = true end end # Particle falling like a feather class FeatheryParticle super Particle # Origin of effect var from: Pos # Randomized variation so this particle is unique var ddt: Float = pi.rand # Direction: `-1.0` for left, `1.0` for right var dir: Float = if 2.rand == 0 then -1.0 else 1.0 # Randomized variation on X var ddx: Float = (4.0 - 8.0.rand)^2.0 # Randomized variation on Y var ddy: Float = (12.0 - 24.0.rand)^2.0 redef fun update_and_draw(display, t) do var dt = t - created_at var dx = ddx + 30.0*(dt+ddt).sin dx *= dir var dy = ddy + 20.0*dt + 16.0*(dt*2.0+ddt*2.0).cos var pos = from + new Pos(dx, dy) if display isa Opengles1Display then display.color(1.0, 1.0, 1.0, 5.0-5.0*dt/ttl) display.blit_centered(img, pos.x, pos.y) if display isa Opengles1Display then display.reset_color if dt > ttl then dead = true end end # Particles that start small then grow bigger and fade out private class BlowUpParticle super Particle # Origin/center of effect var from: Pos redef fun update_and_draw(display, t) do var dt = t - created_at var p = dt/ttl if display isa Opengles1Display then display.color(1.0, 1.0, 1.0, 2.0-p*2.0) img.scale = p*4.0*display_scale display.blit_centered(img, from.x, from.y) if display isa Opengles1Display then display.reset_color if dt > ttl then dead = true end end redef class Screen private var particles = new Array[Particle] private var flashes = new Array[Flash] redef fun draw_hud(display) do var t = app.clock.total.to_f # Particles for particle in particles do particle.update_and_draw(display, t) for particle in particles.reverse_iterator do if particle.dead then particles.remove particle end # Flashes for flash in flashes do flash.update_and_draw(display, t) for flash in flashes.reverse_iterator do if flash.dead then flashes.remove flash end super end private var score_center = new Pos(48.0*display_scale, 32.0*display_scale) is lazy private var score_entry = new Pos(48.0*display_scale, 256.0*display_scale) is lazy private var score_history = new List[Int] private var score_history_max_length = 60 redef fun draw_score(display, score) do # Use an history to smooth the score score_history.add game.points if score_history.length > score_history_max_length then score_history.shift var sum = 0 for h in score_history do sum += h var avg = sum.to_f/score_history.length.to_f var d = game.points.to_f - avg # Color the score according to positive or negative changes var r = 1.0 var g = 1.0 var b = 1.0 if d > 0.0 then r = 1.0-d.to_f/2.0 b = r g = 1.0-d.to_f/10.0 else if d < 0.0 then g = 1.0+d.to_f/5.0 b = g end if display isa Opengles1Display then display.color(r, g, b, 1.0) # Draw the score itself super(display, avg.to_i) if display isa Opengles1Display then display.reset_color end end redef class HoleContent # Add a `CubicBezierParticle` from `event` to the score box with `img` private fun bezier_to_score(img: Image, event: PointerEvent) do app.screen.particles.add new CubicBezierParticle(2.0, 0.0, img, [event.to_pos, event.to_pos + new Pos(0.0, -128.0), app.screen.score_entry, app.screen.score_center]) end end redef class Mole # Number of hair particles private var n_hair_on_hit = 20 redef fun hit(game, hole, event) do super app.assets.hair.scale = display_scale for i in n_hair_on_hit.times do app.screen.particles.add new FeatheryParticle(2.0, 2.0, app.assets.hair, event.to_pos) end bezier_to_score(app.assets.point, event) app.screen.particles.add new BlowUpParticle(0.5, 0.0, app.assets.point, event.to_pos) end end redef class Trap # Image for `CubicBezierParticle` effect towards the score board protected fun penalty_img: Image do return app.assets.penalty_ten # `Flash` effects on hit protected fun flashes: Array[Flash] do return [new Flash(0.5, 0.0, 1.0, 0.0, 0.0, 0.5, 2.0)] end redef fun hit(game, hole, event) do super bezier_to_score(penalty_img, event) app.screen.particles.add new BlowUpParticle(0.5, 0.0, penalty_img, event.to_pos) app.screen.flashes.add_all flashes end end