-- Soft bodies: a flag pinned to a pole, a sheet pinned at four corners catching a crate, a rope -- swinging from a crane, and a balloon under pressure dropped onto the floor. local font = fontLoad("Singe/FreeSansBold.ttf", 28) local frames = 0 fontSelect(font) discPlay() sceneEnable(true) sceneSetBackground(0, 0, 0, 0) sceneSetAmbient(70, 70, 80) local stone = materialNew() materialSetColor(stone, 120, 120, 130) materialSetRoughness(stone, 0.9) local cloth = materialNew() materialSetColor(cloth, 220, 60, 60) materialSetDoubleSided(cloth, true) materialSetRoughness(cloth, 0.8) local linen = materialNew() materialSetColor(linen, 230, 225, 200) materialSetDoubleSided(linen, true) local wood = materialNew() materialSetColor(wood, 150, 100, 50) local hemp = materialNew() materialSetColor(hemp, 190, 160, 100) local rubber = materialNew() materialSetColor(rubber, 80, 180, 90) local floor = nodeNew() nodeSetMesh(floor, meshBox(16, 0.2, 12), stone) nodeSetPosition(floor, 0, -0.1, 0) bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.2, 12) -- The flag: a grid standing upright, pinned along its left edge to the pole. local pole = nodeNew() nodeSetMesh(pole, meshCylinder(0.04, 3, 8), wood) nodeSetPosition(pole, -5, 1.5, 0) bodyNew(pole, BODY_STATIC, SHAPE_CYLINDER, 0.04, 3) local flag = nodeNew() nodeSetMesh(flag, meshPlane(1.6, 1.0, 16, 10), cloth) nodeSetPosition(flag, -4.16, 2.4, 0) nodeSetRotation(flag, 90, 0, 0) softNew(flag, SOFT_CLOTH) softSetStiffness(flag, 0.95, 0.1) softSetMass(flag, 0.5) for _, y in ipairs({ 1.9, 2.15, 2.4, 2.65, 2.9 }) do softPin(flag, -4.96, y, 0) end -- The sheet: pinned at four corners, a crate dropped onto it. local sheet = nodeNew() nodeSetMesh(sheet, meshPlane(3, 3, 20, 20), linen) nodeSetPosition(sheet, 0, 1.6, 0) softNew(sheet, SOFT_CLOTH) softSetStiffness(sheet, 1.0, 0.05) softSetMass(sheet, 4) for _, c in ipairs({ { -1.5, -1.5 }, { 1.5, -1.5 }, { -1.5, 1.5 }, { 1.5, 1.5 } }) do softPin(sheet, c[1], 1.6, c[2]) end local crate = nodeNew() nodeSetMesh(crate, meshBox(0.6, 0.6, 0.6), wood) nodeSetPosition(crate, 0.2, 3.5, 0.1) nodeSetRotation(crate, 20, 30, 0) bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.6, 0.6, 0.6) bodySetMass(crate, 3) -- The rope: from a crane arm out and down, swinging free from a horizontal start. local crane = nodeNew() nodeSetPosition(crane, 4, 4, -1) local rope = nodeNew() nodeSetPosition(rope, 4, 4, -1) softNew(rope, SOFT_ROPE, 6.5, 4, -1, 16, 0.04) nodeSetMaterial(rope, hemp) softSetMass(rope, 1.5) softPin(rope, 4, 4, -1, crane) -- The balloon: a sphere kept round by pressure, dropped and squashed. local balloon = nodeNew() nodeSetMesh(balloon, meshSphere(0.5, 14), rubber) nodeSetPosition(balloon, 4, 2.5, 2) softNew(balloon, SOFT_BODY) softSetStiffness(balloon, 0.8, 0.3) softSetMass(balloon, 0.6) softSetPressure(balloon, 8) local sun = lightNew(LIGHT_DIRECTIONAL) nodeSetPosition(sun, -4, 8, 6) nodeLookAt(sun, 0, 0, 0) lightSetIntensity(sun, 1.5) lightSetShadow(sun, true) local camera = nodeNew() nodeSetPosition(camera, 0, 3.5, 9) nodeLookAt(camera, 0, 1.5, 0) cameraSet(camera) function onOverlayUpdate() frames = frames + 1 overlayClear() fontPrint(20, 20, "Soft bodies, frame " .. frames) if frames % 30 == 0 then local cx, cy = nodeGetPosition(crate) local bx, by = nodeGetPosition(balloon) debugPrint(string.format("frame %d crate y %.2f balloon y %.2f", frames, cy, by)) end if frames == 40 or frames == 100 or frames == 170 then singeScreenshot() end if frames == 180 then singeQuit() end end