Create Physics Polygons

When working with the physics engine in Codea, one thing that I found lacking was the ability to create shapes other then simple circles and rectangles, so I have been working on a class/function to create other types of shapes with given parameters.

Currently the function is capable of creating simple equilateral, equiangular polygons as well as circles and rectangles, and I am working on creating non equilateral triangles as well in addition to shapes like rhombi and trapezoids.

The current format of the function is below –

– Creates Polygon/Ellipse (s = sides, d = {dimensions}

PhysicsShapes:createShape(x,y,s,d)

if #diagonal == 1 then d = {diagonal length}
if #diagonal == 2 then d = {width, height}

Screenshots of the function in action can be seen at the link below.
http://www.flickr.com/photos/99433588@N04/?saved=1

Have you looked at physics.body(POLYGON,vec2…) . This allows you to create just about any shape you want. I’ll admit it takes work figuring out the vec2 points, but it’s doable.

Basically what I was doing is putting together a function that uses a circle with a radius equal to half the specified diagonal and then rotating points around the origin of the circle to form the vector points of the shape vertices, then I create the body as with the

local body = physics.body(POLYGON,unpack(self.points))

I have been having a little difficulty figuring out how I would declare the arguments in the initial function however for creating non standard shapes, because right now the process is segmented.

First calculates turn angle of points
Calculates x points and dumps to table self.xp
Calculates y points and dumps to table self.yp

Performs transformation of points if sides == 4 (to form rectangle)

Orders self.xp,self.yp into vec2(self.xp[i],self.yp[i]) and dumps into self.points

Performs rotation to get standard orientation of rectangles / odd sided polygons by comparing self.points[1] to vec2(0,radius) and updates self.points

Creates polygon with given points or if circle creates circle with specified radius.

local bodyConstruct = function ()

    if sides == 0 or sides == 1 then

        return CIRCLE, r end

    if sides >= 3 then

        return POLYGON, unpack(self.points) end

end

local body = physics.body(bodyConstruct())

I haven’t gotten the triangle transformation or trapazoid / rhombus (slants) to work yet however.

P.S. - I’m having kind of a hard time posting code. Whenever I just try and paste it it comes back without proper formatting unless I space out every line … Is there a more efficient way to do this?

You wouldn’t set the physics body as a local variable, it won’t be useable in other functions and I’m pretty sure it doesn’t take long for it to be put in to the garbage collector

Sounds good. I didn’t fully understand what was happening from your 1st post.

I was using the Test Class structure from the physics simulation of Codea to set up all of the different screens and creating the shapes in the TestX:setup function. All of the physics shapes created by the function were retained by the simulation until the test is changed, simulation reset, or cleared by the debugDraw:clear. I haven’t figured out how to actually delete the shapes either however, and because I created walls all around the screen they just tend to stay there as long as there is room on the screen.

@Luatee - Is there a more efficient way to call the shape creation function then setting it to a local variable?

This is my testing environment for the shapes creation:

These are the parameters set in the Main class

tests = {Test1(),Test2(),Test3()}

defaultGravity = physics.gravity()
parameter.integer("TestNumber", 1, #tests)        
parameter.text("Function","Create Shape")
parameter.integer("Sides", 0,15,4)
parameter.color("StrokeColor", color(184, 180, 223, 255))   
parameter.integer("Width",30,200,50)
parameter.integer("Height",30,200,50) 
parameter.number("Density",0.5,10,1 )
parameter.number("AngularDamping",0,10,0)
parameter.number("LinearDamping",0,10,0 )
parameter.number("ForceGravity",0,10,0.5)

The shapes that are created by touch are defined in the Test#:touched(touch) function

function Test3:touched(touch)

   local dynamic = math.random (20,30)

if touch.state == BEGAN then
  
    local Contents = debugDraw:returnContents (touch.x,touch.y)

    local sides = Sides
    local size = {Width,Height}
        
    local physicsShape = PhysicsShapes:createShape(touch.x,touch.y,Sides,size)
    if SmartPolygon == true then PhysicsShapes:createSmartShape (Sides,Size,physicsShape) end
    physicsShape.gravityScale = ForceGravity
    physicsShape.density = Density
    physicsShape.stroke = {StrokeColor}
    physicsShape.name = Shape
    physicsShape.linearDamping = LinearDamping
    physicsShape.angularDamping = AngularDamping
end 

end

This is an example of what I would put in the Test#:setup function

– pendulum is an example of a circle

local pendulum = PhysicsShapes:createShape(WIDTH/2, HEIGHT/2 - 135, 0,{120})

pendulum.stroke = {self.defaultStroke}
PhysicsShapes:createSmartShape (0,120,pendulum)
pendulum.gravityscale = 2.0
pendulum.friction = 1.0
pendulum.density = 5.0

– pendulumArm is an example of a Rectangle
local pendulumArm = PhysicsShapes:createShape(WIDTH/2, HEIGHT/2 - 75, 4,{25,150})

pendulumArm.stroke = {self.defaultStroke}

– pentagonS is an example of a Prntagon
local pentagonS = PhysicsShapes:createShape(WIDTH/2, HEIGHT/2, 5, {50}

@Beckett2000 I wrote this awhile ago. I don’t know if you could use any of this or if this even applies.


function setup()
    sides=3
    radius=100
    create(sides,radius)
end

function draw()
    background(40, 40, 50)
    fill(255)
    stroke(255)
    strokeWidth(3)
    for z=2,#tab do
        line(a1.x+tab[z].x,a1.y+tab[z].y,a1.x+tab[z-1].x,a1.y+tab[z-1].y)
    end 
    line(a1.x+tab[#tab].x,a1.y+tab[#tab].y,a1.x+tab[1].x,a1.y+tab[1].y)
    text(sides,a1.x,a1.y)
    if a1.y<-100 then
        a1:destroy()
        sides = sides + 1
        create(sides,radius)
    end
end

function create(sides,r)
    tab={}
    for a=1,360,360/sides do
        x=math.sin(math.rad(a))*r
        y=math.cos(math.rad(a))*r
        table.insert(tab,vec2(x,y))
    end  
    a1=physics.body(POLYGON,unpack(tab))
    a1.x=300
    a1.y=600
    a1.gravityScale=.2
end

This is the function that I am currently using for shape creation. It is a little bit redundant however and could use optimization.

@dave1707 Thanks, that is helpful. I have only been working with Codea/Lua for a couple weeks, and that seems like a much more efficient way to calculate all the points. I went about it by breaking it into parts, but it does take up a lot of space.

Do you have any suggestions for how I could handle creating other types of parallelograms or trapezoid?



PhysicsShapes = class()

function PhysicsShapes:init()
    
    self.rotateXY = {}
    self.xp = {}
    self.yp = {}
    self.points = {}
    
end


-- Create Physics Ellipse or Polygon 
-- s = sides d = {dimensions}
    
function PhysicsShapes:createShape(x,y,s,d)
    
    local dimensions = {unpack(d)}
    
    local sides = s
    local Turn = math.rad(360/sides)
    
    self.dimentional = dimensions[1]
   
    -- Calculate Point Turn Angles --
    local Angle = Turn
    local angleadd = 0
    self.rotateXY = {}
    for i = 1, sides do
        local Rotate = Angle + angleadd
        table.insert(self.rotateXY,Rotate)
        local deltaAngle = Angle + angleadd
        angleadd = math.rad(360/sides)
        Angle = deltaAngle
    end
    
    -- Verify Length of Table --
    local checkLength = function (t,l)
        local table = {unpack(t)}
        local countLength = #table
        local Length = l
        local verify = countLength == Length
        if not verify  == true then do
            return verify end

        elseif verify == true then do
            return verify end
        end
    end

    -- Check Arguments to Dimensions {} --
    local tester1 = checkLength(dimensions,1)
    if tester1 then
         self.dimentional = dimensions[1]
        print("1 Argument To Dimensions")
        print("Specified Diagonal", (self.dimentional))
    end

    local tester2 = checkLength(dimensions,2)
    if tester2 then
        self.dimentional = math.sqrt((dimensions[1]^2)+(dimensions[1]^2))
        print("2 Arguments To Dimensions")
        if sides == 4 then
            print("Width",(dimensions[1]))
            print("Height",(dimensions[2]))
        end
        print("Derived Diagonal", (self.dimentional))
    end
   
    local r = 0.5 * self.dimentional
    local shapeWidth = dimensions[1]
    local shapeHeight = dimensions[2]
    local posX = x 
    local posY = y
   
    self.xp = {}
    self.yp = {}
    self.points = {}
        
   -- Create Iteration of Table Values --  
    local iterateTable = function (table)
        local i = 0
        local n = #table
        return function ()
            i = i + 1
            if i <= n then return table[i] end
        end
    end
    
    -- Calculate X Coordinates --
    local RotateX = iterateTable(self.rotateXY)  
    while true do
        local rotateAngleX = RotateX()
        if rotateAngleX == nil then break end
            
        for i = 1, 1 do
            local xv = r * math.cos(rotateAngleX)
            table.insert(self.xp,xv)
        end
    end
        
    -- Calculate Y Coordinates -- 
    local RotateY = iterateTable(self.rotateXY)  
    while true do
        local rotateAngleY = RotateY()
        if rotateAngleY == nil then break end
    
        for i = 1, 1 do
            local yv = r * math.sin(rotateAngleY)
            table.insert(self.yp,yv)
        end
    end          
    
    -- Transform 3 Sided Polygon (incomplete) --
    local triangleS = sides == 3
    
    if triangleS then
        
        local tri1 = vec2(self.xp[1],self.yp[1])
        local tri2 = vec2(self.xp[2],self.yp[2])
        local tri3 = vec2(self.xp[3],self.yp[3])
      
        local dist12 = tri1:dist(tri2)
        local dist23 = tri2:dist(tri3)
        local dist31 = tri3:dist(tri1)
        
        local testPoints = {}
        for i = 1, sides do
            table.insert(testPoints,vec2(self.xp[i],self.yp[i]))
            i = i + 1 end
    
     --[[   
        for i = 1, sides do
            print (testPoints[i])
            i = i + 1 
       ]]--
    end    
        
    -- Transform 4 Sided Polygon (Create Rectangle) --
    local rectangleS = sides == 4
    
    if rectangleS then
            
        local point4 = vec2(self.xp[4],self.yp[4])             
        local point3 = vec2(self.xp[3],self.yp[3])
        local point2 = vec2(self.xp[2],self.yp[2])
        local point1 = vec2(self.xp[1],self.yp[1])
    
        local distAdj = point3:dist(point2)
        print ("Dist. Adjacent Points ", distAdj)

        if tester2 and rectangleS then
            local porportionXY = dimensions[1] <= dimensions[2]
            
            self.rectDistribute = 0
            
            -- Dimension Transformation Distributor --
            if dimensions[1] < dimensions[2] then
                print("Transforming Height")
                self.rectDistribute = ((((dimensions[2] - distAdj)^2)/2)^0.5)/2
                print("Height Distributor", self.rectDistribute) 
            elseif dimensions[1] == dimensions[2] then
                print("No Transformation Value") 
            elseif dimensions[1] > dimensions[2] then
                print("Transforming Width")
                self.rectDistribute = (((((math.abs(dimensions[2] - distAdj))^2)/2)^0.5)/2)*-1
                print("Width Distributor", self.rectDistribute)
            end
            
            local distributeT = self.rectDistribute
                
            -- Transform XP
            self.xp[1] = self.xp[1] + distributeT
            self.xp[2] = self.xp[2] - distributeT
            self.xp[3] = self.xp[3] - distributeT
            self.xp[4] = self.xp[4] + distributeT
                
            -- Transform YP
            self.yp[1] = self.yp[1] + distributeT
            self.yp[2] = self.yp[2] - distributeT
            self.yp[3] = self.yp[3] - distributeT
            self.yp[4] = self.yp[4] + distributeT
        end
    end
            
    local polyS = sides % 2 ~= 0
    local rectS = sides == 4
                                     
    -- Order point lists into Coordinate Pairs --
    local Lines = table.getn(self.xp)
        for i = 1, Lines do 
            local xVec = self.xp[i]
            local yVec = self.yp[i]
            i = i + 1
            
            local vectorPoints = vec2(xVec,yVec)
            table.insert(self.points, vectorPoints)
        end 
        
    -- Rotate Odd Sided Polygon to standard orientation --
    if polyS then
                                             
        local r = 0.5 * self.dimentional
        local pointV = vec2(0,r)
        local rotationV = self.points[1]:angleBetween(pointV)
        print("Polygon Initial Rotation",math.deg(rotationV),"Degrees")

        local polyPoints = #self.points
        for i = 1, polyPoints do
            self.points[i] = self.points[i]:rotate(rotationV)
            i = i + 1
        end
    end 
 
    -- Rotates Rectangles to standard orientation --
    if rectS then
        
        local rectPoints = #self.points
        for i = 1, rectPoints do
            self.points[i] = self.points[i]:rotate(math.rad(45))
            i = i + 1
        end
    end
    
    -- Determines Body Shape Type --     
    local bodyConstruct = function ()
        if sides == 0 or sides == 1 then
            return CIRCLE, r end
        if sides >= 3 then
            return POLYGON, unpack(self.points) end
    end
    
    -- Renders Shape with Given Parameters --
    local shape = physics.body(bodyConstruct())
    shape.x = posX
    shape.y = posY
    shape.sleepingAllowed = true
    shape.restitution = 0.4
    debugDraw:addBody(shape)
    print("Creation Successful")
    return shape
    
end



@Becket2000 If you don’t mind random odd shapes, try this. I added math.random to the code.


function setup()
    sides=3
    radius=100
    create(sides,radius)
end

function draw()
    background(40, 40, 50)
    fill(255)
    stroke(255)
    strokeWidth(3)
    for z=2,#tab do
        line(a1.x+tab[z].x,a1.y+tab[z].y,a1.x+tab[z-1].x,a1.y+tab[z-1].y)
    end 
    line(a1.x+tab[#tab].x,a1.y+tab[#tab].y,a1.x+tab[1].x,a1.y+tab[1].y)
    text(sides,a1.x,a1.y)
    if a1.y<-100 then
        a1:destroy()
        sides = sides + 1
        create(sides,radius)
    end
end

function create(sides,r)
    tab={}
    for a=1,360,360/sides do
        x=math.sin(math.rad(a))*r
        y=math.cos(math.rad(a))*r
        x=x+math.random(-50,50)
        y=y+math.random(-50,50)
        table.insert(tab,vec2(x,y))
    end  
    a1=physics.body(POLYGON,unpack(tab))
    a1.x=300
    a1.y=600
    a1.gravityScale=.2
end

@Beckett2000 I had to look up the definition of trapezoid and parallelogram. Here’s some code that creates random trapezoids and parallelograms. Tap the screen for a different object.


displayMode(FULLSCREEN)

function setup()
    create()
end

function draw()
    background(40, 40, 50)
    fill(255)
    text("tap screen for next object",350,700)
    text("trapezoid",100,500)
    text("parallelogram",100,200)
    stroke(255)
    strokeWidth(3)
    
    -- draw trapezoid
    for z=2,#ttab do
        line(ttab[z].x,ttab[z].y,ttab[z-1].x,ttab[z-1].y)
    end 
    line(ttab[#ttab].x,ttab[#ttab].y,ttab[1].x,ttab[1].y)
    
    -- draw parallelogram
    for z=2,#ptab do
        line(ptab[z].x,ptab[z].y,ptab[z-1].x,ptab[z-1].y)
    end 
    line(ptab[#ptab].x,ptab[#ptab].y,ptab[1].x,ptab[1].y)
end

function create()
    ttab={}    -- trapezoid table
    ptab={}    -- parallelogram table
    
    a1=math.random(150,300)
    a2=math.random(30,100)
    x1=math.random(0,200)
    x2=math.random(0,200)
    y1=math.random(50,200)
    
    -- trapezoid
    table.insert(ttab,vec2(200+x1+a1,400+y1))
    table.insert(ttab,vec2(200+x1,400+y1))
    table.insert(ttab,vec2(200+x2,400))
    table.insert(ttab,vec2(200+x2+a2,400))
    
    -- parallelogram
    table.insert(ptab,vec2(200+x1+a1,100+y1))
    table.insert(ptab,vec2(200+x1,100+y1))
    table.insert(ptab,vec2(200+x2,100))
    table.insert(ptab,vec2(200+x2+a1,100))
end

function touched(t)
    if t.state==BEGAN then
        create()
    end
end


@dave1707 - Thank you, that is exactly what I was looking for. I am trying to create a function that can be called on demand to create shapes as various bodies to act in a simulation.

The PhysicsObject class draws all of the shapes and keeps track of their locations. It is in a simmilar setup as the Debug Draw class from the Physics Lab with regards to storing the shapes in tables, but I’m currently trying to enter the shapes created as keyed values so they can be more easily found and removed.

I used part of the create function you posted above to show the structure I am trying to achieve.



function create(x,y,sides,d)
     
    local r = 0.5 * d
    
    -- Calculate Vec2 Points of Polygon --
    local points = {}
    for a=1,360,360/sides do
        local x = math.sin(math.rad(a))*r
        local y = math.cos(math.rad(a))*r
        table.insert(points,vec2(x,y))
    end
    
    -- If Side Number Is Even Rotate --
    if sides % 2 == 0 then
    for i = 1, sides do
        points[i] = points[i]:rotate(math.rad((360/sides)/2))
        i = i + 1 end
    end
        
    -- Determines Body Shape Type --     
    local bodyConstruct = function ()
        if sides == 0 or sides == 1 then
            return CIRCLE, r end
        if sides >= 3 then
            return POLYGON, unpack(points) end
    end
    
    local shape = physics.body(bodyConstruct())
    shape.x = x
    shape.y = y
    shape.gravityScale = .2
    shape.sleepingAllowed = false
    shape.restitution = 0.4
    PhysicsObject:addBody(shape)
    return shape
end


I still need to add parts to create the rectangles, parallelograms, and trapezoids however and am working on that.

I added the ability to create rectangles, and fixed the previous rotation which didn’t actually set the even sided shapes to the proper rotation.

Previously the shapes seemed to be rotated properly, but bounced slightly to the left when created and hitting the ground, because they were not entirely aligned. I fixed this by orienting the rotation to a point instead of just rotating by a set degree measure of 180/sides.

Next I am going to add trapezoids/parallelograms



-- Create Physics Ellipse or Polygon 
-- s = sides d = {dimensions}

function create(x,y,s,d)
   
    -- Checks Aruments to Dimensions --
    local radius = function(d)
        if #d == 1 then return 0.5 * d[1] end
        if #d == 2 then return 0.5 * (math.sqrt((d[1]^2)+(d[1]^2))) end
    end
    
    local r = radius(d)
    local sides = s
    
    -- Calculate Vec2 Points of Polygon --
    local points = {}
    for a=1,360,360/sides do
        local x = math.sin(math.rad(a))*r
        local y = math.cos(math.rad(a))*r
        table.insert(points,vec2(x,y))
    end
    
    -- If Side Number Is Even Rotate (180/Sides Degrees) --
    if sides % 2 == 0 then
        local pointA = vec2(0,r):rotate(math.rad(180/sides))
        local pointB = points[1]
    for i = 1, sides do
        points[i] = points[i]:rotate(pointB:angleBetween(pointA)) end
    end
    
     -- Creates Non-Equilateral Triangle If Sides == 3 and Width ~= Height --
    if sides == 3 and #d == 2 and d[1] ~= d[2] then
        
        points[1] = points[1] + vec2(0,(d[2] - d[1])/2)
        points[2] = points[2] - vec2(0,(d[2] - d[1])/2)
        points[3] = points[3] - vec2(0,(d[2] - d[1])/2)  
    end
    
    -- Creates Rectangle If Sides == 4 and Width ~= Height --
    if sides == 4 and #d == 2 and d[1] ~= d[2] then
    
        points[1] = points[1] + vec2(0,(d[2] - d[1])/2)
        points[2] = points[2] + vec2(0,(d[2] - d[1])/2)
        points[3] = points[3] - vec2(0,(d[2] - d[1])/2)
        points[4] = points[4] - vec2(0,(d[2] - d[1])/2)    
    end
    
    -- Determines Body Shape Type --     
    local bodyConstruct = function ()
        if sides == 0 or sides == 1 then return CIRCLE, r end
        if sides >= 3 then return POLYGON, unpack(points) end
    end
    
    -- Creates Shape Physics Body --
    local shape = physics.body(bodyConstruct())
    shape.x = x
    shape.y = y
    shape.gravityScale = .2
    shape.sleepingAllowed = false
    shape.restitution = 0.4
    PhysicsObject:addBody(shape)
    return shape
    
end