# devices. It is used to position the sprites in `App::ui_sprites`.
#
# See the sample game at `contrib/asteronits/` and the basic project template
-# at `lib/gamnit/examples/template_flat/`.
+# at `lib/gamnit/examples/template/`.
module flat
import glesv2
import performance_analysis
import gamnit
-import gamnit::cameras_cache
+intrude import gamnit::cameras
+intrude import gamnit::cameras_cache
import gamnit::dynamic_resolution
import gamnit::limit_fps
import gamnit::camera_control
-# Draw a `texture` at `center`
+# Visible 2D entity in the game world or UI
#
-# An instance of `Sprite` can only belong to a single `SpriteSet` at
-# a time. The on screen position depends on the camera associated
+# Similar to `gamnit::Actor` which is in 3D.
+#
+# Each sprite associates a `texture` to the position `center`.
+# The appearance is modified by `rotation`, `invert_x`,
+# `scale`, `red`, `green`, `blue` and `alpha`.
+# These values can be changed at any time and will trigger an update
+# of the data on the GPU side, having a small performance cost.
+#
+# For a sprite to be visible, it must be added to either the world `sprites`
+# or the `ui_sprites`.
+# However, an instance of `Sprite` can only belong to a single `SpriteSet`
+# at a time. The final on-screen position depends on the camera associated
# to the `SpriteSet`.
+#
+# ~~~
+# # Load texture and create sprite
+# var texture = new Texture("path/in/assets.png")
+# var sprite = new Sprite(texture, new Point3d[Float](0.0, 0.0, 0.0))
+#
+# # Add sprite to the visible game world
+# app.sprites.add sprite
+#
+# # Extra configuration of the sprite
+# sprite.rotation = pi/2.0
+# sprite.scale = 2.0
+#
+# # Show only the blue colors
+# sprite.red = 0.0
+# sprite.green = 0.0
+# ~~~
+#
+# To add a sprite to the UI it can be anchored to screen borders
+# with `ui_camera.top_left` and the likes.
+#
+# ~~~nitish
+# # Place it a bit off the top left of the screen
+# var pos = app.ui_camera.top_left.offset(128.0, -128.0, 0)
+#
+# # Load texture and create sprite
+# var texture = new Texture("path/in/assets.png")
+# var sprite = new Sprite(texture, pos)
+#
+# # Add it to the UI (above world sprites)
+# app.ui_sprites.add sprite
+# ~~~
class Sprite
# Texture drawn to screen
center_direct = value
end
- # Rotation on the Z axis, positive values go counterclockwise
+ # Rotation on the Z axis, positive values turn counterclockwise
var rotation = 0.0 is writable(rotation_direct=)
- # Rotation on the Z axis, positive values go counterclockwise
+ # Rotation on the Z axis, positive values turn counterclockwise
fun rotation=(value: Float)
do
if isset _rotation and value != rotation then needs_update
# Scale applied to this sprite
#
- # The default size of `self` depends on the size in pixels of `texture`.
+ # The basic size of `self` depends on the size in pixels of `texture`.
var scale = 1.0 is writable(scale_direct=)
- # Scale applied to this sprite, see `scale`
+ # Scale applied to this sprite
+ #
+ # The basic size of `self` depends on the size in pixels of `texture`.
fun scale=(value: Float)
do
if isset _scale and value != scale then needs_update
# Current context to which `self` was sorted
private var context: nullable SpriteContext = null
+ # Index in `context`
+ private var context_index: Int = -1
+
# Current context to which `self` belongs
private var sprite_set: nullable SpriteSet = null
end
# Camera for `ui_sprites` using an orthogonal view
var ui_camera = new UICamera(app.display.as(not null)) is lazy
- # World sprites to draw as seen by `world_camera`
+ # World sprites drawn as seen by `world_camera`
var sprites: Set[Sprite] = new SpriteSet
- # UI sprites to draw as seen by `ui_camera`, drawn over world `sprites`
+ # UI sprites drawn as seen by `ui_camera`, over world `sprites`
var ui_sprites: Set[Sprite] = new SpriteSet
# Main method to refine in clients to update game logic and `sprites`
do
texture.load
- ui_camera.reset_height 1080.0
-
- var splash = new Sprite(texture, ui_camera.center)
+ var splash = new Sprite(texture, ui_camera.center.offset(0.0, 0.0, 0.0))
ui_sprites.add splash
var display = display
if display != null then display.close
end
- redef fun frame_core(display)
+ redef fun on_resize(display)
do
- # Prepare to draw, clear buffers
- glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT)
+ super
+
+ world_camera.mvp_matrix_cache = null
+ ui_camera.mvp_matrix_cache = null
+ # Update all sprites in the UI
+ for sprite in ui_sprites do sprite.needs_update
+ end
+
+ redef fun frame_core(display)
+ do
# Check errors
var gl_error = glGetError
assert gl_error == gl_NO_ERROR else print_error gl_error
end
redef class Point3d[N]
- # Get a new `Point3d[Float]` with an offset of each axis of `x, y, z`
- fun offset(x, y, z: Numeric): Point3d[Float]
- do
- return new Point3d[Float](self.x.to_f+x.to_f, self.y.to_f+y.to_f, self.z.to_f+z.to_f)
- end
-
# ---
# Associate each point to its sprites
end
end
+redef class OffsetPoint3d
+ redef fun x=(v)
+ do
+ if isset _x and v != x then needs_update
+ super
+ end
+
+ redef fun y=(v)
+ do
+ if isset _y and v != y then needs_update
+ super
+ end
+
+ redef fun z=(v)
+ do
+ if isset _z and v != z then needs_update
+ super
+ end
+end
+
# Set of sprites sorting them into different `SpriteContext`
private class SpriteSet
super HashSet[Sprite]
# Map texture then static vs dynamic to a `SpriteContext`
- var contexts_map = new HashMap2[GamnitRootTexture, Bool, SpriteContext]
+ var contexts_map = new HashMap2[RootTexture, Bool, SpriteContext]
# Contexts in `contexts_map`
var contexts_items = new Array[SpriteContext]
# Context config and state
# Only root texture drawn by this context
- var texture: nullable GamnitRootTexture
+ var texture: nullable RootTexture
# OpenGL ES usage of `buffer_array` and `buffer_element`
var usage: GLBufferUsage
# Sprites drawn by this context
- var sprites = new GroupedArray[Sprite]
+ var sprites = new GroupedSprites
# Sprites to update since last `draw`
var sprites_to_update = new Set[Sprite]
# Update GPU data of `sprite`
fun update_sprite(sprite: Sprite)
do
- var sprite_index = sprites.index_of(sprite)
- if sprite_index == -1 then return
+ var context = sprite.context
+ if context != self then return
+
+ var sprite_index = sprite.context_index
+ assert sprite_index != -1
# Vertices data
# The data can be compressed by a call to `defragment`.
#
# ~~~
+# intrude import gamnit::flat
+#
# var array = new GroupedArray[String]
# assert array.to_s == ""
#
#
# array.add "E"
# assert array.to_s == "[A,B,c,D,E]"
-# assert array.capacity == 5
+# assert array.capacity == 6
# assert array.length == 5
#
# array.remove "A"
# Number of item slots in the array
fun capacity: Int do return items.length
- # Index of `item`
- fun index_of(item: E): Int do return items.index_of(item)
-
# List of available slots
var available = new MinHeap[Int].default
# Index of the spots after filled chunks
var ends = new List[Int]
- # Add `item` to the first available slot
- fun add(item: E)
+ # Add `item` to the first available slot and return its index
+ fun add(item: E): Int
do
length += 1
# at end of first chunk
ends.first += 1
end
- return
+ return i
end
items.add item
starts.add 0
ends.add 1
else ends.last += 1
+ return ends.last - 1
end
# Remove the first instance of `item`
fun remove(item: E)
do
- var i = items.index_of(item)
- assert i != -1
+ var index = items.index_of(item)
+ remove_at(item, index)
+ end
+
+ # Remove `item` at `index`
+ fun remove_at(item: E, index: Int)
+ do
+ var i = index
length -= 1
items[i] = null
# Returns the elements that moved as a list.
#
# ~~~
+ # intrude import gamnit::flat
+ #
# var array = new GroupedArray[String]
# array.add "a"
# array.add "b"
while max > 0 and (starts.length > 1 or starts.first != 0) do
var i = ends.last - 1
var e = items[i]
+ assert e != null
remove e
add e
moved.add e
end
end
+# Optimized `GroupedArray` to use `Sprite::context_index` and avoid using `index_of`
+private class GroupedSprites
+ super GroupedArray[Sprite]
+
+ redef fun add(item)
+ do
+ var index = super
+ item.context_index = index
+ return index
+ end
+
+ redef fun remove(item) do remove_at(item, item.context_index)
+end
+
redef class GLfloatArray
private fun fill_from_matrix(matrix: Matrix, dst_offset: nullable Int)
do