This example shows how to use the stingray.ProceduralMesh interface to create meshes on the fly:
To use this code in a project, first save it in a .lua file in your project. Then, in one of your project scripts, require the file and cache its return in a local variable:
local proc_mesh_controller = require 'script/lua/proc_mesh_controller'
You have to call three functions on this variable:
proc_mesh_controller:start(): call this when a level is first loaded. If you're using the Appkit, you can do it in the Project.on_level_load_pre_flow() function.
proc_mesh_controller:update(dt): call this each frame, and pass the delta time since the last frame. If you're using the Appkit, you can do it in the Project.update(dt) function.
proc_mesh_controller:shutdown(): call this when your level is unloaded. If you're using the Appkit, you can do it in the Project.on_level_shutdown_post_flow() function.
require('core/appkit/lua/class') ProcMeshController = Appkit.class(ProcMeshController) local M = ProcMeshController Vector3ArrayClass = Appkit.class(Vector3ArrayClass) local Vector3Array = Vector3ArrayClass function Vector3Array:init() self.x = stingray.Float32Array() self.y = stingray.Float32Array() self.z = stingray.Float32Array() end function Vector3Array.splat(f) local res = Vector3Array() res.x = f res.y = f res.z = f return res end function Vector3Array:push(v) self.x:push(v.x) self.y:push(v.y) self.z:push(v.z) end function Vector3Array.add(a,b) local res = Vector3Array() res.x = stingray.Float32Array.add(a.x, b.x) res.y = stingray.Float32Array.add(a.y, b.y) res.z = stingray.Float32Array.add(a.z, b.z) return res end function Vector3Array.__add(a,b) return Vector3Array.add(a,b) end function Vector3Array.sub(a,b) local res = Vector3Array() res.x = stingray.Float32Array.sub(a.x, b.x) res.y = stingray.Float32Array.sub(a.y, b.y) res.z = stingray.Float32Array.sub(a.z, b.z) return res end function Vector3Array.__sub(a,b) return Vector3Array.sub(a,b) end function Vector3Array.mul(a,b) local res = Vector3Array() res.x = stingray.Float32Array.mul(a.x, b.x) res.y = stingray.Float32Array.mul(a.y, b.y) res.z = stingray.Float32Array.mul(a.z, b.z) return res end function Vector3Array.__mul(a,b) return Vector3Array.mul(a,b) end function Vector3Array.div(a,b) local res = Vector3Array() res.x = stingray.Float32Array.div(a.x, b.x) res.y = stingray.Float32Array.div(a.y, b.y) res.z = stingray.Float32Array.div(a.z, b.z) return res end function Vector3Array.__div(a,b) return Vector3Array.div(a,b) end function Vector3Array.__unm(a) local res = Vector3Array() res.x = -a.x res.y = -a.y res.z = -a.z return res end function Vector3Array.__div(a,b) return Vector3Array.div(a,b) end function Vector3Array.length(a) return stingray.Float32Array.sqrt(a.x*a.x + a.y*a.y + a.z*a.z) end function Vector3Array.cross(a,b) local res = Vector3Array() res.x = (a.y*b.z)-(a.z*b.y) res.y = (a.z*b.x)-(a.x*b.z) res.z = (a.x*b.y)-(a.y*b.x) return res end function Vector3Array.interleave(arrays) local t = {} for _,a in ipairs(arrays) do t[#t+1] = a.x t[#t+1] = a.y t[#t+1] = a.z end return stingray.Float32Array.interleave(t) end local function append_v3(buffer, ...) local nb = #buffer local n = select('#', ...) for i=1,n do local v = select(i, ...) buffer[nb + (i-1)*3 + 1] = v.x buffer[nb + (i-1)*3 + 2] = v.y buffer[nb + (i-1)*3 + 3] = v.z end end local function append_f(buffer, ...) local nb = #buffer local n = select('#', ...) for i=1,n do buffer[nb + i] = select(i, ...) end end local function extruded_triangle(iterations, v1, v2, v3, vbuffer, ibuffer, ignorebase) local n = stingray.Vector3.normalize(stingray.Vector3.cross(v2-v1, v3-v1)) if iterations == 0 then local i = #vbuffer / 6 append_v3(vbuffer, v1, n, v2, n, v3, n) append_f(ibuffer, i, i+1, i+2) else local v4 = (v1 + v2) / 2 local v5 = (v2 + v3) / 2 local v6 = (v3 + v1) / 2 local a = stingray.Vector3.length(v2-v1) / 2 local h = a * math.sqrt(6) / 3 local v7 = (v1 + v2 + v3) / 3 + n * h / 2 if not ignorebase then extruded_triangle(iterations-1, v1, v4, v6, vbuffer, ibuffer) extruded_triangle(iterations-1, v4, v2, v5, vbuffer, ibuffer) extruded_triangle(iterations-1, v6, v5, v3, vbuffer, ibuffer) end extruded_triangle(iterations-1, v7, v6, v4, vbuffer, ibuffer) extruded_triangle(iterations-1, v7, v4, v5, vbuffer, ibuffer) extruded_triangle(iterations-1, v7, v5, v6, vbuffer, ibuffer) end end local function menger_sponge(iterations, vmin, vmax, vbuffer, ibuffer) local bc = stingray.Script.temp_byte_count() if iterations == 0 then local i = #vbuffer / 6 local v1 = vmin local v2 = stingray.Vector3(vmax.x, vmin.y, vmin.z) local v3 = stingray.Vector3(vmin.x, vmax.y, vmin.z) local v4 = stingray.Vector3(vmax.x, vmax.y, vmin.z) local v5 = stingray.Vector3(vmin.x, vmin.y, vmax.z) local v6 = stingray.Vector3(vmax.x, vmin.y, vmax.z) local v7 = stingray.Vector3(vmin.x, vmax.y, vmax.z) local v8 = vmax local x = stingray.Vector3(1,0,0) local y = stingray.Vector3(0,1,0) local z = stingray.Vector3(0,0,1) append_v3(vbuffer, v1, -y, v2, -y, v6, -y, v5, -y, v4, y, v3, y, v7, y, v8, y, v2, x, v4, x, v8, x, v6, x, v3, -x, v1, -x, v5, -x, v7, -x, v3, -z, v4, -z, v2, -z, v1, -z, v5, z, v6, z, v8, z, v7, z ) append_f(ibuffer, i, i+1, i+2, i, i+2, i+3) i = i + 4 append_f(ibuffer, i, i+1, i+2, i, i+2, i+3) i = i + 4 append_f(ibuffer, i, i+1, i+2, i, i+2, i+3) i = i + 4 append_f(ibuffer, i, i+1, i+2, i, i+2, i+3) i = i + 4 append_f(ibuffer, i, i+1, i+2, i, i+2, i+3) i = i + 4 append_f(ibuffer, i, i+1, i+2, i, i+2, i+3) i = i + 4 else for x = 1,3 do for y = 1,3 do for z = 1,3 do if (x ~= 2 and y ~= 2) or (y ~= 2 and z ~= 2) or (x ~= 2 and z ~= 2) then local d = (vmax - vmin)/3 local xyz = stingray.Vector3(x, y, z) menger_sponge(iterations-1, vmin + stingray.Vector3.multiply_elements(xyz - stingray.Vector3(1,1,1), d), vmin + stingray.Vector3.multiply_elements(xyz, d), vbuffer, ibuffer) end end end end end stingray.Script.set_temp_byte_count(bc) end local function make_sphere_points(N) local sphere = {n = Vector3Array()} local a = 4 * math.pi / N local d = math.sqrt(a) local M_theta = math.floor(math.pi / d + 0.5) local d_theta = math.pi / M_theta local d_phi = a / d_theta for m = 0,M_theta-1 do local theta = math.pi * (m + 0.5) / M_theta local M_phi = math.floor(2*math.pi*math.sin(theta)/d_phi + 0.5) for n = 0,M_phi-1 do local phi = 2 * math.pi * n / M_phi local x = math.sin(theta) * math.cos(phi) local y = math.sin(theta) * math.sin(phi) local z = math.cos(theta) local n = stingray.Vector3(x, y, z) sphere.n:push(n) end end sphere.u = Vector3Array.sub( stingray.Vector3(0,0,1), sphere.n * Vector3Array.splat(sphere.n.z) ) sphere.u = sphere.u / Vector3Array.splat(Vector3Array.length(sphere.u)) sphere.r = Vector3Array.cross(sphere.n, sphere.u) return sphere end local function make_patch_sphere(sphere_points, r, d, vbuffer, ibuffer) local pos = sphere_points.n * stingray.Vector3(r,r,r) local x = sphere_points.u local y = sphere_points.r local z = sphere_points.n local rx = stingray.Vector3(d,d,d) local ry = stingray.Vector3(d,d,d) local rz = stingray.Vector3(d,d,d)*0.1 local v1 = pos - x*rx - y*ry - z*rz local v2 = pos + x*rx - y*ry - z*rz local v3 = pos - x*rx + y*ry - z*rz local v4 = pos + x*rx + y*ry - z*rz local v5 = pos - x*rx - y*ry + z*rz local v6 = pos + x*rx - y*ry + z*rz local v7 = pos - x*rx + y*ry + z*rz local v8 = pos + x*rx + y*ry + z*rz local interleaved = Vector3Array.interleave( {v1, -y, v2, -y, v6, -y, v5, -y, v4, y, v3, y, v7, y, v8, y, v2, x, v4, x, v8, x, v6, x, v3, -x, v1, -x, v5, -x, v7, -x, v3, -z, v4, -z, v2, -z, v1, -z, v5, z, v6, z, v8, z, v7, z} ) stingray.Float32Array.swap(vbuffer, interleaved) if ibuffer then local count = #sphere_points.n.x ibuffer:resize(0) for n=0,count-1 do local i = 24*n ibuffer:push(i, i+1, i+2, i, i+2, i+3) i = i + 4 ibuffer:push(i, i+1, i+2, i, i+2, i+3) i = i + 4 ibuffer:push(i, i+1, i+2, i, i+2, i+3) i = i + 4 ibuffer:push(i, i+1, i+2, i, i+2, i+3) i = i + 4 ibuffer:push(i, i+1, i+2, i, i+2, i+3) i = i + 4 ibuffer:push(i, i+1, i+2, i, i+2, i+3) i = i + 4 end end end function M:shutdown() for _,mesh in ipairs(self.meshes) do self:destroy_procmesh(mesh) end end function M:start() self.spinning_units = {} self.meshes = {} local make_menger_sponge = function(pos, iter) local vbuffer = {} local ibuffer = {} local s = 1 menger_sponge(iter, stingray.Vector3(-s,-s,0), stingray.Vector3(s,s,2*s), vbuffer, ibuffer) local unit = stingray.World.spawn_unit(stingray.Application.main_world(), "core/units/camera", pos) local mesh = self:make_procmesh(unit, "rp_root", vbuffer, ibuffer) table.insert(self.spinning_units, unit) table.insert(self.meshes, mesh) end make_menger_sponge(stingray.Vector3(-4,-6,1), 0) make_menger_sponge(stingray.Vector3(-4,-2,1), 1) make_menger_sponge(stingray.Vector3(-4, 2,1), 2) make_menger_sponge(stingray.Vector3(-4, 6,1), 3) self.sphere_points = make_sphere_points(300, self.sphere_points) local vbuffer = stingray.Float32Array() local ibuffer = stingray.Float32Array() make_patch_sphere(self.sphere_points, 3, 0.2, vbuffer, ibuffer) local unit = stingray.World.spawn_unit(stingray.Application.main_world(), "core/units/camera", stingray.Vector3(-10, 0, 5)) self.sphere = self:make_procmesh(unit, "rp_root", vbuffer, ibuffer, {vertex_buffer_validity = stingray.RenderBuffer.RB_VALIDITY_UPDATABLE}) table.insert(self.spinning_units, unit) table.insert(self.meshes, self.sphere) end function M:make_procmesh(unit, node, vertices, indices, options) local t = {} options = options or {} local xyz32 = stingray.RenderBuffer.format(stingray.RenderBuffer.RB_FLOAT_COMPONENT, true, false, 32, 32, 32, 0) local channels = { {format = xyz32, semantic = stingray.RenderBuffer.RB_POSITION_SEMANTIC, vb_index = 0, set = 0, instance = false}, {format = xyz32, semantic = stingray.RenderBuffer.RB_NORMAL_SEMANTIC, vb_index = 0, set = 0, instance = false} } t.vdecl = stingray.RenderBuffer.create_description(stingray.RenderBuffer.RB_VERTEX_DESCRIPTION, channels) local a = 20 local R = a * math.sqrt(3) / 3 local r = R / 2 t.vbuffer = stingray.RenderBuffer.create_buffer(options.vertex_buffer_validity or stingray.RenderBuffer.RB_VALIDITY_STATIC, stingray.RenderBuffer.RB_VERTEX_BUFFER_VIEW, 6*4, vertices) t.ibuffer = stingray.RenderBuffer.create_buffer(stingray.RenderBuffer.RB_VALIDITY_STATIC, stingray.RenderBuffer.RB_INDEX_BUFFER_VIEW, 4, indices) t.mesh = stingray.ProceduralMesh.create(unit, node, "procedural-mesh", stingray.ProceduralMesh.MO_VIEWPORT_VISIBLE_FLAG + stingray.ProceduralMesh.MO_SHADOW_CASTER_FLAG) stingray.ProceduralMesh.set_bounding_box(t.mesh, stingray.Vector3(-100, -100, -100), stingray.Vector3(100, 100, 100)) local batches = {{ primitive_type = stingray.ProceduralMesh.MO_TRIANGLE_LIST, material_index = 0, vertex_offset = 0, primitives = #indices / 3, index_offset = 0, vertices = 0, instances = 1 }} stingray.ProceduralMesh.set_batch_info(t.mesh, batches) stingray.ProceduralMesh.add_resource(t.mesh, t.vdecl) stingray.ProceduralMesh.add_resource(t.mesh, t.vbuffer) stingray.ProceduralMesh.add_resource(t.mesh, t.ibuffer) stingray.ProceduralMesh.set_materials(t.mesh, {"core/stingray_renderer/shader_import/no_uvs"}) return t end function M:destroy_procmesh(t) stingray.ProceduralMesh.destroy(t.mesh) stingray.RenderBuffer.destroy_buffer(t.ibuffer) stingray.RenderBuffer.destroy_buffer(t.vbuffer) stingray.RenderBuffer.destroy_description(t.vdecl) end function M:update(dt) self.t = (self.t or 0) + dt local rot = stingray.Quaternion.axis_angle(stingray.Vector3(0,0,1), self.t/2) for _,unit in ipairs(self.spinning_units) do stingray.Unit.set_local_rotation(unit, 1, rot) end if self.sphere then self.sphere_vbuffer = self.sphere_vbuffer or stingray.Float32Array() self.sphere_vbuffer:resize(0) make_patch_sphere(self.sphere_points, 1.8 + (math.sin(self.t*4)+1)*1.5, 0.2, self.sphere_vbuffer, nil) stingray.RenderBuffer.update_buffer(self.sphere.vbuffer, stingray.RenderBuffer.RB_FLOAT_COMPONENT, self.sphere_vbuffer) end end return M