#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# Polygon editing example with physics

edited January 2012 Posts: 779

I thought I'd post some sample code that didn't make it into 1.3 due to time restrictions. This example shows you how to use the triangulate function to draw arbitrary polygons as well as methods for editing them using touches. The example also shows how physics interact with these polygons as well.

``````-- distPointToLineSeg(): shortest distance of a point to a line segment.
function distPointToLineSeg(p , s1, s2)
local v = s2 - s1
local w = p - s1

c1 = w:dot(v)
if c1 <= 0 then
return p:dist(s1)
end

c2 = v:dot(v)
if c2 <= c1 then
return p:dist(s2)
end

b = c1 / c2;
pb = s1 + b * v;
return p:dist(pb)
end
--===================================================================

-- Use this function to perform your initial setup
function setup()
debugDraw = PhysicsDebugDraw()

print("Hello Polygon!")
print("1. Tap in clockwise order to create a polygon.")
print("2. Drag existing points to move them.")
print("3. Drag on lines to add new points.")

-- the mesh to draw the polygon with
polyMesh = mesh()
-- the current set of vertices for the polygon
verts = {}
-- the polygon fill color
col = color(255, 188, 0, 255)

index = -1
touchID = -1

-- rigid body for the polygon
polyBody = nil

timer = 0
end

-- This function gets called once every frame
function draw()

timer = timer + DeltaTime
-- create a circle every 2 seconds
if timer > 2 then
local body = physics.body(CIRCLE, 25)
body.restitution = 0.5
body.x = WIDTH/2
body.y = HEIGHT
timer = 0
end

-- This sets the background color to black
background(0, 0, 0)

-- draw physics objects
debugDraw:draw()

-- draw the polygon interia
fill(col)
polyMesh:draw()

pushStyle()
lineCapMode(PROJECT)
fill(255, 255, 255, 255)

-- draw the polygon outline
local pv = verts
for k,v in ipairs(verts) do
noStroke()
ellipse(v.x, v.y, 10, 10)
stroke(col)
strokeWidth(5)
line(pv.x, pv.y, v.x, v.y)
pv = v
end
if pv then
line(pv.x, pv.y, verts.x, verts.y)
end
popStyle()

end

function touched(touch)
local tv = vec2(touch.x, touch.y)

if touch.state == BEGAN and index == -1 then
-- find the closest vertex within 50 px of thr touch
touchID = touch.id
local minDist = math.huge
for k,v in ipairs(verts) do
local dist = v:dist(tv)
if dist < minDist and dist < 50 then
minDist = dist
index = k
end
end

-- if no point is found near the touch, insert a new one
if index == -1 then
index = #verts
if index == 0 then
index = index + 1
end

-- if touch is within 50px to a line, insert point on line
if #verts > 2 then
local minDist = math.huge
local pv = verts[index]
for k,v in ipairs(verts) do
local dist = distPointToLineSeg(tv, pv, v)
if dist < minDist and dist < 50 then
minDist = dist
index = k
end
pv = v
end
end

table.insert(verts, index, tv)
else
verts[index] = tv
end

elseif touch.state == MOVING and touch.id == touchID then
verts[index] = tv
elseif touch.state == ENDED and touch.id == touchID then
index = -1
end

-- use triangulate to generate triangles from the polygon outline for the mesh
polyMesh.vertices = triangulate(verts)
if polyBody then
polyBody:destroy()
end
if #verts > 2 then
polyBody = physics.body(POLYGON, unpack(verts))
polyBody.type = STATIC
end
end

PhysicsDebugDraw = class()

function PhysicsDebugDraw:init()
self.bodies = {}
self.joints = {}
self.touchMap = {}
self.contacts = {}
end

table.insert(self.bodies,body)
end

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()

pushStyle()
smooth()
strokeWidth(5)
stroke(128,0,128)

local gain = 2.0
local damp = 0.5
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,255)
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)
end

stroke(255,255,255,255)
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,255)
elseif body.type == DYNAMIC then
stroke(150,255,150,255)
elseif body.type == KINEMATIC then
stroke(150,150,255,255)
end

if body.shapeType == POLYGON then
strokeWidth(5.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(5.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(5.0)
strokeWidth(2.5)
end

popMatrix()
end

stroke(255, 0, 0, 255)
fill(255, 0, 0, 255)

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

``````

• edited January 2012 Posts: 622

That's fun just to play with. It will come in handy as well.

• Posts: 118

Thanks for that @John! Will be useful in upcoming projects!

• Posts: 622

I still love this. So, much so it's called 112 in my list of projects.

I'm now using it to freestyle draw vertices. I've found a place to add a clearoutput() and print that allow the vertices to be cut and pasted out.

``````elseif touch.state == ENDED and touch.id == touchID then
index = -1
clearOutput()
print(unpack(verts))
end
``````
• edited September 2021 Posts: 1,819

This is an awesome old project! I’ve attached a zip that updates the deprecated “unpack” syntax.

Does anybody know how one would set the mesh to have an image in it?

• Posts: 911

@UberGoober great wee program.

Here a non perfect solution but will hopefully push you in the right direction for mapping an image

``````-- John Polygon Editing

-- distPointToLineSeg(): shortest distance of a point to a line segment.
function distPointToLineSeg(p , s1, s2)
local v = s2 - s1
local w = p - s1

c1 = w:dot(v)
if c1 <= 0 then
return p:dist(s1)
end

c2 = v:dot(v)
if c2 <= c1 then
return p:dist(s2)
end

b = c1 / c2;
pb = s1 + b * v;
return p:dist(pb)
end
--===================================================================

-- Use this function to perform your initial setup
function setup()
debugDraw = PhysicsDebugDraw()

print("Hello Polygon!")
print("1. Tap in clockwise order to create a polygon.")
print("2. Drag existing points to move them.")
print("3. Drag on lines to add new points.")

-- the mesh to draw the polygon with
polyMesh = mesh()
-- the current set of vertices for the polygon
verts = {}
-- the polygon fill color
col = color(255, 188, 0, 255)

index = -1
touchID = -1

-- rigid body for the polygon
polyBody = nil

timer = 0

polyMesh.texture=img
end

-- This function gets called once every frame
function draw()

timer = timer + DeltaTime
-- create a circle every 2 seconds
if timer > 0.8 then
local body = physics.body(CIRCLE, 25)
body.restitution = 0.5
body.x = WIDTH/2
body.y = HEIGHT
timer = 0
end

-- This sets the background color to black
background(0, 0, 0)

-- draw physics objects
debugDraw:draw()

-- draw the polygon interia
fill(col)

polyMesh:draw()

pushStyle()
lineCapMode(PROJECT)
fill(255, 255, 255, 255)

-- draw the polygon outline
local pv = verts
for k,v in ipairs(verts) do
noStroke()
ellipse(v.x, v.y, 10, 10)
stroke(col)
strokeWidth(2)
line(pv.x, pv.y, v.x, v.y)
pv = v
end
if pv then
line(pv.x, pv.y, verts.x, verts.y)
end
popStyle()

end

function touched(touch)
local tv = vec2(touch.x, touch.y)

if touch.state == BEGAN and index == -1 then
-- find the closest vertex within 50 px of thr touch
touchID = touch.id
local minDist = math.huge
for k,v in ipairs(verts) do
local dist = v:dist(tv)
if dist < minDist and dist < 50 then
minDist = dist
index = k
end
end

-- if no point is found near the touch, insert a new one
if index == -1 then
index = #verts
if index == 0 then
index = index + 1
end

-- if touch is within 50px to a line, insert point on line
if #verts > 2 then
local minDist = math.huge
local pv = verts[index]
for k,v in ipairs(verts) do
local dist = distPointToLineSeg(tv, pv, v)
if dist < minDist and dist < 50 then
minDist = dist
index = k
end
pv = v
end
end

table.insert(verts, index, tv)
else
verts[index] = tv
end

elseif touch.state == MOVING and touch.id == touchID then
verts[index] = tv
elseif touch.state == ENDED and touch.id == touchID then
index = -1
end

-- use triangulate to generate triangles from the polygon outline for the mesh
polyMesh.vertices = triangulate(verts)

local t={}
local flag=0
local minx=0
local miny=0
local maxx=0
local maxy=0
for i,p in pairs(verts) do
if flag==0 then
minx=p.x
miny=p.y
maxx=p.x
maxy=p.y
flag=1
else
if p.x<minx then minx=p.x end
if p.y<miny then miny=p.y end
if p.x>maxx then maxx=p.x end
if p.y>maxy then maxy=p.y end
end
end

for i,p in pairs(verts) do
local nx=(p.x-minx)/(maxx-minx)
local ny=(p.y-miny)/(maxy-miny)
table.insert(t,vec2(nx,ny))
end

polyMesh.texCoords=triangulate(t)
polyMesh:setColors(color(255))
if polyBody then
polyBody:destroy()
end
if #verts > 2 then
polyBody = physics.body(POLYGON, table.unpack(verts))
polyBody.type = STATIC
end
end

PhysicsDebugDraw = class()

function PhysicsDebugDraw:init()
self.bodies = {}
self.joints = {}
self.touchMap = {}
self.contacts = {}
end

table.insert(self.bodies,body)
end

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()

pushStyle()
smooth()
strokeWidth(5)
stroke(128,0,128)

local gain = 2.0
local damp = 0.5
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,255)
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)
end

stroke(255,255,255,255)
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,255)
elseif body.type == DYNAMIC then
stroke(150,255,150,255)
elseif body.type == KINEMATIC then
stroke(150,150,255,255)
end

if body.shapeType == POLYGON then
strokeWidth(5.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(5.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(5.0)
strokeWidth(2.5)
end

popMatrix()
end

stroke(255, 0, 0, 255)
fill(255, 0, 0, 255)

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

``````
• Posts: 1,819

Thanks @West, though of course it’s @John ’s not mine. Did I seem like I was claiming it was mine?

• Posts: 911
@UberGoober not at all. Thanks for resurfacing it
• Posts: 1,819

@West I can’t make hide nor hair of the code you added. Can you explain it a little?

• Posts: 911

@UberGoober I can try, but I don't fully understand meshes/triangulation which I suspect is leading to the discrepancies in the images. Anyway, here is my train of thought:

In setup

``````img=readImage(asset.builtin.Cargo_Bot.Crate_Blue_3)
polyMesh.texture=img
``````

This assigns the image to be used as the texture.

verts - this is the array of the polygon vertices as input by the user

`````` polyMesh.vertices = triangulate(verts)
``````

The triangulate turns the user inputted vertices into an array of mesh vertices. In a mesh the polygon is represented by a series of triangles. For example a rectangle would be represented by two triangles, and expressed by the 6 vertices - 3 for the first triangle and three for the second (even though some of the vertices are shared, I think they need to be explicitly represented.

To overlay a texture, each vertex in the mesh (polyMesh.vertices) needs to reference a point/position in the texture image. This goes into polyMesh.texcoords as a list of equivalent texture coordinates. These coordinates need to run between 0 and 1 for both left to right and bottom to top of the image.

This bit of code is finding the bounding box of the user inputted polygon

``````local flag=0
local minx=0
local miny=0
local maxx=0
local maxy=0
for i,p in pairs(verts) do
if flag==0 then
minx=p.x
miny=p.y
maxx=p.x
maxy=p.y
flag=1
else
if p.x<minx then minx=p.x end
if p.y<miny then miny=p.y end
if p.x>maxx then maxx=p.x end
if p.y>maxy then maxy=p.y end
end
end
``````

then (hopefully) scaling each point to this bounding box (for example if the leftmost user inputted point was at x=100 and the right most point was at 300, then a point at 200 would be in the middle and would have a texture coordinate of x=0.5)

`````` for i,p in pairs(verts) do
local nx=(p.x-minx)/(maxx-minx)
local ny=(p.y-miny)/(maxy-miny)
table.insert(t,vec2(nx,ny))
end

``````

So the array t, should contain a scaled version of the user inputted polygon, between 0 and 1.

Finally, we need to have the array of the mesh texcoords, rather than the user inputted texCoords so we need to triangulate the texcoords too (like we did with the vertices at the start)

``````polyMesh.texCoords=triangulate(t)
``````

I am assuming that the triangulate function will work in the same way on both the vertices and the texcoords - but this may be where it is falling down.

Maybe a better way would be to map the polyMesh.vertices (after initial triangulation) to the texture rather than fitting the user inputted vertices (verts) to the texture then triangulating.

• Posts: 1,819

@West… I think I get it but…

So it’s like this: first, we need to know the bounding box of the entire polygon mesh, and the position of each mesh vertex in relation to that bounding box.

Then, we need to know what part of the image is being used, in other words which four points on the image should correspond to each corner of the bounding box of the polygon.

Finally, once we have the bounding box of the polygon mapped to a box somewhere on the image (or maybe the whole image), we can get the texCoords of each vertex, by translating the (relative) vertex coordinates into the (absolute) coordinates in the bounding box of the image.

…is that right in theory?

• Posts: 911

@UberGoober Yes I think so. The issue is you are mapping a rectangle (the texture) onto a polygon, but didn’t specify how this was to be mapped.

The bounding box approach is to allow a cookie cutter type approach - you are effectively stretching the polygon in the x and y direction to the size of the texture image until one of a polygon vertex touches the each edge of the texture then chop out the shape of polygon from the texture.

An alternative would be to map each point perimeter of the polygon to a set of equally spaced points around the perimeter of the mesh - then the texture would be squashed and distorted on to the shape - I think this might be a bit harder to implement though

• Posts: 10,048

@UberGoober @West Here’s a simple example. Drag your finger around the screen to enclose an area. When you lift your finger, what you enclosed will be cut out of the background image.

``````viewer.mode=FULLSCREEN

function setup()
count=0
tab={}
mtab={}
m=mesh()
fill(255)
sizeX,sizeY=WIDTH,HEIGHT
end

function draw()
background(0)
if result~=nil then
sprite(result,WIDTH/2,HEIGHT/2)
else
sprite(img,WIDTH/2,HEIGHT/2)
for a,b in pairs(tab) do
ellipse(b.x,b.y,5)
end
end
end

function touched(t)
if t.state==CHANGED then
count=count+1
table.insert(tab,t)
if #tab>1 then
table.insert(mtab,(vec2(tab.x,tab.y)))
table.insert(mtab,(vec2(tab[count-1].x,tab[count-1].y)))
table.insert(mtab,(vec2(tab[count].x,tab[count].y)))
end
elseif t.state==ENDED then
process()
end
end

function process()
m.vertices=mtab
m:setColors(255,255,255)
m.draw(m)
setContext()
original=image(WIDTH,HEIGHT)
setContext(original)
sprite(img,WIDTH/2,HEIGHT/2)
setContext()
result=image(sizeX,sizeY)
setContext(result)
blendMode(MULTIPLY)
sprite(original,sizeX/2,sizeY/2)
blendMode(NORMAL)
setContext()
end
``````
• Posts: 1,819

@dave1707 I am not sure but I think your example may not be quite applicable to the problem, because you’re using a mesh shape as a mask for drawing a sprite, instead of applying an image to a mesh itself as a texture.

It’s a really clever solution to the visual needs of this demo, neatly avoiding all that triangulation jazz.

But I’m not clear if it helps with of drawing an image inside a 2D physics body—for example, if you used this code on a 2D square that was bouncing off a floor, the sprite wouldn’t stay aligned with the cube’s position and rotation, would it?

• Posts: 10,048

@UberGoober So you’re creating a mesh of different shapes that you want an image applied to. I’ll have to look thru what else I have and see if I have something for that would work.

• Posts: 10,048

Moved code that was here to another discussion.