Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

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

Comments

  • dave1707dave1707 Mod
    Posts: 9,295

    @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")  
    
  • Posts: 509

    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.

  • edited July 2020 Posts: 2,277

    @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.

  • edited July 2020 Posts: 2,277

    @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.

  • Posts: 2,277

    @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.

  • edited July 2020 Posts: 643

    @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.

  • dave1707dave1707 Mod
    edited July 2020 Posts: 9,295

    @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
    
  • edited August 2020 Posts: 2,277

    @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.

  • edited July 2020 Posts: 643

    @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.

  • dave1707dave1707 Mod
    edited July 2020 Posts: 9,295

    @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.

  • Posts: 643

    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?

  • edited July 2020 Posts: 2,277
    @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'.
  • Posts: 643

    @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?

  • dave1707dave1707 Mod
    Posts: 9,295

    @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.

  • Posts: 643

    @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.

  • Posts: 643

    @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?

  • JohnJohn Admin Mod
    Posts: 643

    @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.

  • JohnJohn Admin Mod
    Posts: 643

    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.

  • Posts: 643

    @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.

  • Posts: 643

    It can be dangerous piloting a ROV through the KM3NeT telescope!

    I implemented the ROV explosion in my KM3NeT app when the ROV collides with an optical module.

  • JohnJohn Admin Mod
    Posts: 643

    @piinthesky Nice!

  • edited May 29 Posts: 1,195

    @dave1707 , I tried to adapt your code into a single function that could make any Craft model explodable.

    It works great on the sphere and the monkey, but not on the warship — you can see all three tested in the code.

    I think it’s because I can’t get the vertices of the submeshes.

    @John, above you mentioned intending to “add model.submeshIndex and model.submeshCount,” and I see that you did it with submeshCount but apparently not submeshIndex`. Did you provide a different way to get at submeshes, or are they still beyond reach?

  • Posts: 2,277

    @UberGoober - all explode fine for me. Just modified the initial code a bit.


    scene = craft.scene() scene.sun.rotation=quat.eulerAngles(30,180,0) v=scene.camera:add(OrbitViewer,vec3(0,3,10),25, -80, 2000) craftEntity=scene:entity() craftEntity.model = craft.model(asset.builtin.Primitives.Sphere) -- craftEntity.model = craft.model(asset.builtin.Primitives.Monkey) -- craftEntity.model = craft.model(asset.builtin.Watercraft.watercraftPack_003_obj) craftEntity = explodeyCraft(craftEntity, color(172, 106, 59)) craftEntity.eulerAngles = vec3(20, 180, -0)
  • edited May 29 Posts: 1,195

    @Bri_G if you comment out the part of explode() that makes the shards move and rotate it’s clear that fewer than half of the shapes are being converted to shards.

  • Posts: 2,277
    @UberGoober - hmmmm, do you think that is because the model has been optimised to remove duplicate vertices? If that's the case you won't be able to affect all triangles.
  • Posts: 1,195
    @Bri_G why don’t you think the other data is in submeshes, which not only seems to me the most likely scenario, but is also the one suggested by @John as the source of this kind of problem in this very thread?
  • Posts: 2,277
    @UberGoober - my bad, I need to re-read some of these threads.
  • Posts: 1,195

    @John, above you made this comment regarding trying to get submesh vertices:

    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.

    ...can you give some demonstration of that? I’d love to have a way to access the submeshes’ geometry, even a convoluted way.

  • Posts: 643

    @UberGoober what @John suggested was from within Blender to export a single model for each separate material of the original model and then in Codea to group them back together. This way one could easily manipulate each material/model. This would need some expertise on the Blender side.

  • Posts: 1,195

    @piinthesky thanks for clearing that up! I was genuinely baffled.

  • dave1707dave1707 Mod
    Posts: 9,295

    @UberGoober Comment out the below line in your explode function of the .zip file and try the explode. Then go thru and try every model in the Watercraft pack. You’ll notice that each model only has parts of the full image. It’s like each image is made up of multiple parts from other models. Looking thru the .obj file I haven’t seen where the model is getting all of the info it needs. It’s like the .obj file doesn’t have all the vertices and it’s getting other information from somewhere else.

                --b.position=b.position+b.vel
    
  • Posts: 1,195

    @dave1707, I know all that, that's exactly why I'm asking this question in the first place.

    I don't know why you don't believe me that it's all about submeshes, @Bri_G didn't either until I pointed out to him that @John addresses this issue in one of the other threads about exploding meshes, and he implies it's all about submeshes.

    You can get the number of submeshes out of Craft, you can apply materials individually to submeshes with Craft, but you don't seem to be able to get the actual vertex information on submeshes out of Craft--in theory, if you could apply a shader to each individual submesh, and there were some variable inside the shader that you could watch, you could harvest the vertex information on the fly.

  • Posts: 643

    @UberGoober seems we will just have to beg nicely to @John to make this information accessible.

  • dave1707dave1707 Mod
    Posts: 9,295

    @UberGoober What is your definition of a subMesh. The “m.submeshCount”gives you the number of color values used in the mtl file. In the robot example, there are 4 colors that can be changed and the submeshCount will give you 4. In the watercraft 001 run below, it gives a count of 7 for submeshCount which matches the number of color groups in the .mtl file for watercraft 001.

    Run this code for each of the watercraft models. All of the triangles defined in the .obj file will be colored red. The question is, where is the uncolored part coming from. Looking thru the .obj file, I don’t see anything that tells me.

    Some runs will take longer to display because of the number of triangles.

    I don’t have all the answers yet. I’m just going by what I’m seeing looking at the images and a dump of the .obj and .mtl files.

    viewer.mode=STANDARD
    
    function setup()
        assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
        scene=craft.scene()
        v=scene.camera:add(OrbitViewer, vec3(0,0,0), 40, 0, 2000)
        m=scene:entity()
    
        m.model = craft.model(asset.builtin.Watercraft.watercraftPack_001_obj)
    
        m.material = craft.material(asset.builtin.Materials.Specular)
        m.material.diffuse=color(255, 255, 255, 255)
        m.position=vec3(0,0,0)    
        print("positions  ",#m.model.positions)
        print("index  ",m.model.indexCount)
        print("vertex  ",m.model.vertexCount) 
        print("=====  Working  =====")       
        for z=1,m.model.indexCount//3 do
            create(z)        
        end
        print("=====  Done  =====")
    end
    
    function update(dt)
        scene:update(dt)
    end
    
    function draw()
        update(DeltaTime)
        scene:draw() 
    end
    
    function create(point)
        tab={}
        for z=(point-1)*3+1,(point-1)*3+3 do
            i=m.model.indices[z]  
            p=m.model.positions[i]
            table.insert(tab,p)
        end
        local r=scene:entity()
        r.model=craft.model()
        r.model.positions={tab[1],tab[2],tab[3]}      
        r.model.indices={1,2,3}
        r.model.colors={color(255),color(255),color(255)}
        r.model.uvs={vec2(1,0),vec2(0,0),vec2(0,1)}
        r.material = craft.material(asset.builtin.Materials.Basic)
        r.material.diffuse=color(255,0,0)
    end
    
  • edited June 9 Posts: 643

    @dave1707 if m.positions does not give you all the vertices you will not see all of them. I think it will only give you the positions of the first mesh.

    I wonder if by bypassing craft and using @yojimbo2000 objloader one could access all the vertices?

  • edited June 9 Posts: 1,195

    @dave1707 I'm not sure you're correct in defining submeshes as simply color values. Submeshes are, I believe, separate geometries, and that's the reason you can change their color values independently. They're not, I don't think, just designated color areas on a single mesh. Plus the word "mesh" in "submesh" is kind of a clue, to me, that they're meshes.

    In my experience with trying to apply shaders to Craft models, if I only apply the shader to one submesh I get results like the watercraft ship--it only applies to one piece.

    In the end it doesn't really matter because the goal is the same--I want to be able to apply an effect to an entire model, not just part of it--and no matter what the proper definition of submeshes is, they seem to be the things that hold the key to this.

  • Posts: 1,195

    @piinthesky I guess there's only one way to find out...

    OTOH if you were able to decipher the yojimbo code you might find out how to access sub meshes directly on the Craft OBJ models also...

  • Posts: 643

    yes, that is what i meant.
    the obj files are obj files-it's just whether you try to read via craft or via objloader.
    There is also the pseudoMesh code from @LoopSpace. Unfortunately i don't have the time at the moment to play around with all this.

  • Posts: 1,195

    I have played with the PseudoMesh code a little bit, I don't think it addresses sub meshes.

  • dave1707dave1707 Mod
    edited June 9 Posts: 9,295

    @UberGoober @piinthesky So you’re saying that the .obj file is only one of the submeshes, but Codea is able to somehow read all of the submeshes given the name of the one .obj file.

    PS. Where is the yojimbo2000 code you’re talking about.

  • Posts: 643

    i guess the obj files are complete but m.positions only gives the positions of the first mesh.

    https://codea.io/talk/discussion/6955/3d-obj-model-importer-for-potential-future-inclusion-with-codea-now-with-wireframe-mode

  • dave1707dave1707 Mod
    Posts: 9,295

    @piinthesky Thanks for the link. When I dump the watercraft .obj and .mtl files, I’m seeing the positions only for the triangles I show in red with my above program. The rest of the image is shown, but I’m not sure where Codea is getting the information to create the rest of the image since I don’t see anything in the .obj file to point it to that info.

  • Posts: 643

    how do you do the dump, using m.positions or some other way? via m.positions you may not see everything.

  • dave1707dave1707 Mod
    edited June 10 Posts: 9,295

    @piinthesky I thru this together to dump the .obj or .mtl files. It’s not great but it did enough for what I wanted.

    PS. If you select to sort the file, blank lines will be removed.

    Change the first line in setup to read different files, .obj or .mtl .

    viewer.mode=STANDARD
    
    function setup()
        str=readText(asset.builtin.Watercraft.watercraftPack_001_obj)   -- obj or mtl
        print("file length  "..#str.."  bytes")
        parameter.boolean("sort_file",false,sortFile)
        textMode(CORNER)
        fill(255)
        tab={}
        cnt=0
        for a in string.gmatch(str,".-%\n") do
            table.insert(tab,a)
        end 
        print("# of lines",#tab) 
        pos,dy,yy=1,0,0
        font("Courier")
    end
    
    function draw()
        background(0)
        xx=0
        st=pos+yy//1
        en=math.min(#tab,pos+yy//1+50)
        for z=st,en do
            xx=xx+1
            prt=string.format("%6d  %s",z,tab[z])
            text(prt,10,HEIGHT-xx*20-20)
        end
    end
    
    function touched(t)
        if t.state==CHANGED then
            dy=(dy+t.deltaY)
            if dy<1 then
                dy=1
            end
            yy=dy//5
        end
    end
    
    function sortFile()
        table.sort(tab)
        for z=#tab,1,-1 do
            if string.byte(string.sub(tab[z],1,1))==13 then
                table.remove(tab,z)
            end        
        end
        print("blank lines removed")
        print("# of lines",#tab) 
    end
    
Sign In or Register to comment.