For a while I’m playing around with sprites running up and down a series of hills (2D game). I plan to fill the hills, represented by a physics.body chain with a seamless texture. What I try to do is, to complete the chain to a polygon, triangular it (build-in function) and use this for a mesh. Is that a way to do it and how to set the appropriate TexCoords? Probably I could use Rects too in my mesh but to be honest, I never understand the usage of the TexCoords.
Any hint is welcome!
Thanks in advance!
The best way to do it is to set each texCoord element to the it’s corresponding vertex divided by the width/height of the texture being used. The texture itself has to have power of 2 dimensions for wrapping to be enabled (i.e. 64x64, 128x128, 256x256)
Here’s an example that I modified to use a repeating texture:
-- 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(101, 228, 107, 255)
index = -1
touchID = -1
-- rigid body for the polygon
polyBody = nil
timer = 0
local size = 16
tex = image(size,size)
setContext(tex)
stroke(0, 255, 80, 255)
strokeWidth(5)
noFill()
rectMode(CENTER)
rect(size/2,size/2,size,size)
setContext()
polyMesh.texture = tex
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
debugDraw:addBody(body)
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[1]
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[1].x, verts[1].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
local tris = triangulate(verts)
if tris then
local uvs = {}
for i = 1,#tris do
local v = tris[i]
table.insert(uvs, vec2(v.x/tex.width, v.y / tex.height))
end
polyMesh.vertices = tris
polyMesh.texCoords = uvs
end
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
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()
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)
line(0,0,body.radius-3,0)
strokeWidth(2.5)
ellipse(0,0,body.radius*2)
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
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, 2643)
elseif contact.state == MOVING then
self.contacts[contact.id] = contact
elseif contact.state == ENDED then
self.contacts[contact.id] = nil
end
end
Neither
tex = image("Cargo Bot: Crate Blue 2")
nor
tex = readImage("Cargo Bot: Crate Blue 2")
is working in my nor in John’s Example with assigning it later on to the mesh.texture
What a shame, so close, so easy, but not working for me.
Images only repeat if they have power of 2 dimensions (i.e. 2, 4, 8, 16, 32, 64). I’m assuming “Cargo Bot: Crate Blue 2” doesn’t have power of 2 dimensions. What you can do is re-render it to a new image using setContext() at the closest power of 2 dimensions and then it should work.
John, the Crate 2 Image I just used for demonstration. But I didn’t read carefully. I thought square dimensions are sufficient and didn’t understand the power of 2 rule. Yes, of course I can create e.g. 64x64 tiles and use them for the hill pattern. Thanks a lot again!
Albrecht
.@aciolino I changed back to the simple code formatting. Kind of annoying consider # is pretty common in lua.
looks like anything with a hashmark expands to an tag.
John, I now tried your example and it works well after I removed the few html a tags. Impressive real time rendering and physics example.
Also it worked well in my environment, although I have a more complex polygon which extends over some screen sizes and I triangulated the polygon by myself (btw. I’ve made screenshots for illustration, but honestly I don’t know, where Codea have stored them). My only problem is now, that it works just with your image example (with setcontext() …) but not the way it should be when using e.g.
...
self.m = mesh()
...
self.w, self.h = spriteSize("Cargo Bot:Crate Blue 2")
self.m.texture = "Cargo Bot:Crate Blue 2" -- <- not working properly when drawing mesh
...
for i,v in ipairs( self.verts) do
table.insert(self.texcoords, vec2(v.x/self.w, v.y/ self.h)
end
self.m.vertices = self.verts
self.m.texCoords = self.texcoords
...
What can I do to make it work with externally created png Files?
Thanks in advance!
Albrecht
John, Wow - what a comprehensive comments. i’ll Need some time to work it out. I think I already have the backbone for the bodies. The xy size dependent texCoords are probably the golden hint.
Thanks a Lot!
Albrecht
i am using @john’s mesh polygon fill routine to simulate a lake.
I define a polygon and then tile it with a water image (64*64). This all works fine.
I then thought i should be able to use the ripple shader from the codea examples to give a better ‘watery’ feel to the lake. This does not work, i just get a black filled polygon!
I am not very expert in these things, so maybe this is stupid thing to try-if anyone has any ideas?
If your lake is greater than 2048 pixels, you have exceeded limits and will get black.
I’ve done a series of tutorials on 3D tiling, see here.
http://coolcodea.wordpress.com/2013/05/25/64-3d-tiling-images-across-an-object/
http://coolcodea.wordpress.com/2013/05/27/665-3d-terrain-hills/
And many more
@Ignatz let me thank you for your wonderful tutorials-i really learnt a lot from them. The tilershader is a very elegant solution.
I choose the @John approach as i thought i could just add on top of it the ripple shader.
I have tried a variety of different size images and different size meshes, but don’t find any combination that works? It seems that as soon as the shader is active the polygon filling becomes black. Even the falling physics circles are no longer visible?
So does anyone know how to tile an arbitrary polygon and then add a shader effect, like ripple or swirl, to the full area of the polygon (or simultaneously to each unit of the tiling)?