outline triangulate()'ed meshes with a line()

the thing is, I want to make my life easier and just use triangulate() on a set of vertices for a polygon. then I want to give the whole shape an outline. how can I filter out the relevant polygons to use this in a loop and connect them with line()?

EDIT: I know I could have a table set of vertices for use with line() and then another table with the triangulated set of verts for drawing the mesh, but this just increases the memory usage…

Also another question, how could I automate the packing of the verts into the texture atlas?

The way I do it is set my shape say an irregular hexagon and place 6 vertices around the screen then use line through this table then triangulate the same table and draw the mesh from the points, assuming that’s what you wanted?

I’m not sure if its what you want, but you could sort by angle, then calculate the centre point of your vertices and connect it to two adjacent vertices to create a triangle, and repeat all the way round. The line around the outside is formed by the sorted list of vertices.

Of course this assumes all the vertices are on the edge of the polygon…

@Luatee won’t it be too heavy on memory if i would triangulate on each draw() loop?

Depends what you do, I wouldn’t triangulate it in draw. If it needs to be updated regularly then make a function and you can create a sort of timed stack which is my way of creating and executing functions at an interval, others might not like this method. This way you can triangulate the mesh every 0.2 seconds or so making it look smoother but if the polygon is complex then you could experience jolts in frames because of memory usage, otherwise if the mesh polygon is only set once then create the vertices in setup and triangulate them in setup, this way you can create two variables of the two different vertices and whenever the non triangulated vertices are updated then triangulate them and set your old variable to this.

As an example here’s a terrain class I made which updates using this method:

function Terrain:BodyChanged(verts)
    local bool = false
    if #verts ~= #self.verts then
        bool = true
        return bool
    end
    for i=1,#verts do
        local vc = verts[i]
        local nc = self.verts[i]
        if vc ~= nc then
            bool = true
            
        end
    end
    return bool
end

function Terrain:draw()
    if ElapsedTime > self.oe+0.2 and 1/DeltaTime > 30 then
        self.oe = ElapsedTime
        if self:BodyChanged(self.floor.points) then
            self.floor:destroy()
            self.floor = physics.body(POLYGON,unpack(self.verts))
            self.floor.type = STATIC
            self.oldverts = self.verts
            tween.delay(0.1,function()
                self.m.vertices = triangulate(self.verts)
            end)
        end 
    end
    self.m:draw() 
    local sv = self.verts
    for i=1,#sv do
        sprite(txture,sv[i].x,sv[i].y,5,5)
    end
end

The function BodyChanged checks all the current vertices against the stored vertices (self.verts) and if they are different then it returns true otherwise false, this saves on memory as you aren’t executing the triangulate function every frame, it can have a negative effect with complex polygons.

As you can see in the draw loop I have used a timer using ElapsedTime and adding 0.2 seconds on to it, this is different from a ‘timed’ stack which looks like this:

function tstack()
 tween.delay(0.2,function() print("execute") tstack() end)
end

The recreating of the physics body in the draw loop takes up a lot of performance but that doesn’t matter too much as I assume you’re not using the physics engine. Also instead of creating a triangulated variable in this code it’s better to just triangulate it when you need to pass the stored vertices (self.verts) to the mesh.

Here’s an example of this


--# Main
-- destructable

-- Use this function to perform your initial setup
function setup()
    
    txture = readImage("Documents:ExampleCircle",5,5)
    ter = Terrain(vec2(WIDTH/2,HEIGHT/4),vec2(WIDTH,HEIGHT/2))
    circ = {}
    circn = 0
    holding = nil
end

function collide(c)
    local cp = circ.position
    if c.state == BEGAN and c.normalImpulse > 2 then
        for k,v in pairs(ter:getTable()) do
            for a,b in pairs(circ) do 
            if v:dist(b.position) < 50 then 
                ter:setVert(k, v + (v-b.position):normalize()*(50-v:dist(b.position)))
                ter:setVert(k, v + (c.normal*-1)*c.normalImpulse*(3-v:dist(b.position)/19))
            end
            end
        end
    end
end

function touched(t)
    for a,b in pairs(circ) do
    local cp = b.position
    if vec2(t.x,t.y):dist(cp) < 40 and t.state == BEGAN then
        holding = {b,vec2(t.x,t.y)}
    end
    if holding and t.state == MOVING then
        holding = {b,vec2(t.x,t.y)}
    end
    if t.state == ENDED then
        holding = nil
    end
    end
    if not holding then
        ter:touched(t)
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)
    local n = math.random(20)
    if n == 15 and circn < 2 then
        circn = circn + 1
        circ[circn] = physics.body(CIRCLE,15)
        circ[circn].position = vec2(math.random(50,WIDTH-50),math.random(HEIGHT/2+50,HEIGHT-50))
    end
    for k,v in pairs(circ) do
        if holding ~= nil and holding[1] == v then
            v:applyForce(holding[2]-v.position-v.linearVelocity/4)
        end
    end
    -- Do your drawing here
    ter:draw()
    for k,v in pairs(circ) do
        sprite("Documents:circle",v.x,v.y,30,30)
    end
    pushStyle()
    fill(255, 255, 255, 255)
    text(math.floor(1/DeltaTime),100,100)
    popStyle()
end


--# Terrain
Terrain = class()

function Terrain:init(pos,size)
    self.pos = pos
    self.size = size
    local verts = {}
    local width,height = (size.x/WIDTH)*20,(size.y/HEIGHT)*20
    for x=1,width do
        table.insert(verts,vec2(pos.x-size.x/2+(size.x/width)*x,pos.y+size.y/2))
    end
    for y=1,height do
        table.insert(verts,vec2(pos.x+size.x/2,pos.y+size.y/2-(size.y/height)*y))
    end
    for x=1,width do
        table.insert(verts,vec2(pos.x+size.x/2-(size.x/width)*x,pos.y-size.y/2))
    end
    for y=1,height do
        table.insert(verts,vec2(pos.x-size.x/2,pos.y-size.y/2+(size.y/height)*y))
    end
    self.verts = verts
    self.m = mesh()
    self.m:setColors(255,255,255,255)
    self.m.vertices = triangulate(verts)
    self.floor = physics.body(POLYGON,unpack(verts))
    self.floor.type = STATIC
    
    self.bound = vec2(width,height):len()
    self.tbound = 30
    print(self.bound)
    self.oe = ElapsedTime
    self.oldverts = {}
end

function Terrain:BodyChanged(verts)
    local bool = false
    if #verts ~= #self.verts then
        bool = true
        return bool
    end
    for i=1,#verts do
        local vc = verts[i]
        local nc = self.verts[i]
        if vc ~= nc then
            bool = true
            
        end
    end
    return bool
end

function Terrain:draw()
    if ElapsedTime > self.oe+0.2 and 1/DeltaTime > 10 then
        self.oe = ElapsedTime
        if self:BodyChanged(self.floor.points) then
            self.floor:destroy()
            self.floor = physics.body(POLYGON,unpack(self.verts))
            self.floor.type = STATIC
            self.oldverts = self.verts
            tween.delay(0.1,function()
                self.m.vertices = triangulate(self.verts)
            end)
        end 
    end
    self.m:draw() 
    local sv = self.verts
    for i=1,#sv do
        sprite(txture,sv[i].x,sv[i].y,5,5)
        strokeWidth(3)
        if i > 1 then
            line(sv[i].x,sv[i].y,sv[i-1].x,sv[i-1].y)
        end 
    end
end

function Terrain:getTable()
    return self.verts 
end

function Terrain:setVert(i,v)
    self.verts[i] = v
end

function Terrain:touched(t)
    self.t = true
    if t.state == ENDED then
        self.t = false
    end
    local p,s = self.pos,self.size
    local tp = vec2(t.x,t.y)
    local sp = #self.verts
    for i=1,sp do
        local sv = self.verts[i]
        local svi
        if i > 1 then
            svi = self.verts[i-1]
        else 
            svi = self.verts[#self.verts]
        end
        if sv and sv:dist(tp) < self.bound then
            self.verts[i] = sv + (tp-sv):normalize()*0.2
        end
        if sv ~= nil then 
            if sv:dist(svi)>self.tbound*1.5 then
                local d = (sv+svi)/2
                if d.x > p.x-s.x/2-5 and d.x < p.x+s.x/2+5 and d.y > p.y-s.y/2-5 then
                    table.insert(self.verts,i,d)
                    return
                else
                    table.remove(self.verts,i)
                end
            end
        end
        for j=1,sp do
            if sv and self.verts[j] and sv:dist(self.verts[j])<self.tbound/2 and j~=i then
                table.remove(self.verts,i)
            end
        end
        if sv and sv:dist(tp) < self.tbound then 
            local dir = (sv-tp):normalize()*5+vec2(t.deltaX,t.deltaY)
            self.verts[i] = self.verts[i] + dir
        end
    end  
end