This PR groups features required by my recent unpublished tech demos built on gamnit.
Here are more details on the less intuitive commits:
* The change to `current_fps` lets a program use it right from the beginning and gives a value which is somewhat realistic (vs returning 0).
* The `placeholder_model` can be useful when quickly writing the prototype of a game to get something visible in the 3D space.
* The doc of `subtexture` clarifies an ambiguity as to whether the method expected pixel offsets or proportional values out of 1.0.
* The tool `texture_atlas_parser` can be useful to other games.
* `draw_mode` is very useful to create optimized `Mesh`, using `gl_TRIANGLE_STRIP` and `gl_TRIANGLE_FAN`.
* The old API of `triangulate` was counter-intuitive. The result was put in a parameter and the list of points was cleared in the process. This PR offers a simple method and the recursive implementation as an optimized alternative. ping @BlackMinou
* `tinks_vr` is not playable but it shows how to convert a gamnit game to a basic VR version.
Pull-Request: #2115
Reviewed-by: Jean Privat <jean@pryen.org>
bin/asteronits: $(shell ${NITLS} -M src/asteronits.nit linux) ${NITC} pre-build
${NITC} src/asteronits.nit -m linux -o $@
-bin/texture_atlas_parser: src/texture_atlas_parser.nit
- ${NITC} src/texture_atlas_parser.nit -o $@
+bin/texture_atlas_parser: ../../lib/gamnit/texture_atlas_parser.nit
+ ${NITC} ../../lib/gamnit/texture_atlas_parser.nit -o $@
src/controls.nit: art/controls.svg
make -C ../inkscape_tools/
../inkscape_tools/bin/svg_to_png_and_nit art/controls.svg -a assets/ -s src/ -x 2.0 -g
-src/spritesheet_city.nit: bin/texture_atlas_parser
+src/spritesheet.nit: bin/texture_atlas_parser
bin/texture_atlas_parser art/sheet.xml --dir src/ -n spritesheet
-pre-build: src/controls.nit src/spritesheet_city.nit
+pre-build: src/controls.nit src/spritesheet.nit
check: bin/asteronits
NIT_TESTING=true bin/asteronits
--- /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.
+
+# VR mode for Android with Google Cardboard
+#
+# This version is not playable and very laggy as it is not modified
+# or optimized in any way for VR.
+# This module is made available as a minimal example of a VR game.
+module tinks_vr
+
+import gamnit::vr
# Coordinates on the texture per vertex
var texture_coords = new Array[Float] is lazy, writable
+ # `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
# Execute draw
if mesh.indices.is_empty then
- glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+ glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
else
- glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+ glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
end
end
end
var xd = sample_used_texture.offset_right - xa
var ya = sample_used_texture.offset_top
var yd = sample_used_texture.offset_bottom - ya
+ xd *= 0.999
+ yd *= 0.999
var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
for i in [0..mesh.texture_coords.length/2[ do
tex_coords[i*2] = xa + xd * mesh.texture_coords[i*2]
- tex_coords[i*2+1] = ya + yd * mesh.texture_coords[i*2+1]
+ tex_coords[i*2+1] = 1.0 - (ya + yd * mesh.texture_coords[i*2+1])
end
program.tex_coord.array(tex_coords, 2)
program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
if mesh.indices.is_empty then
- glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+ glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
else
- glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+ glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
end
end
end
program.normal.array(mesh.normals, 3)
if mesh.indices.is_empty then
- glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+ glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
else
- glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+ glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
end
end
end
var d = [1.0, 0.0]
var texture_coords = new Array[Float]
- for v in [c, d, a, a, d, b] do for i in 6.times do texture_coords.add_all v
+ var face = [a, c, d, a, d, b]
+ for i in 6.times do for v in face do texture_coords.add_all v
return texture_coords
end
if leaves.is_empty then
# Nothing was loaded, use a cube with the default material
- var leaf = new LeafModel(new Cube, new SmoothMaterial.default)
+ var leaf = placeholder_model
leaves.add leaf
end
end
# Load textures need for these materials
for name in texture_names do
if not asset_textures_by_name.keys.has(name) then
- var tex = new GamnitAssetTexture(name)
+ var tex = new TextureAsset(name)
asset_textures_by_name[name] = tex
end
end
redef class Sys
# Textures loaded from .mtl files for models
- var asset_textures_by_name = new Map[String, GamnitAssetTexture]
+ var asset_textures_by_name = new Map[String, TextureAsset]
# All instantiated asset models
var models = new Set[ModelAsset]
+
+ # Blue cube of 1 unit on each side, acting as placeholder for models failing to load
+ #
+ # This model can be freely used by any `Actor` as placeholder or for debugging.
+ var placeholder_model = new LeafModel(new Cube, new SmoothMaterial.default) is lazy
end
# Only affects the desktop implementations.
var show_cursor: Bool = true is writable
+ # Number of bits used for the red value in the color buffer
+ fun red_bits: Int do return 8
+
+ # Number of bits used for the green value in the color buffer
+ fun green_bits: Int do return 8
+
+ # Number of bits used for the blue value in the color buffer
+ fun blue_bits: Int do return 8
+
# Prepare this display
#
# The implementation varies per platform.
setup_egl_display native_display
# We need 8 bits per color for selection by color
- select_egl_config(8, 8, 8, 0, 8, 0, 0)
+ select_egl_config(red_bits, green_bits, blue_bits, 0, 8, 0, 0)
var format = egl_config.attribs(egl_display).native_visual_id
native_window.set_buffers_geometry(0, 0, format)
redef fun close do close_egl
end
-redef class GamnitAssetTexture
+redef class TextureAsset
redef fun load_from_platform
do
setup_egl_display x11_display
if debug_gamnit then print "Setting up EGL context"
- select_egl_config(8, 8, 8, 8, 8, 0, 0)
+ select_egl_config(red_bits, green_bits, blue_bits, 8, 8, 0, 0)
setup_egl_context window_handle
end
end
end
-redef class GamnitAssetTexture
+redef class TextureAsset
redef fun load_from_platform
do
end
# Select an EGL config
- protected fun select_egl_config(blue, green, red, alpha, depth, stencil, sample: Int)
+ protected fun select_egl_config(red, green, blue, alpha, depth, stencil, sample: Int)
do
var config_chooser = new EGLConfigChooser
config_chooser.renderable_type_egl
config_chooser.surface_type_egl
- config_chooser.blue_size = blue
- config_chooser.green_size = green
config_chooser.red_size = red
+ config_chooser.green_size = green
+ config_chooser.blue_size = blue
if alpha > 0 then config_chooser.alpha_size = alpha
if depth > 0 then config_chooser.depth_size = depth
if stencil > 0 then config_chooser.stencil_size = stencil
# Current frame-rate
#
- # Updated each 5 seconds.
- var current_fps = 0.0
+ # Updated each 5 seconds, initialized at the value of `maximum_fps`.
+ var current_fps: Float = maximum_fps is lazy
redef fun frame_full
do
private var frame_count = 0
# Deadline used to compute `current_fps`
- private var frame_count_deadline = 0
+ private var frame_count_deadline = 5
# Check and sleep to maintain a frame-rate bellow `maximum_fps`
#
end
end
+ # Diagnose possible problems with the shaders of the program
+ #
+ # Lists to the console inactive uniforms and attributes.
+ # These may not be problematic but they can help to debug the program.
+ fun diagnose
+ do
+ if gl_program == null then compile_and_link
+
+ print "# Diagnose {class_name}"
+ for k,v in uniforms do
+ if not v.is_active then print "* Uniform {v.name} is inactive"
+ end
+ for k,v in attributes do
+ if not v.is_active then print "* Attribute {v.name} is inactive"
+ end
+ end
+
# Attributes of this program organized by name
#
# Active attributes are gathered at `compile_and_link`.
# Insert the first attribute, to load the root texture
var png_file = "images" / xml_file.basename("xml") + "png"
attributes.add """
- var root_texture = new Texture("{{{png_file}}}")"""
+ var root_texture = new TextureAsset("{{{png_file}}}")"""
# Read XML file
var content = xml_file.to_path.read_all
abstract class Texture
# Prepare a texture located at `path` within the `assets` folder
- new (path: Text) do return new GamnitAssetTexture(path.to_s)
+ new (path: Text) do return new TextureAsset(path.to_s)
# Root texture of which `self` is derived
fun root: GamnitRootTexture is abstract
# OpenGL handle to this texture
fun gl_texture: Int do return root.gl_texture
- # Prepare a subtexture from this texture
+ # Prepare a subtexture from this texture, from the given pixel offsets
fun subtexture(left, top, width, height: Numeric): GamnitSubtexture
do
# Setup the subtexture
end
# Texture loaded from the assets folder
-class GamnitAssetTexture
+class TextureAsset
super GamnitRootTexture
# Path to this texture within the `assets` folder
end
# Split a polygon into triangles
-# Useful for converting a concave polygon into multiple convex ones
-fun triangulate(pts: Array[Point[Float]], results: Array[ConvexPolygon]) do
- var poly = new Polygon(pts)
- pts = poly.points
- recursive_triangulate(pts, results)
+#
+# Useful for converting a concave polygon into multiple convex ones.
+#
+# See: the alternative `triangulate_recursive` uses arrays in-place.
+fun triangulate(points: Array[Point[Float]]): Array[ConvexPolygon]
+do
+ var results = new Array[ConvexPolygon]
+ triangulate_recursive(points.clone, results)
+ return results
end
-private fun recursive_triangulate(pts: Array[Point[Float]], results: Array[ConvexPolygon]) do
- if pts.length == 3 then
- results.add(new ConvexPolygon(pts))
+# Split a polygon into triangles using arrays in-place
+#
+# Consumes the content of `points` and add the triangles to `results`.
+#
+# See: the alternative `triangulate` which does not modify `points` and returns a new array.
+fun triangulate_recursive(points: Array[Point[Float]], results: Array[ConvexPolygon]) do
+ if points.length == 3 then
+ results.add(new ConvexPolygon(points))
return
end
- var prev = pts[pts.length - 1]
- var curr = pts[0]
- var next = pts[1]
- for i in [1..pts.length[ do
+ var prev = points[points.length - 1]
+ var curr = points[0]
+ var next = points[1]
+ for i in [1..points.length[ do
if turn_left(prev, curr, next) then
- prev = pts[i-1]
+ prev = points[i-1]
curr = next
- if i+1 == pts.length then next = pts[pts.length - 1] else next = pts[i+1]
+ if i+1 == points.length then next = points[points.length - 1] else next = points[i+1]
continue
end
var contains = false
var triangle = new ConvexPolygon(new Array[Point[Float]].with_items(prev, curr, next))
- for p in pts do
+ for p in points do
if p != prev and p != curr and p != next then
if triangle.contain(p) then
contains = true
end
if not contains then
results.add(triangle)
- pts.remove(curr)
- recursive_triangulate(pts, results)
+ points.remove(curr)
+ triangulate_recursive(points, results)
break
end
- prev = pts[i-1]
+ prev = points[i-1]
curr = next
- if i+1 == pts.length then next = pts[pts.length - 1] else next = pts[i+1]
+ if i+1 == points.length then next = points[points.length - 1] else next = points[i+1]
end
end