Wrecking Ball

SourceEdit
--[[ Wrecking ball suspended from rope. Cyrus-free.

Making realtime rope simulation is finicky on any physics engine. At certain weight the force becomes
too much to be successfully distributed among rope elements.

Some steps that can help solve the issue:
1) lower the mass of suspended weight or lower the gravity constant
2) increase mass of each rope element, preferably having more mass at top of rope and less mass at bottom
3) decrease number of rope elements
4) decrease the simulation time step
5) modify engine code to use direct solver instead of iterative solver (step() instead of quickStep())
--]]

local world

function lovr.load()
  world = lovr.physics.newWorld(0, -3, 0, false)
  -- ground plane
  local box = world:newBoxCollider(vec3(0, -0.05, 0), vec3(20, 0.1, 20))
  box:setKinematic(true)
  -- hanger
  local hangerPosition = vec3(0, 2, -1)
  local hanger = world:newBoxCollider(hangerPosition, vec3(0.3, 0.1, 0.3))
  hanger:setKinematic(true)
  -- ball
  local ballPosition = vec3(-1, 1, -1)
  local ball = world:newSphereCollider(ballPosition, 0.2)
  -- rope
  local firstEnd, lastEnd = makeRope(
    hangerPosition + vec3(0, -0.1, 0),
    ballPosition   + vec3(0,  0.3, 0),
    0.02, 10)
  lovr.physics.newDistanceJoint(hanger, firstEnd, hangerPosition, vec3(firstEnd:getPosition()))
  lovr.physics.newDistanceJoint(ball, lastEnd, ballPosition, vec3(lastEnd:getPosition()))
  -- brick wall
  local x = 0.3
  local even = true
  for y = 1, 0.1, -0.1 do
    for z = -0.5, -1.5, -0.2 do
      world:newBoxCollider(x, y, even and z or z - 0.1, 0.08, 0.1, 0.2)
    end
    even = not even
  end

  lovr.graphics.setBackgroundColor(0.1, 0.1, 0.1)
end


function lovr.update(dt)
  world:update(dt)
end


function lovr.draw(pass)
  for i, collider in ipairs(world:getColliders()) do
    local shade = (i - 10) / #world:getColliders()
    pass:setColor(shade, shade, shade)
    local shape = collider:getShapes()[1]
    local shapeType = shape:getType()
    local x,y,z, angle, ax,ay,az = collider:getPose()
    if shapeType == 'box' then
      local sx, sy, sz = shape:getDimensions()
      pass:box(x,y,z, sx,sy,sz, angle, ax,ay,az)
    elseif shapeType == 'sphere' then
      pass:setColor(0.4, 0, 0)
      pass:sphere(x,y,z, shape:getRadius())
    end
  end
end


function makeRope(origin, destination, thickness, elements)
  local length = (destination - origin):length()
  thickness = thickness or length / 100
  elements = elements or 30
  elementSize = length / elements
  local orientation = vec3(destination - origin):normalize()
  local first, last, prev
  for i = 1, elements do
    local position = vec3(origin):lerp(destination, (i - 0.5) / elements)
    local anchor   = vec3(origin):lerp(destination, (i - 1.0) / elements)
    element = world:newBoxCollider(position, vec3(thickness, thickness, elementSize * 0.95))
    element:setRestitution(0.1)
    element:setGravityIgnored(true)
    element:setOrientation(quat(orientation))
    element:setLinearDamping(0.01)
    element:setAngularDamping(0.01)
    element:setMass(0.001)
    if prev then
      local joint = lovr.physics.newBallJoint(prev, element, anchor)
      joint:setResponseTime(10)
      joint:setTightness(1)
    else
      first = element
    end
    prev = element
  end
  last = prev
  return first, last
end