Skip to content

Instantly share code, notes, and snippets.

@DolenzSong
Last active September 9, 2016 16:24
Show Gist options
  • Save DolenzSong/f9b0d5e0253cbfee70f58030b327d79a to your computer and use it in GitHub Desktop.
Save DolenzSong/f9b0d5e0253cbfee70f58030b327d79a to your computer and use it in GitHub Desktop.
Fake 3D physics for Codea, based off of Ignatz's version
--# Main
saveImage("Project:Icon", readImage("SpaceCute:Planet"))
if notSet then print "notSet is true" else print "notSet is nil and false" end
existenceIsTruth = "wow"
if existenceIsTruth then print("existenceIsTruth: "..existenceIsTruth) end
displayMode(OVERLAY)
supportedOrientations(LANDSCAPE_ANY)
function setup()
makeCameraControls()
setPhysicsVariables()
boxSize=HEIGHT --size of virtual box
radius = 35
proportion = 0.2604
boxSize3D = boxSize*proportion --size of 3D cube
radius3D = radius*proportion
fake3D = {}
Create3DCube(boxSize3D)
physicsLabSetup()
notStarted=true
end
function makeCameraControls()
parameter.number("cameraX", -200, 200, -15)
parameter.number("cameraY", -200, 200, 90)
parameter.number("cameraZ", -200, 200, 60)
parameter.number("lookX", -200, 200, -15)
parameter.number("lookY", -200, 200, -90)
parameter.number("lookZ", -200, 200, -90)
end
function setPhysicsVariables()
ballBounciness = 1
ballGravityScale = 1
wallBounciness = 1
end
function touched(t)
if t.tapCount==2 and t.state == ENDED then
local newFake = Fake3DSphere(t.x, t.y, radius)
table.insert(fake3D, newFake)
notStarted=false
end
if t.tapCount==5 and t.state == ENDED then
local center1 = vec2(t.x, t.y)
local center2 = vec2(t.x+radius*2, t.y)
local joint1Pos = vec2(t.x+radius, t.y)
local newFake = Fake3DSphere(center1.x, center1.y, radius)
local newFake2 = Fake3DSphere(center2.x, center2.y, radius)
local center1a = newFake.circles[2].position
local joint2Pos = vec2(center1a.x+radius, center1a.y)
local joint1 = physics.joint(WELD, newFake.circles[1],newFake2.circles[1],joint1Pos)
local joint2 = physics.joint(WELD, newFake.circles[2],newFake2.circles[2],joint2Pos)
joint1.frequency = 1
joint2.frequency = 1
debugDraw:addJoint(joint1)
debugDraw:addJoint(joint2)
table.insert(fake3D, newFake)
table.insert(fake3D, newFake2)
end
debugDraw:touched(t)
end
function Create3DCube(size) --size is size of 3D box
local img=readImage("SpaceCute:Background")--:copy(4,4,62,62)
box=CreateBlock(size,size,size,color(255),vec3(0,0,0),img)
end
function draw()
background(220)
perspective(90)
camera(cameraX,cameraY,cameraZ,lookX,lookY,lookZ)
box:draw()
for i, fake in ipairs(fake3D) do
fake:updatePosition()
pushMatrix()
translate(fake.sphere.position:unpack())
fake.sphere.shader.mModel=modelMatrix()
fake.sphere.shader.centre=vec4(fake.sphere.position.x,fake.sphere.position.y,fake.sphere.position.z,1)
fake.sphere:draw()
popMatrix()
end
ortho()
viewMatrix(matrix())
stroke(0)
strokeWidth(1)
physicsLabDraw()
end
--# Shader
S = {
v = [[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
vTexCoord = texCoord;
vPosition = mModel * position;
gl_Position = modelViewProjection * position;
}
]],
f = [[
precision highp float;
uniform lowp sampler2D texture;
uniform vec4 ambientColor;
uniform vec3 directDirection;
uniform vec4 directColor;
uniform vec4 centre;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
vec4 col = texture2D(texture, vTexCoord);
vec4 norm = normalize(vPosition-centre);
float diffuse = max( 0.0, dot( norm.xyz, directDirection )); //calculate strength of reflection
col = col * (ambientColor + diffuse * directColor); //total color
col.a = 1.0;
gl_FragColor=col;
}
]]
}
--# Utility
function CreateBlock(w,h,d,col,pos,tex) --width,height,depth,colour,position,texture
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local n={vec3(0,0,1),vec3(1,0,0),vec3(0,0,-1),vec3(-1,0,0),vec3(1,0,0),vec3(-1,0,0)}
local norm={}
for i=1,6 do for j=1,6 do norm[#norm+1]=n[i] end end
local ms = mesh()
ms.vertices = vert
ms.normals=norm
if tex then ms.texture,ms.texCoords = tex,texCoords end
ms:setColors(col or color(255))
return ms
end
function CreateSphere(r,tex,col,nx,ny)
local vertices,tc = Sphere_OptimMesh(nx or 40,ny or 20)
vertices = Sphere_WarpVertices(vertices)
for i=1,#vertices do vertices[i]=vertices[i]*r end
local ms = mesh()
ms.vertices=vertices
if tex then ms.texture,ms.texCoords=tex,tc end
ms:setColors(col or color(255))
return ms
end
function Sphere_OptimMesh(nx,ny)
local v,t={},{}
local k,s,x,y,x1,x2,i1,i2,sx,sy=0,1,0,0,{},{},0,0,nx/ny,1/ny
local c = vec3(1,0.5,0)
local m1,m2
for y=0,ny-1 do
local nx1 = math.floor( nx * math.abs(math.cos(( y*sy-0.5)*2 * math.pi/2)) )
if nx1<6 then nx1=6 end
local nx2 = math.floor( nx * math.abs(math.cos(((y+1)*sy-0.5)*2 * math.pi/2)) )
if nx2<6 then nx2=6 end
x1,x2 = {},{}
for i1 = 1,nx1 do x1[i1] = (i1-1)/(nx1-1)*sx end x1[nx1+1] = x1[nx1]
for i2 = 1,nx2 do x2[i2] = (i2-1)/(nx2-1)*sx end x2[nx2+1] = x2[nx2]
local i1,i2,n,nMax,continue=1,1,0,0,true
nMax = nx*2+1
while continue do
m1,m2=(x1[i1]+x1[i1+1])/2,(x2[i2]+x2[i2+1])/2
if m1<=m2 then
v[k+1],v[k+2],v[k+3]=vec3(x1[i1],sy*y,1)-c,vec3(x1[i1+1],sy*y,1)-c,vec3(x2[i2],sy*(y+1),1)-c
t[k+1],t[k+2],t[k+3]=vec2(-x1[i1]/2,sy*y) ,vec2(-x1[i1+1]/2,sy*y),vec2(-x2[i2]/2,sy*(y+1))
if i1<nx1 then i1 = i1 +1 end
else
v[k+1],v[k+2],v[k+3]=vec3(x1[i1],sy*y,1)-c,vec3(x2[i2],sy*(y+1),1)-c,vec3(x2[i2+1],sy*(y+1),1)-c
t[k+1],t[k+2],t[k+3]=vec2(-x1[i1]/2,sy*y),vec2(-x2[i2]/2,sy*(y+1)),vec2(-x2[i2+1]/2,sy*(y+1))
if i2<nx2 then i2 = i2 +1 end
end
if i1==nx1 and i2==nx2 then continue=false end
k,n=k+3,n+1
if n>nMax then continue=false end
end
end
return v,t
end
function Sphere_WarpVertices(verts)
local m = matrix(0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0)
local vx,vy,vz,vm
for i,v in ipairs(verts) do
vx,vy = v[1], v[2]
vm = m:rotate(180*vy,1,0,0):rotate(180*vx,0,1,0)
vx,vy,vz = vm[1],vm[5],vm[9]
verts[i] = vec3(vx,vy,vz)
end
return verts
end
--# PhysicsLabMain
--supportedOrientations(LANDSCAPE_ANY)
-- Use this function to perform your initial setup
function physicsLabSetup()
lineCapMode(ROUND)
debugDraw = PhysicsDebugDraw()
--defaultGravity = physics.gravity()
createGround()
end
function createCircle(x,y,r)
local circle = physics.body(CIRCLE, r)
-- enable smooth motion
circle.interpolate = true
circle.x = x
circle.y = y
circle.restitution = 0.25
circle.sleepingAllowed = false
debugDraw:addBody(circle)
return circle
end
--[[
function createFake3DCircles(x,y,r)
local fakers = {}
for i=0,1 do
local circle = physics.body(CIRCLE, r)
local sensor = physics.body(CIRCLE, r)
-- enable smooth motion
circle.interpolate = true
sensor.interpolate = true
circle.x = x +(i*r*2.2)
circle.y = y
circle.restitution = 1
circle.sleepingAllowed = true
sensor.sensor = true
sensor.gravityScale = 0
sensor.sleepingAllowed = true
sensor.position = circle.position
sensor.density = 0
sensor.mass = 0
sensor.mask = {1,i+1}
sensor.categories = {}
sensor.weldedTo = circle
sensor.plane = i
local distJoint = physics.joint(WELD, circle, sensor, circle.position, sensor.position, 10)
debugDraw:addJoint(distJoint)
circle.mask = {1}
circle.categories = {i+2}
table.insert(fakers, sensor)
end
fakers[1].linkedTo = fakers[2]
fakers[2].linkedTo = fakers[1]
for i=1,2 do
debugDraw:addBody(fakers[i])
debugDraw:addBody(fakers[i].weldedTo)
end
end
]]
function createBox(x,y,w,h)
-- polygons are defined by a series of points in counter-clockwise order
local box = physics.body(POLYGON, vec2(-w/2,h/2), vec2(-w/2,-h/2), vec2(w/2,-h/2), vec2(w/2,h/2))
box.interpolate = true
box.x = x
box.y = y
box.restitutions = 0.25
box.sleepingAllowed = false
debugDraw:addBody(box)
return box
end
function createGround()
local ground = physics.body(POLYGON, vec2(20,20), vec2(20,0), vec2(boxSize+20,0), vec2(boxSize+20,20))
ground.type = STATIC
ground.restitution = wallBounciness
debugDraw:addBody(ground)
local wallLeft = physics.body(POLYGON, vec2(boxSize+20,20), vec2(boxSize+40,20), vec2(boxSize + 40,boxSize), vec2(boxSize+20, boxSize))
wallLeft.type = STATIC
wallLeft.restitution = wallBounciness
debugDraw:addBody(wallLeft)
local wallRight = physics.body(POLYGON, vec2(0,20), vec2(20,20), vec2(20,boxSize), vec2(0, boxSize))
wallRight.type = STATIC
wallRight.restitution = wallBounciness
debugDraw:addBody(wallRight)
return ground
end
function createRandPoly(x,y)
local count = math.random(3,10)
local r = math.random(25,75)
local a = 0
local d = 2 * math.pi / count
local points = {}
for i = 1,count do
local v = vec2(r,0):rotate(a) + vec2(math.random(-10,10), math.random(-10,10))
a = a + d
table.insert(points, v)
end
local poly = physics.body(POLYGON, unpack(points))
poly.x = x
poly.y = y
poly.sleepingAllowed = false
poly.restitution = 0.25
debugDraw:addBody(poly)
return poly
end
function cleanup()
clearOutput()
debugDraw:clear()
end
-- This function gets called once every frame
function physicsLabDraw()
for i,fake in ipairs(fake3D) do
--fake:updatePosition() --has had to be put in main draw
end
debugDraw:draw()
font("Vegur-Bold")
fontSize(22)
fill(255, 255, 255, 255)
--physics.gravity(defaultGravity) --has to be being set somewhere else?
physics.gravity(Gravity)
end
function collide(contact)
if contact.bodyA.fake3DLink ~= nil and contact.bodyB.fake3DLink ~= nil then
if contact.state == BEGAN then
if contact.bodyA.fake3DLink:bothCirclesContacting(contact.bodyB) then
--set masks and categories on bodies
contact.bodyA.fake3DLink:addToMasksAndCategories(3)
contact.bodyB.fake3DLink:addToMasksAndCategories(3)
end
elseif contact.bodyA.fake3DLink:isCollidable() then
contact.bodyA.fake3DLink:resetMasksAndCategories()
contact.bodyB.fake3DLink:resetMasksAndCategories()
end
end
if debugDraw then
debugDraw:collide(contact)
end
if currentTest and currentTest.collide then
currentTest:collide(contact)
end
end
--# PhysicsDebugDraw
PhysicsDebugDraw = class()
function PhysicsDebugDraw:init()
self.bodies = {}
self.joints = {}
self.touchMap = {}
self.contacts = {}
end
function PhysicsDebugDraw:addBody(body)
table.insert(self.bodies,body)
end
function PhysicsDebugDraw:addJoint(joint)
table.insert(self.joints,joint)
end
function PhysicsDebugDraw:clear()
-- deactivate all bodies
for i,body in ipairs(self.bodies) do
body:destroy()
end
for i,joint in ipairs(self.joints) do
joint:destroy()
end
self.bodies = {}
self.joints = {}
self.contacts = {}
self.touchMap = {}
end
function PhysicsDebugDraw:draw()
local drawAlpha = 15
pushStyle()
smooth()
strokeWidth(5)
local gain = 2.0
local damp = 0.5
stroke(255, 0, 241, drawAlpha)
for k,v in pairs(self.touchMap) do
local worldAnchor = v.body:getWorldPoint(v.anchor)
local touchPoint = v.tp
local diff = touchPoint - worldAnchor
local vel = v.body:getLinearVelocityFromWorldPoint(worldAnchor)
v.body:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor)
line(touchPoint.x, touchPoint.y, worldAnchor.x, worldAnchor.y)
end
stroke(0,255,0,drawAlpha)
strokeWidth(5)
for k,joint in pairs(self.joints) do
local a = joint.anchorA
local b = joint.anchorB
--line(a.x,a.y,b.x,b.y) --uncomment to see joints
end
stroke(255,255,255,drawAlpha)
noFill()
for i,body in ipairs(self.bodies) do
pushMatrix()
translate(body.x, body.y)
rotate(body.angle)
if body.type == STATIC then
stroke(255,255,255,drawAlpha)
elseif body.type == DYNAMIC then
if body.categories[1] == 2 then
stroke(150,255,150,drawAlpha)
else
stroke(225, 167, 90, drawAlpha)
end
elseif body.type == KINEMATIC then
stroke(150,150,255,drawAlpha)
end
if body.shapeType == POLYGON then
strokeWidth(3.0)
local points = body.points
for j = 1,#points do
a = points[j]
b = points[(j % #points)+1]
line(a.x, a.y, b.x, b.y)
end
elseif body.shapeType == CHAIN or body.shapeType == EDGE then
strokeWidth(3.0)
local points = body.points
for j = 1,#points-1 do
a = points[j]
b = points[j+1]
line(a.x, a.y, b.x, b.y)
end
elseif body.shapeType == CIRCLE then
strokeWidth(3.0)
line(0,0,body.radius-3,0)
ellipse(0,0,body.radius*2)
end
popMatrix()
end
stroke(255, 0, 0, drawAlpha)
fill(255, 0, 0, drawAlpha)
for k,v in pairs(self.contacts) do
for m,n in ipairs(v.points) do
ellipse(n.x, n.y, 10, 10)
end
end
popStyle()
end
function PhysicsDebugDraw:touched(touch)
local touchPoint = vec2(touch.x, touch.y)
if touch.state == BEGAN then
for i,body in ipairs(self.bodies) do
if body.type == DYNAMIC and body:testPoint(touchPoint) then
self.touchMap[touch.id] = {tp = touchPoint, body = body, anchor = body:getLocalPoint(touchPoint)}
return true
end
end
elseif touch.state == MOVING and self.touchMap[touch.id] then
self.touchMap[touch.id].tp = touchPoint
return true
elseif touch.state == ENDED and self.touchMap[touch.id] then
self.touchMap[touch.id] = nil
return true;
end
return false
end
function PhysicsDebugDraw:collide(contact)
if contact.state == BEGAN then
self.contacts[contact.id] = contact
--sound(SOUND_HIT, 26498888)
elseif contact.state == MOVING then
self.contacts[contact.id] = contact
elseif contact.state == ENDED then
self.contacts[contact.id] = nil
end
end
--# Fake3DSphere
Fake3DSphere = class()
function Fake3DSphere:init(x, y, radius)
self.circles = {}
self.sensorCircles = {}
local theseCircles = {}
for i=0,1 do
local circle = physics.body(CIRCLE, radius)
-- enable smooth motion
circle.interpolate = true
circle.x = x +(i*radius*2.2)
circle.y = y
circle.restitution = ballBounciness
circle.gravityScale = ballGravityScale
circle.sleepingAllowed = true
circle.mask = {1}
circle.categories = {i+2}
circle.fixedRotation = true
table.insert(self.circles, circle)
local sensorCircle = physics.body(CIRCLE, radius)
sensorCircle.position = circle.position
sensorCircle.interpolate = true
sensorCircle.sensor = true
sensorCircle.fake3DLink = self
sensorCircle.plane = i
sensorCircle.gravityScale = 0
sensorCircle.sleepingAllowed = true
sensorCircle.density = 0
sensorCircle.frequency = 0
sensorCircle.mass = 0
--sensorCircle.mask = {1,i+1}
--sensorCircle.categories = {}
table.insert(self.sensorCircles, sensorCircle)
table.insert(theseCircles, circle)
-- WELD holds the circle and the sensor together
--
local distJoint = physics.joint(WELD, circle, sensorCircle, circle.position)
debugDraw:addJoint(distJoint)
--]]
debugDraw:addBody(sensorCircle)
debugDraw:addBody(circle) -- must be added after sensor to draw correctly
end
self.sphere=CreateSphere(radius3D*1.5,readImage("Cargo Bot:Starry Background"),color(255))
self.sphere.shader=shader(S.v,S.f)
self.sphere.shader.ambientColor=color(255)*0.5
self.sphere.shader.directDirection=vec3(-1,1,-1):normalize()
self.sphere.shader.directColor=color(255)*0.5
self.sphere.fake3DLink = self
-- joint for constraining y values between circles
--
local wheelJoint = physics.joint(PRISMATIC, theseCircles[1], theseCircles[2], theseCircles[1].position, vec2(1,0))
--wheelJoint.enableLimit = true
debugDraw:addJoint(wheelJoint)
--]]
end
function Fake3DSphere:updatePosition()
--[[
--works but stutters, tries to override physics engine:
if self.circles[1].y ~= self.circles[2].y then
local newY = (self.circles[1].y + self.circles[2].y) / 2
self.circles[1].y = newY
self.circles[2].y = newY
end
]]
--[[ these were replaced by WELD, used to enforce sensor and circle positions
self.sensorCircles[1].x = self.circles[1].x
self.sensorCircles[1].y = self.circles[1].y
self.sensorCircles[2].x = self.circles[2].x
self.sensorCircles[2].y = self.circles[2].y
]]
local offset = boxSize/2
self.sphere.position = vec3(self.circles[1].x-offset,self.circles[1].y-offset, self.circles[2].x-offset)*proportion
end
function Fake3DSphere:bothCirclesContacting(otherBody)
if otherBody.fake3DLink == nil then
return false
end
if self.sensorCircles[1]:testOverlap(otherBody.fake3DLink.sensorCircles[1]) then
if self.sensorCircles[2]:testOverlap(otherBody.fake3DLink.sensorCircles[2]) then
return true
end
else
return false
end
end
function Fake3DSphere:addToMasksAndCategories(number)
--4 and 5 are added to number to prevent confusion with existing values (1-3)
self.circles[1].mask = {1, 4+number}
self.circles[2].mask = {1, 5+number}
self.circles[1].categories = {2, 4+number}
self.circles[2].categories = {3, 5+number}
--[[
print("myMask1:", table.unpack(self.circles[1].mask))
print("myMask2:", table.unpack(self.circles[2].mask))
print("categories1:", table.unpack(self.circles[1].categories))
print("categories2:", table.unpack(self.circles[2].categories))
]]
end
function Fake3DSphere:resetMasksAndCategories()
--print("masks and categories reset")
self.circles[1].mask = {1}
self.circles[2].mask = {1}
self.circles[1].categories = {2}
self.circles[2].categories = {3}
end
function Fake3DSphere:isCollidable()
-- true if there's more than one value in any mask
return #self.circles[1].mask > 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment