Exploding Meshes

I’ve finished a rough version of exploding meshes. This code will take a mesh (and a few other params) and make the mesh explode in either a vector impact or bloom method.

I still have a bug with my shadows; they are rotating in all 3 axes though I only want then to rotate in two.

Part 1 of 2 - the Fragment class and functions to explode


--Fragment.lua
Fragment = class()
Fragment.DEFAULT_MASS = 1
Fragment.GRAVITY=9.81        --GRAVITY constant...
Fragment.EXPLODEBLOOM = 1
Fragment.EXPLODEIMPACT = 2

--------------------------------------------------------
-- Creates a fragment object. The mesh that the fragment renders is passed
-- to this function, so a custom mesh could override a fragment of a source mesh
-- if desired.
--------------------------------------------------------
function Fragment:init(params)

    self.rotationSpeed = params.rotationSpeed or 10
    self.motionSpeed = params.motionSpeed or 100
    self.location = params.location or vec3(0,0,0)
    self.thisMesh = params.Mesh
    self.v = params.velocity or vec3(1,1,1)    -- velocity (vec3)
    self.life = params.life  or 15    -- life cycle, default 1/4 sec
    self.scale = params.scale or self.DEFAULT_SCALE    -- scale of mesh
    --every cycle shrink frag by this multiplier. Keep under 1 to shrink, > 1 to grow.
    self.scalediminish = params.scalediminish or .99
    self.mass = params.mass or Fragment.DEFAULT_MASS     -- mass

    --CALCULATE / ASSIGN some items
    self.curangle=0
    --fudging mass as # of G's in the y direction. Y is up only in this world!
    --base acceleration for this fragment
    self.acc = vec3(0, -Fragment.GRAVITY*self.mass, 0)        
    self.lifeLeft = self.life  --set up for run, this gets decremented per frame
    if params.shadows then 
        self.shadow=mesh()
        self.shadow.vertices = params.Mesh.vertices
        self.shadow:setColors(color(0,0,0,64))
    end
end

function Fragment:draw()
    if self.lifeLeft > 0 then
        self.lifeLeft = self.lifeLeft - 1
    end

    if self.lifeLeft ~= 0 then
        pushMatrix()
        local x,y,z = GetCenter(self.thisMesh)
        translate(self.location.x, self.location.y, self.location.z)
        translate(-x,-y,-z)
        rotate(self.curangle,1,1,1)    --rotation values       
        translate(-x,-y,-z)
        scale(self.scale, self.scale,self.scale)   --scale factor overall. static .99 or vec3
        self.thisMesh:draw()
        popMatrix()
        
        --shadow?     
        if self.shadow ~= nil then 
            pushMatrix()
            translate(self.location.x, 0, self.location.z)
            translate(-x,0,-z)
            rotate(90,0,1,0)
            rotate(self.curangle,1,0,1)    --rotation values       
            translate(-x,0,-z)
            scale(self.scale, self.scale,self.scale)   
            tint(0, 0, 0, 255)
            self.shadow:draw()
            noTint()
            popMatrix()
        end

        --set up for next frame
        self.curangle = self.curangle + self.rotationSpeed --change rotation angle
        self.scale = self.scale * self.scalediminish    --change fragment size
        --move this fragment. Note that 60 represents FPS.
        local curframe = self.life- self.lifeLeft
        self.location = self.location +  (self.v * (curframe/60))/self.motionSpeed  -- hack!!
        self.v = self.v +  (self.acc * (curframe/60))/self.motionSpeed        --change velocity

    end
end        

--------------------------------------------------------
-- takes a source mesh and creates all of the fragments - as seperate small meshes.
-- this is done so we can rotate, translate and scale pieces seperate that the
-- master body from whence they came.
--------------------------------------------------------
function FragmentMesh(params) 
    tri = {} tc={} triColor = {}
    pos = 0 
    local meshlist = {}
    local triangleCount = 0
    local moduloTriangle = params.moduloTriangle or 10 
    aMesh = mesh()
    aMesh=params.Mesh
    
    --explode a mesh
    for k,v in pairs(aMesh.vertices) do
        pos=pos+1
        if (pos > 2) then

            triangleCount = triangleCount + 1
            
            if triangleCount == moduloTriangle then 
                --make new copies of prev mesh 
                table.insert(tri,aMesh:vertex(k-2))
                table.insert(tri,aMesh:vertex(k-1))
                table.insert(tri, v)    -- aMesh:vertex(k) is the same thing... add the vertex
                
                if (#params.Mesh.texCoords >0) then 
                    table.insert(tc,aMesh:texCoord(k-2))
                    table.insert(tc,aMesh:texCoord(k-1))
                    table.insert(tc,aMesh:texCoord(k))    --add the texCoord
                end
                
                if (#params.Mesh.colors >0) then 
                    table.insert(triColor, GetColor(aMesh:color(k-2)))
                    table.insert(triColor, GetColor(aMesh:color(k-1)))
                    table.insert(triColor, GetColor(aMesh:color(k)))
                else
                    table.insert(triColor, color(255,0,0,255))    
                    table.insert(triColor, color(0,0,255,255))    
                    table.insert(triColor, color(0,255,0,255))    
                end
                
                exp = mesh()
                exp.vertices = tri
                exp.colors = triColor
                exp.texture = params.Mesh.texture    
                exp.texCoords = tc
                --
                --create attributes of this mesh for later use
                life = params.life or 60*3   --3 secs to live 
                sc = params.scale or 1
                sd = params.scalediminish         ---needed to make a copy to pass it?!
                local location = params.location
                local mass=params.mass
                local hasShadows =  params.shadows

                if (params.impactVector == nil) then
                    _, sz = GetBoundingBox(exp)
                    if sz.x <1 then sz.x=sz.x*2 end
                    if sz.y <1 then sz.y=sz.y*2 end
                    if sz.z <1 then sz.z=sz.z*2 end
                    velocity = vec3(
                        math.random(-sz.x,sz.x),
                        math.random(sz.y,sz.y),
                        math.random(-sz.z,sz.z))
                else
                    if (params.explosiontype == Fragment.EXPLODEIMPACT) then
                        velocity = vec3(
                        (math.random(-100,100))/100+params.impactVector.x,
                        (math.random(-100,100))/100+params.impactVector.y,
                        (math.random(-100,100))/100+params.impactVector.z)
                    else
                        velocity = vec3(
                        (math.random(-100,100)/100)*params.impactVector.x,
                        (math.random(-20,100) /100)*params.impactVector.y,
                        (math.random(-100,100)/100)*params.impactVector.z)
                    end 
                end
      --print(location)
                local param = {life=life, velocity=velocity, mass=mass, 
                    scale=sc, scalediminish = sd, 
                    location=location, rotationSpeed = 15,
                    motionSpeed = 5, Mesh = exp, shadows = hasShadows}

                f = Fragment(param)
                
                table.insert(meshlist, f)
                tri = {} tc={} triColor = {}
                pos = 0
                triangleCount = 0
            end
        end
    end  
    return meshlist
end


part 2 of for fragment class


--------------------------------------------------------
--public helper functions
--------------------------------------------------------

--change a mesh color entry into a standard color entry
function GetColor(c)
    return color(c.x,c.y,c.z,c.a)
end

--calculates the bounding box of an object. Returns the low and high values.
function GetBoundingBox(v)
    --determine bounding box.
    vlow = vec3(0,0,0)
    vhigh = vec3(0,0,0)
    for k=1, #v.vertices do
        if v:vertex(k).x < vlow.x then vlow.x = v:vertex(k).x end
        if v:vertex(k).y < vlow.y then vlow.y = v:vertex(k).y end
        if v:vertex(k).z < vlow.z then vlow.z = v:vertex(k).z end
                
        if v:vertex(k).x > vhigh.x then vhigh.x = v:vertex(k).x end
        if v:vertex(k).y > vhigh.y then vhigh.y = v:vertex(k).y end
        if v:vertex(k).z > vhigh.z then vhigh.z = v:vertex(k).z end
    end
    
    return vlow, vhigh
end

--finds the ceter of a 3D mesh. needed for central rotation.
function GetCenter(aMesh)
    local x=0 
    local y=0
    local z=0
    for k,v in pairs(aMesh.vertices) do
        x=x+v.x
        y=y+v.y
        z=z+v.z
    end
    x = x / #aMesh.vertices
    y = y / #aMesh.vertices
    z = z / #aMesh.vertices
    return x,y,z
end

Sample use. the Cubes class is the standard sample from the 3D Cube project; it was used to quickly “steal” a mesh for testing.


-- Explode

-- Use this function to perform your initial setup
function setup()
    
    Cubes:init()   --for Frame of reference and quick mesh use
    explode = {}
    explode2 = {}
    explode3={}
    
--let camera float around.
sx=.1
szz=.1
cx=-75
cz=75
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(173, 173, 184, 255)
    if math.abs(cx) > 150 then sx=-sx end
    if math.abs(cz) > 150 then szz=-szz end
    cx = cx + sx
    cz = cz + szz

    perspective(40, WIDTH/HEIGHT,0.1, 500)   
    --camera(5,5,5, 0,0,0, 0,1,0)
    camera(cx,30,cz, 20,10,0, 0,1,0)

      -- Do your drawing here
    DrawGround()

---THE MAGIC HAPPENS HERE
    if (#explode==0) then 
        --explode a mesh
         local params = { 
            Mesh = Cubes.mw, explosiontype = Fragment.EXPLODEBLOOM,  
            moduloTriangle =1, life = 810, impactVector =vec3(10,20,10),
            mass=.2, scale=4, scalediminish=1,  location=vec3(20,5,0),
            --motionSpeed = 50000
        }
        explode = FragmentMesh(params)
---THE MAGIC HAPPENS HERE
    end
    
---HACK!!!!!! Make a few extra explosions for fun. ex2 and 3 are only for demoing 
    if (#explode2==0) then 
         local params2 = { 
            Mesh = Cubes.ms, explosiontype = Fragment.EXPLODEBLOOM,  
            moduloTriangle =1, life = 120, impactVector =vec3(2,20,0),
            mass=1, scale=2, scalediminish=.99,  location=vec3(0,20,0),
            shadows=true
        }
        explode2 = FragmentMesh(params2)
        end
        
        if (#explode3==0) then 
        local params3 = { 
            Mesh = Cubes.mg, explosiontype = Fragment.EXPLODEIMPACT,  
            moduloTriangle =1, life = 120, impactVector =vec3(-8,10,-8),
            mass=1, scale=1, scalediminish=.99, location=vec3(20,20,0),
            shadows=false
        }
        explode3 = FragmentMesh(params3)
      end
    
    
    --give me a frame of reference
    if (#explode == 0) then 
        Cubes.mw:draw()
    end
    ---This could be in a table and looped.
    for ek,ev in pairs(explode) do 
        ev:draw()
        if (ev.lifeLeft < 1)  or (ev.location.y < -1) then table.remove(explode, ek) end    
    end
    for ek,ev in pairs(explode2) do 
        ev:draw()
        if (ev.lifeLeft < 1) or (ev.location.y < -1)then table.remove(explode2, ek) end        
    end

    for ek,ev in pairs(explode3) do 
        ev:draw()
        if (ev.lifeLeft < 1) or (ev.location.y < -1)then table.remove(explode3, ek) end        
    end
    
end
function DrawGround()
    pushMatrix()
    rotate(90,1,0,0)
    scale(300,300,0)
    sprite("Dropbox:GroundTile1")
    popMatrix()
end

Don’t know how to embed videos yet, so:
Using base cube models
http://www.youtube.com/watch?v=OU_3EHBofkg

Using X3d and PLY loaded models. Long delay from 12-19 secods as largest model is prepared (building) for detonation.
http://www.youtube.com/watch?v=d0EPR4890MA

To embed the video you just have to do send by email in youtube, then copy the adress in the mail, then directly paste it in the post:“http://www.youtube.com/watch?v=OU_3EHBofkg” then it is automatically recognized by the forum as a video and transformed into this: http://www.youtube.com/watch?v=OU_3EHBofkg
The adress appeared in clear above because i put it inside “”

Thanks @jmv38.

I like you buildings. How did you do the shape and the textures?

I took them from 3D warehouse in SketchUp. They are PLY or X3D files that I converted from Collada format. The bldg is 35 E Wacker in Chicago, IL, where I live.