Class to Explode craft models

This class takes a craft model and creates a new craft entity which has as its children models of all the triangles which constituent the original model. If the explode parameter is set true, each triangle is given a random velocity and rotation and its trajectory will evolve following the specified gravity.

As one might expect it is quite cpu intensive, but could be ok if you want to explode small models. If anyone has idea to improve the speed let me know!

-- craft model disintegration
-- PiInTheSky, 15 july 2020

 --    displayMode (FULLSCREEN_NO_BUTTONS)
    displayMode (OVERLAY)
--   displayMode (FULLSCREEN)

function setup()

    fps=120

    parameter.watch("fpsstr")
    parameter.watch("memory")

    parameter.integer("tdirection",-1,1,1)
    parameter.number("gScale",0.,1.,0.1)

-- Create a new craft scene    
    scene = craft.scene()   
    scene.farPlane=10000
    scene.nearPlane=0

--orbital viewer
    orv=scene.camera:add(OrbitViewer, vec3(0,0,0), 400, 0, 2000)
    
--model 
    myModel=scene:entity()
    myModel.active=true
    myModel.model = craft.model(asset.builtin.Primitives.Sphere)      
--    myModel.model = craft.model(asset.builtin.Primitives.Monkey)      
    myModel.scale=vec3(1,1,1)*20
    myModel.position=vec3(50,100,0)  
    myModel.eulerAngles=vec3(0,180,0)

    myModelTriangles=makeTriangles(myModel)
    myModelTriangles.ent.scale=vec3(1,1,1)*20
    myModelTriangles.ent.position=vec3(-50,100,0)
    myModelTriangles.ent.active=true
    
    parameter.boolean("explode",false, function(s) myModelTriangles.explode=s end) 
end

makeTriangles=class()

function makeTriangles:init(originalEnt)
    self.originalEnt=originalEnt
    self.allTs={}
    self.t=0
    self.endt=5
    self.explode=false
    self.sampling=1
    self.destroyAtEnd=false
    
    local originalModel=self.originalEnt.model
    local vCount=originalModel.vertexCount
    local iCount=originalModel.indexCount
    self.nTriangles=iCount/(3*self.sampling)
    
    print("vertex Count ",vCount)
    print("index Count ",iCount)
    print("no of triangles ",self.nTriangles/self.sampling)
    
--parent entity of all the children triangles
    self.ent=scene:entity()
    self.ent.active=true
    self.ent.scale=self.originalEnt.scale
    self.ent.position=self.originalEnt.position
    self.ent.eulerAngles=self.originalEnt.eulerAngles
    
--loop over indices to make the triangle models
    local i
    local step=3*self.sampling
    for i=1, iCount, step do
        self:createTriangle(i)     
    end

    return self
end
    
function makeTriangles:createTriangle(i)    
    local originalModel=self.originalEnt.model
    
    local i1=originalModel.indices[i]
    local i2=originalModel.indices[i+1]
    local i3=originalModel.indices[i+2]
      
    local p1=originalModel.positions[i1]
    local p2=originalModel.positions[i2]
    local p3=originalModel.positions[i3]
    
    local n1=originalModel.normals[i1]
    local n2=originalModel.normals[i2]
    local n3=originalModel.normals[i3]

--    local c1=originalModel.colors[i1]
--    local c2=originalModel.colors[i2]
--    local c3=originalModel.colors[i3]
--    print(c1,c2,c3)
    
--centre of mass position        
    local pcom=(p1+p2+p3)/3

    p1=p1-pcom
    p2=p2-pcom
    p3=p3-pcom
       
    local tri=scene:entity()
    tri.parent=self.ent
    tri.active=true
    tri.model=craft.model()
    tri.model.indices ={1,2,3, 3,2,1}  
    tri.model.positions= {p1,p2,p3,  p1,p2,p3}
    tri.model.normals= {n1,n2,n3,  n1,n2,n3}    
--  tri.model.colors={c1,c2,c3, c1,c2,c3}
    
    tri.material = craft.material(asset.builtin.Materials.Standard)
    tri.position=pcom
    tri.material.diffuse=color(238, 59, 185)  
    tri.velocity=vec3(math.random()-0.5, math.random()-0.5, math.random()-0.5)*2
    tri.deltaQuatScale=5
    tri.deltaQuat=quat.eulerAngles( (math.random()-0.5)*tri.deltaQuatScale,
                                    (math.random()-0.5)*tri.deltaQuatScale,
                                    (math.random()-0.5)*tri.deltaQuatScale )    
    table.insert(self.allTs,tri)
end

function makeTriangles:update()
    local g=vec3(0,-10,0)*gScale
    local dt=DeltaTime*tdirection
    
    if self.explode then
        self.t=self.t+dt
        local nTriangles=self.nTriangles
        local i      
        for i =1, nTriangles do
            local thisT=self.allTs[i]
            local newv=thisT.velocity+g*dt
            thisT.velocity=newv
            thisT.position=thisT.position+newv*dt
            thisT.rotation=thisT.rotation*thisT.deltaQuat        
        end
    
        if self.t>self.endt then
            print("stop exploding")
            self.explode=false
            if self.destroyAtEnd then 
                for i =1, nTriangles do
                    self.allTs[i]:destroy()
                end
                collectgarbage()
            end
        end
    end
end

function update(dt)    
    memory = string.format("%.3f Mb",collectgarbage("count")/1024)
    
    myModelTriangles:update()   
    scene:update(dt)
end

function draw()
    update(DeltaTime)  
    scene:draw()
    
--frames per second
    myfps(WIDTH*0.9,HEIGHT*0.9)	   
end

function touched(touch)
    orv:touched(touch)
end

function myfps(x,y)
    fps=fps*0.9+0.1/DeltaTime
    fpsstr=string.format("%.0f",fps)
    text("fps="..fpsstr,x,y)
end

@piinthesky That’s a great demo. One thing, you should include the line below in any projects that use OrbitViewer. If you don’t, then it shows an error because Cameras isn’t checked as a dependency. New users might not know to check the Cameras dependency and not know what’s wrong. If Cameras isn’t checked, then it prints the message telling them to check it.

assert(OrbitViewer, "Please check Cameras as a dependency")  

I wonder if it is possible to do this without creating new entities.

It should be possible to modify the standard craft shader to include an extra position and velocity buffer which is applied to each triangle. That’s what I did in my explosion class many, many years ago. But it would involve modifying the default shader.

@piinthesky - excellent demo, have you chased up @LoopSpace ’s demo, could give you some pointers.

One possible issue, don’t know if this is specific to one instance - I set the t-direction to 0, scale to 0.63 and zoomed in on the pink sphere - then exploded the sphere. It looked like there were three exploded spheres, a large scale spherical one and two small scale circular ones (possibly at the poles - need to zoom in). Could be a feature of smaller triangles at the poles.

@piinthesky - increased gScale to 1.0 and t-direction to 0. Zoomed in then exploded. Image attached to show the main sphere fractions and the central circular smaller sections.

Looks like you may have three sets of spheres here.

@piinthesky - looks like this might be an issue with the Craft sphere model. Had trouble with texturing that before now - could be the cause as it looks like you have 3 spheres present. The inbuilt monkey obj model you included in your code doesn’t show the same issue. @John - is this possible.

@Bri_G i guess it is probably an issue with that model near the poles. @John said he would fix the icosphere but didn’t get around to it yet.

Yes, i was inspired by the @LoopSpace disintegration shader. I didn’t manage to make his shader work within craft, so i tried my brute force method. I did discuss with @john in some thread whether it could be done via Shader, but it did not seem straightforward.

My way, gives a big hit on the fps, but in principle one could make the triangles physics bodies and they could interact with each and other physics bodies in the scene.

@piinthesky I thought I’d try my hand at the explode code and this is what I came up with. On my iPad Air 3, your setup() run time takes 3 seconds to complete with 6,912 indices and 2,304 triangles. I got my setup() time down to .16 seconds with 15,360 indices and 5,120 triangles. In the createSphere line of code, the 4 creates a level 4 icosphere. If that is changes to 5 for a level 5 icosphere, the number of indices increases to 61,440 and triangles to 20,480. That setup() run time takes .88 seconds. Of course when the explode starts for that, the FPS drops tp 7.

displayMode(STANDARD)

function setup() 
    sc=require("socket")
    st=sc:gettime()
    fill(255)
    tab={}
    assert(OrbitViewer, "Please include Cameras as a dependency")        
    scene = craft.scene()
    scene.sun.rotation=quat.eulerAngles(30,180,0)
    v=scene.camera:add(OrbitViewer,vec3(0,0,0),200,0,1000)
    createSphere(vec3(0,20,100),10,4)    
    createTable()
    en=sc:gettime()
    print("setup time ",en-st)
end

function draw()
    update(DeltaTime)
    scene:draw()
    if not explode then
        text("indices "..s.model.indexCount,WIDTH/2,HEIGHT-50)
        text("nbr of triangles "..#tab,WIDTH/2,HEIGHT-75)
        text("Tap screen to explode",WIDTH/2,HEIGHT-200)
    end
end

function update(dt)
    scene:update(dt)
    if explode then
        for a,b in pairs(tab) do
            b.rot=b.rot+vec3(math.random(),math.random(),math.random())*5
            b.rotation=quat.eulerAngles(b.rot.x,b.rot.y,b.rot.z)
            b.position=b.position+b.vel
        end
    end
end

function touched(t)
    if t.state==BEGAN then
        explode=true
        displayMode(FULLSCREEN)
    end
end

function createSphere(pos,sz,lv)
    s=scene:entity()
    s.position=pos
    s.model = craft.model.icosphere(sz,lv)
    s.material = craft.material(asset.builtin.Materials.Standard)
    s.material.diffuse=color(255, 255, 0)
end

function createTable()
    local ind=s.model.indices
    local pos=s.model.positions
    for z=1,s.model.indexCount,3 do
        local p1=pos[ind[z]]
        local p2=pos[ind[z+1]]
        local p3=pos[ind[z+2]]
        local avg=(p1+p2+p3)/3    
        local pos={p1-avg,p2-avg,p3-avg}
        table.insert(tab,createTriangles(pos,avg))
    end    
end

function createTriangles(pos1,pos2)  
    local r=scene:entity()
    r.position=pos2
    r.model = craft.model()
    r.model.positions=pos1
    r.model.indices={1,2,3,3,2,1}
    r.model.colors={color(255),color(255),color(255)}
    r.material = craft.material(asset.builtin.Materials.Basic)  
    r.material.diffuse=color(0, 189, 255)
    r.rot=vec3(math.random(360),math.random(360),math.random(360))
    r.vel=pos2*math.random()*.05
    return r
end

@dave1707 - very interesting, again another very neat demo. Since you used the built in craft icosphere model looks like following code

craft.model(asset.builtin.Primitives.Sphere)

introduces them, or they are generated in the code - possibly down to the triangulation code.

Trying this out on a few models I play around with.
Thanks for this.

@dave1707 good setup time improvement-thanks. I notice that not setting up the normals improves the fps a lot, but it is not so pretty. By the way, when i first wrote it, i thought not to save the triangles in a table and only loop over the children, that turned out to be very, very slow, so i used a dedicated table.

@Bri_G yes the icosphere seems more ‘reasonable’ than the primitive sphere.

@piinthesky I was trying to do the normals so the second sphere would look like the original (except for the color), but I couldn’t get it to work right. I figured it didn’t matter that much so I left them out. I was mostly after the speed increase. Maybe later I’ll work on it more.

When i try the explode code on the built in Kenny models, only some parts of the model are copied over to triangles-any ideas why that might be?

@piinthesky - many model builders have tools to simplify, which generally means reduce triangles for complex models, to help reduce display/speed and load on graphics processor. Packages used by Codea, where mutiple models could be used, may well have been ‘simplified’.

@Bri_G hmmm, but no matter the process to make the model, wouldn’t the final product always be made up from triangles. @John do you know what is going on?

@Bri_G @piinthesky I wonder if those are made of multiple objects and our code is only getting the last one of multiple and showing the triangles for it.

@dave1707 yes maybe, and each of the sub-objects has a different colour. If so, I don’t know how to get the vertex info for all the sub-objects.

@John i have difficulty accessing all the triangles of the poly mesh in many .obj models. I have the impression that not all the indices/vertices info makes it into the corresponding arrays of the craft model-could that be possible?

@piinthesky What you could be experiencing is that some models might be made up of several different submeshes. This part of the engine wasn’t exposed to simplify the API since I didn’t anticipate anyone trying to load a model and access submeshes directly. So if you load something that has 3 submeshes you only get access to the vertices and triangles in the first one.

Generally you only get this when you load a model that consists of several different materials (which are then split into the separate submesh structures).

As a work around you could export your model as several parts based on material and then attach them all to the same parent entity which would give you the same model but access to all the geometry.

What I might do as a fix for this particular issue is add model.submeshIndex and model.submeshCount. By default it is set to 1, and if you set it to another index then all model related methods will work with the submesh at that index.

@john ok, i think it would be good to have access to all the submeshes in a future release. Also it would make it possible to change the vertex colours in the code.
For now, i will try as you suggest by splitting the model into sub models.