

Knock down a pyramid of blocks
Click or tap the scene to throw a glowing ball. Every block is a physics body, so the pyramid tumbles and scatters, casting shadows as it falls. Rebuild stacks it again.
Run it in your browser: Knock down a pyramid of blocks (opens datumhue.app)
local color = datumhue.color
local vec3 = datumhue.math.vec3
local input = datumhue.input
local scene = datumhue.scene.new({ width = 1280, height = 720, hdr = true })
local view = scene:mount({ width = "100%", height = "100%", auto_size = true })
scene.background = color("#2d353b")
scene.bloom = { intensity = 0.3 }
scene.tonemapping = "khronos_pbr_neutral"
scene:directional_light({ direction = vec3(0.7, -1, -0.6), intensity = 8000, shadows = true })
-- The camera looks down at the pyramid. On a pane narrower than 16:9 it
-- backs away along the same line, so the pyramid and the stage fit across.
local eye, target = vec3(5, 8, 13), vec3(0, 2.4, 0)
view:on_resize(function(size)
if size then
local back = math.max(1, 16 * size.y / (9 * size.x))
scene.camera.pos = eye + (eye - target) * (back - 1)
scene.camera:look_at(target)
end
end)
local stage = scene:cylinder({
pos = vec3(0, -0.5, 0),
scale = vec3(16, 1, 16),
color = color("#475258"),
})
stage:add_body({ type = "static", shape = "cylinder" })
-- One paint per row, from the bottom. The top block glows, and so do the balls.
local glow = datumhue.material.standard({ color = color("#dbbc7f"), emissive = color(8, 5, 1.5) })
local paints = {}
for i, hex in ipairs({ "#e67e80", "#e69875", "#dbbc7f", "#a7c080", "#83c092", "#7fbbb3" }) do
paints[i] = datumhue.material.standard({ color = color(hex) })
end
paints[7] = glow
local blocks, balls = {}, {}
local function build()
for _, node in ipairs(blocks) do
node:remove()
end
for _, node in ipairs(balls) do
node:remove()
end
blocks, balls = {}, {}
for row = 0, 6 do
for i = 0, 6 - row do
local block = scene:cube({
pos = vec3((i - (6 - row) / 2) * 1.05, 0.5 + row, 0),
material = paints[row + 1],
})
block:add_body()
blocks[#blocks + 1] = block
end
end
end
-- Throw a ball from the camera through a pixel of the scene.
local function throw(at)
local ray = at and scene:screen_to_ray(at.x, at.y)
if not ray then
return
end
local ball =
scene:sphere({ pos = ray:get_point(2), scale = vec3(0.8, 0.8, 0.8), material = glow })
ball:add_body({ shape = "sphere", mass = 2, ccd = true })
ball.velocity = ray.direction * 40
balls[#balls + 1] = ball
if #balls > 8 then
table.remove(balls, 1):remove()
end
end
-- A press on the scene throws toward the touch or the mouse that made it.
-- The scene is sized to the view, so a point in the view is a pixel of it.
view:on_press(function()
if input.last_source == "touch" then
local touches = input.touches()
local touch = touches[#touches]
throw(touch and input.app_touch_position(touch.id) - view.bounds.min)
else
throw(view:mouse_position())
end
end)
local button = datumhue.ui.root:button({ text = "Rebuild", top = 16, right = 16, on_click = build })
button:update({ font_size = 32 })
build()










