-- Particles, 3D: a torch flame on a post, a fountain, and dust drifting in a sunbeam over the disc, -- with a crate to show the billboards depth-testing against geometry and casting no shadow. The -- smoke is lit by the scene's lights and fades softly where it meets the post; sparks bounce on -- the floor and leave trails; the fountain's drops fade into the pool rather than cutting into it. local font = fontLoad("Singe/FreeSansBold.ttf", 28) local frames = 0 fontSelect(font) discPlay() sceneEnable(true) sceneSetBackground(0, 0, 0, 0) sceneSetAmbient(50, 50, 60) local stone = materialNew() materialSetColor(stone, 110, 110, 120) materialSetRoughness(stone, 0.9) local wood = materialNew() materialSetColor(wood, 140, 90, 50) local floor = nodeNew() nodeSetMesh(floor, meshBox(12, 0.2, 8), stone) nodeSetPosition(floor, 0, -1.1, 0) local post = nodeNew() nodeSetMesh(post, meshCylinder(0.08, 2.2, 12), wood) nodeSetPosition(post, -2.5, 0, 0) local crate = nodeNew() nodeSetMesh(crate, meshBox(1, 1, 1), wood) nodeSetPosition(crate, 0.8, -0.5, 0.5) -- The flame: additive, born in a small ball at the top of the post, rising and shrinking. local torchTip = nodeNew() nodeSetParent(torchTip, post) nodeSetPosition(torchTip, 0, 1.15, 0) local flame = emitterNew(torchTip) emitterSetBlend(flame, PARTICLE_ADD) emitterSetRate(flame, 140) emitterSetLife(flame, 0.4, 0.9) emitterSetSpeed(flame, 0.6, 1.4) emitterSetDirection(flame, 0, 1, 0) emitterSetSpread(flame, 15) emitterSetSize(flame, 0.35, 0.05, 0.3) emitterSetColor(flame, 255, 200, 90, 255, 255, 40, 0, 0) emitterSetRadius(flame, 0.06) emitterSetSpin(flame, -90, 90) emitterStart(flame) -- Smoke above the flame, alpha blended and slow. local smoke = emitterNew(torchTip) emitterSetRate(smoke, 25) emitterSetLife(smoke, 1.5, 2.5) emitterSetSpeed(smoke, 0.5, 0.9) emitterSetDirection(smoke, 0.2, 1, 0) emitterSetSpread(smoke, 20) emitterSetSize(smoke, 0.2, 0.9, 0.2) emitterSetColor(smoke, 160, 160, 170, 120, 120, 120, 130, 0) emitterSetSpin(smoke, -30, 30) emitterSetLit(smoke, true) emitterSetSoftness(smoke, 0.3) emitterStart(smoke) -- Sparks: thrown from the torch, falling, bouncing on the floor, each trailing a streak. local sparks = emitterNew(torchTip) emitterSetBlend(sparks, PARTICLE_ADD) emitterSetRate(sparks, 40) emitterSetLife(sparks, 1.2, 2.0) emitterSetSpeed(sparks, 1.5, 3.0) emitterSetDirection(sparks, 0, 1, 0) emitterSetSpread(sparks, 50) emitterSetGravity(sparks, 0, -6, 0) emitterSetSize(sparks, 0.05, 0.02) emitterSetColor(sparks, 255, 220, 120, 255, 255, 80, 20, 0) emitterSetCollide(sparks, COLLIDE_FLOOR, 0.5, 0.3, -1) emitterSetTrail(sparks, 8, 0.03) emitterStart(sparks) -- A fountain: water drops thrown up and falling under gravity, staying where they were born. local spout = nodeNew() nodeSetPosition(spout, 2.5, -1, -0.5) local fountain = emitterNew(spout) emitterSetRate(fountain, 300) emitterSetLife(fountain, 1.0, 1.6) emitterSetSpeed(fountain, 4.0, 5.0) emitterSetDirection(fountain, 0, 1, 0) emitterSetSpread(fountain, 8) emitterSetGravity(fountain, 0, -9.8, 0) emitterSetSize(fountain, 0.08, 0.12) emitterSetColor(fountain, 150, 200, 255, 220, 150, 200, 255, 0) emitterSetMax(fountain, 1500) emitterSetSoftness(fountain, 0.15) emitterStart(fountain) -- Dust: a few slow motes in a box of air, following nothing. local air = nodeNew() nodeSetPosition(air, 0, 0.5, 0) local dust = emitterNew(air) emitterSetRate(dust, 20) emitterSetLife(dust, 4, 6) emitterSetSpeed(dust, 0.05, 0.2) emitterSetSpread(dust, 180) emitterSetSize(dust, 0.04, 0.04) emitterSetColor(dust, 255, 240, 200, 0, 255, 240, 200, 160) emitterSetRadius(dust, 2.5) emitterSetMax(dust, 200) emitterStart(dust) emitterBurst(dust, 120) local sun = lightNew(LIGHT_DIRECTIONAL) nodeSetPosition(sun, -3, 6, 4) nodeLookAt(sun, 0, 0, 0) lightSetIntensity(sun, 1.4) lightSetShadow(sun, true) local camera = nodeNew() nodeSetPosition(camera, 0, 1.2, 7) nodeLookAt(camera, 0, 0, 0) cameraSet(camera) function onOverlayUpdate() frames = frames + 1 nodeRotate(post, 0, 0.5, 0) overlayClear() fontPrint(20, 20, "Particles 3D, frame " .. frames .. " flame " .. emitterGetCount(flame) .. " fountain " .. emitterGetCount(fountain)) if frames == 60 or frames == 120 or frames == 170 then singeScreenshot() end if frames == 180 then singeQuit() end end