Seamless texture to a complex polygon

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. :frowning:

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! :slight_smile:
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)?