Use hand motion to influence particle motion
[lovr-particles] / main.lua
1 local vec3 = lovr.math.vec3
2 local mat4 = lovr.math.mat4
3 local quat = lovr.math.quat
4
5 local nParticles = 2 ^ 14
6 local batchSize = 128
7 local batches = nParticles / batchSize
8 local dt = 0.01
9
10 local features
11 local shaders = {}
12 local blocks = {}
13 local meshes = {}
14 local compute = {}
15 local hands = {}
16
17 function lovr.load()
18 features = lovr.graphics.getFeatures()
19 shaders.particles = lovr.graphics.newShader("shaders/particles.vert", "shaders/particles.frag")
20
21 blocks.particles = lovr.graphics.newShaderBlock("compute", {
22 -- Reading a value seems to work around whatever the rendering glitch
23 -- was. So we include this flag so we only have to ask for a single
24 -- value, instead of having to copy a large array. Still, annoying :(
25 -- For reference, see issue comment at:
26 -- https://github.com/bjornbytes/lovr/issues/211#issuecomment-580985010
27 reloadFlag = "float",
28 particles = {"mat4", nParticles},
29 }, { readable = true })
30 shaders.particles:sendBlock('particleData', blocks.particles)
31 shaders.particles:send("batchSize", batchSize)
32
33 meshes.particle = makeParticleMesh(batchSize)
34
35 compute.particles = lovr.graphics.newComputeShader("shaders/particles.comp")
36 compute.particles:sendBlock('particleData', blocks.particles)
37 compute.particles:send("dt", dt)
38
39 local xr, yr, zr = range(-1, 1), range(1, 3), range(-1, -0.5)
40 local sr, vr = range(0.005, 0.01), range(-0.05, 0.05)
41 local initial = initialParticleData(nParticles, xr, yr, zr, sr, vr)
42 blocks.particles:send("particles", initial)
43
44 for _, hand in ipairs(lovr.headset.getHands()) do
45 local x, y, z = lovr.headset.getPose(hand)
46 hands[hand] = {
47 pos = lovr.math.newVec3(x, y, z),
48 vel = lovr.math.newVec3(0, 0, 0),
49 rot = lovr.math.newQuat(),
50 }
51 end
52 end
53
54 function lovr.draw()
55 lovr.graphics.clear()
56 local x, y, z = lovr.headset.getPose("head")
57 shaders.particles:send("headPos", vec3(x, y, z))
58 local positions = {}
59 local velocities = {}
60 for _, hand in ipairs(lovr.headset.getHands()) do
61 local x, y, z, a, ax, ay, az = lovr.headset.getPose(hand)
62 local pos = vec3(x, y, z)
63 hands[hand].vel:set((pos - hands[hand].pos) / dt)
64 hands[hand].pos:set(pos)
65 hands[hand].rot:set(a, ax, ay, az)
66 table.insert(positions, pos)
67 table.insert(velocities, hands[hand].vel)
68 end
69 compute.particles:send("handPos", positions)
70 compute.particles:send("handVel", velocities)
71
72 lovr.graphics.compute(compute.particles, batches)
73 -- Read the buffer to force SSBO synch of some sort.
74 blocks.particles:read("reloadFlag")
75
76 lovr.graphics.setColor(0.6, 0.1, 0)
77 lovr.graphics.setShader(nil)
78 for _, hand in pairs(hands) do
79 -- local x, y, z = hand.pos:unpack()
80 lovr.graphics.cube("fill", hand.pos, 0.1, hand.rot)
81 end
82
83 withDepthTest("lequal", false, function()
84 lovr.graphics.setColor(1, 1, 1)
85 lovr.graphics.setShader(shaders.particles)
86 meshes.particle:draw(mat4(), batches)
87 end)
88 end
89
90 function makeParticleMesh(size)
91 local format = { {'vertPosition', 'float', 2}, }
92 local verts = {}
93 local vertMap = {}
94 local insert = table.insert
95 for chunk = 1, size do
96 insert(verts, { 1, 1})
97 insert(verts, { 1, -1})
98 insert(verts, {-1, -1})
99 insert(verts, {-1, 1})
100 local base = 4 * (chunk - 1)
101 insert(vertMap, 3 + base)
102 insert(vertMap, 2 + base)
103 insert(vertMap, 1 + base)
104 insert(vertMap, 4 + base)
105 insert(vertMap, 3 + base)
106 insert(vertMap, 1 + base)
107 end
108 local mesh = lovr.graphics.newMesh(format, verts, "triangles", "static")
109 mesh:setVertexMap(vertMap)
110 return mesh
111 end
112
113 function range(min, max)
114 local scale = max - min
115 return function()
116 return min + lovr.math.random() * scale
117 end
118 end
119
120 function initialParticleData(size, xRange, yRange, zRange, sRange, vRange)
121 local particles = {}
122 local insert = table.insert
123 for i = 1, size do
124 insert(particles, {
125 xRange(), yRange(), zRange(), sRange(),
126 vRange(), vRange(), vRange(), 1,
127 0, 0, 0, 0,
128 0, 0, 0, 0,
129 })
130 end
131 return particles
132 end
133
134 function withDepthTest(compareMode, write, callback)
135 local oldComp, oldWrite = lovr.graphics.getDepthTest()
136 lovr.graphics.setDepthTest(compareMode, write)
137 callback()
138 lovr.graphics.setDepthTest(oldComp, oldWrite)
139 end