Converting meshes to models

@LoopSpace here is my test code. I am trying to make a spotlight beam effect using a 3d cone. Need to add dependencies on your PseudoMesh class and the orbitViewer class (in. Examples). You will see the cone disappears at the same viewer angles for both the invertedNormals and non-inverted normals? Set useTransparency to true get the effect of the light fading with distance (sort of).

-- Test spotlight beam using cone

-- Use this function to perform your initial setup
function setup()
 
-- Create a new craft scene    
    scene = craft.scene()   

--orbital viewer
    scene.camera:add(OrbitViewer, vec3(0,0,0), 400, 0, 2000)
    orv=scene.camera:get(OrbitViewer)   

--texture for cone for transparency
    useTransparency=false
    if useTransparency then  
        coneTexture=image(255,255)
        setContext(coneTexture)
            for i=1,255 do
            for j=1,255 do
                mycol=color(46, 218, 240, j)
                coneTexture:set(i,j,mycol )
            end
            end
        setContext()
    end

    
    coneHeight=1   
    coneBase=1
       
    local coneMesh = PseudoMesh()
    coneMesh:addCylinder({
                ends=0,
                axis=vec3(0,1,0),
                height=coneHeight,
                startRadius=coneBase,
                endRadius=0, 
                centre=vec3(0,-coneHeight/2,0),
                size=30,   --number of facets
                })
--    coneMesh:invertNormals()
    coneModel= coneMesh:toModel()     
    
--  
    local coneMesh2 = PseudoMesh()
    coneMesh2:addCylinder({
                ends=0,
                axis=vec3(0,1,0),
                height=coneHeight,
                startRadius=coneBase,
                endRadius=0, 
                centre=vec3(0,-coneHeight/2,0),
                size=30,   --number of facets
                })
    coneMesh2:invertNormals()
    coneModel2= coneMesh2:toModel()     
    
    
    cone1=scene:entity()
    cone1.model =coneModel
    cone1.scale=vec3(1,1,1)*50
    cone1.material = craft.material("Materials:Standard")
    if useTransparency then
        cone1.material.map=coneTexture
        cone1.material.diffuse=color(255, 0, 37, 255)
        cone1.material.opacity=0.8
        cone1.material.blendMode=NORMAL   
    end  

    cone2=scene:entity()
    cone2.model =coneModel2
    cone2.scale=vec3(1,1,1)*50
    cone2.material = craft.material("Materials:Standard")
    cone2.position=vec3(0,100,0)
    if useTransparency then
        cone2.material.map=coneTexture
        cone2.material.diffuse=color(255, 0, 37, 255)
        cone2.material.opacity=0.8
        cone2.material.blendMode=NORMAL    
    end 
--]]   
    
end
    
function update(dt)  
    scene:update(dt)
end   
 
function draw()
    update(DeltaTime)
    scene:draw()   
end


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

@piinthesky The problem is that you are using addCylinder to add a cone. Since addCylinder is designed for cylinders, it expects there to be a circle at either end and so all of the normals are calculated using how each triangle relates to the circle at its end. By making the ending radius 0, you are scupperring this calculation. So many of your normals are 0 (or possibly NaN).

The simplest solution is to make the end radius small but non-zero. I get a reasonable looking cone with end radius 0.1, but 0.01 also works.

There is an addCone method, but that is more general and needs more input. What this does is take a set of points, thought of as a sample of points along a path, and constructs what is known as the “topological cone” on that set of points. This involves connecting each given point to an apex (which must also be specified). As it is more general, you have to construct the points explicitly.

@LoopSpace Ahha, eureka! Yes, setting the end radius to a non-zero small value does the trick. By drawing two cones at the same location one with ‘normal’ normals, the other with ‘inverted’ normals and setting useTransparency=true gives pretty much the effect i was looking for. Thanks.

Currently, the transparency of the texture increases as function of distance from the apex. I would also like that the transparency increases as a function of distance from the cone axis i.e. soften the ‘edges’ of the light beam cone-can you think of a way to do that?

Thought I’d try my hand at making a transparent cone that moves thru a cube of spheres. I took one of my tube programs and modified it to make a cone. When you run this, it’s not a true cone just to make it easier to see what it looks like as you rotate the cubed spheres. To see a full cone, comment out the line conePoint=2 at the start of the code so that conePoint has a value of 1000.

displayMode(FULLSCREEN)

function setup()
    conePoint=1000
    conePoint=2
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()    
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 2000, 0, 2000)
    v.camera.farPlane=3000
    v.rx,v.ry=10,-45
    cone(vec3(400,0,0),vec3(0,0,0),200,40)
    for x=-5,5 do   -- create cube of spheres
        for y=-5,5 do
            for z=-5,5 do
                createSphere(vec3(x*60,y*60,z*60))
            end
        end
    end
    x,y,z=400,100,0 -- cone starting position
end

function draw()
    update(DeltaTime)
    scene:draw() 
end

function update(dt)
    scene:update(dt)
    x=x-.5  -- move cone thru spheres
    cn.position=vec3(x,y,z) -- change cone position
end

function cone(p1,p2,dia,sides)
    local pos,ind,nor,col={},{},{},{}
    local rp=vec3(100,100,100)
    local v1=rp-p1
    local r1=v1:cross(p2-p1)
    local s1=r1:cross(p2-p1)
    local n
    r1,s1=r1:normalize(),s1:normalize() 
    for a=0,719,360/sides do
        n = r1 * math.cos(math.rad(a)) + s1 * math.sin(math.rad(a))
        n=n*dia
        if a>359 then
            table.insert(pos,n/conePoint + p2)    -- add p2 to last loop
        else
            table.insert(pos,n + p1)    -- add p1 to loop
        end
        table.insert(nor,n)
    end    
    local o,p={1,2,3,4,5,6,3,2,1,6,5,4},{}
    for z=1,#pos-sides do
        p[1],p[2],p[3],p[4],p[5],p[6]=z,z+1,z+sides+1,z,z+sides+1,z+sides
        if z%sides==0 then
            p[2]=z-sides+1
            p[3]=z+1
            p[5]=z+1
        end
        for t=1,12 do
            table.insert(ind,p[o[t]])
        end
    end 
    for z=1,#pos do
        table.insert(col,color(255))
    end    
    cn=scene:entity()
    cn.model = craft.model()
    cn.model.positions=pos
    cn.model.indices=ind
    cn.model.colors=col
    cn.model.normals=nor
    cn.material=craft.material("Materials:Basic")
    cn.material.blendMode = NORMAL
    cn.material.opacity=.5    
end

function createSphere(p)
    local s=scene:entity()
    s.position=vec3(p.x,p.y,p.z)
    s.model = craft.model.icosphere(8,3)
    s.material = craft.material("Materials:Specular")
    s.material.diffuse=color(255,0,0)
end

@dave1707, thanks that is interesting-i always thought that with craft models only either the inner or outer surfaces can be visible, but here both are visible! @LoopSpace why then are the PseudoMesh models only visible on one of the surfaces?

@dave1707 i tried to use my coneTexture (see the code i uploaded earlier) on your implementation of the cone, but it does not seem to work? Using coneTexture was a way to make the intensity of the ‘spot light’ fade as a function of distance from the apex.

@piinthesky Not sure why texture isn’t working. In other code, I use something like cn.material.map=img and I get the img texture. Maybe because I’m not using a built in model or maybe there’s something else that needs to be set. I don’t fully understand everything that needs to be set.

@piinthesky Here’s another version where a map can be used. I created a white gradient image to use so the cone gets darker the farther away from the cube. I don’t know if this is any better for you.

displayMode(FULLSCREEN)

function setup()
    img=image(256,256)  -- create gradiant texture image
    setContext(img)
    for x=0,256 do
        for y=0,256 do
            img:set(x,y,x,x,x)
        end
    end
    
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(255, 255, 255, 255)
    skyMaterial.horizon=color(255, 255, 255, 255)
    v=scene.camera:add(OrbitViewer,vec3(px,100,pz), 1000, 0, 10000)
    v.camera.farPlane=10000
    pos={}
    for z=0,360,20 do
        x=math.cos(math.rad(z))*50
        y=math.sin(math.rad(z))*50
        table.insert(pos,vec3(x,y,800))
        x=math.cos(math.rad(z))*5
        y=math.sin(math.rad(z))*5
        table.insert(pos,vec3(x,y,25))
    end
    for z=1,#pos-2,2 do
        p1=pos[z]
        p2=pos[z+1]
        p3=pos[z+3]
        p4=pos[z+2]
        createTile(p1,p2,p3,p4)
    end
    z=#pos-1
    p1=pos[z]
    p2=pos[z+1]
    p3=pos[2]
    p4=pos[1]
    createTile(p1,p2,p3,p4)
    createCube(vec3(0,0,0))
end

function draw()
    update(DeltaTime)
    scene:draw() 
end

function update(dt)
    scene:update(dt)
end

function createTile(p1,p2,p3,p4)
    local c=color(255,255,255,255)
    local pt=scene:entity()
    pt.model = craft.model()
    pt.model.positions={p1,p3,p4,p1,p2,p3}
    pt.model.indices={1,2,3,4,5,6,6,5,4,3,2,1}
    pt.model.colors={c,c,c,c,c,c}
    pt.model.uvs={vec2(0,0),vec2(1,1),vec2(0,1),vec2(0,0),vec2(1,0),vec2(1,1)}
    pt.material = craft.material("Materials:Basic")  
    pt.material.blendMode = NORMAL
    pt.material.opacity=.8  
    pt.material.map=img
    --pt.material.map=readImage("Blocks:Error") 
end

function createCube(p)
    local s=scene:entity()
    s.position=vec3(p.x,p.y,p.z)
    s.model = craft.model.cube(vec3(50,50,50))
    s.material = craft.material("Materials:Specular")
    s.material.diffuse=color(255,0,0)
end

@piinthesky @dave1707 - just looking at this from an external point of view the cone seems to look artificial - that is it looks like a solid object. There was a post by @ignatz where he was building a 2d game where he made a flaming torch for mazes which produced an in beam high colour out of beam graduated darker effect - link below. I think you may need a shader for this where all in beam objects are shown in true colour fading offset towards the perimeter rather than building an object.

https://codea.io/talk/discussion/5614/2d-sidescrolling-toolkit#latest

@piinthesky The model in @dave1707 's code is visible from both sides because each triangle is included twice, once with the vertices clockwise and once anticlockwise. This makes it visible from both sides. You could achieve the same effect with my code by adding a second copy of the cone with inverted normals. Speaking of which, the normals in @dave1707 's code aren’t quite right - they don’t take into account the slant of the cone sides.

As to the transparency, I have to admit that I’m not really sure what you are trying to accomplish overall so can’t advise on the best solution, but distance from the cone axis is proportional to distance from the apex so if you can fade appropriately from the apex then you’ll be fine. If this is simulating a light cone, then it should fade with the inverse square of the distance to the apex. But changes in transparency don’t always look the same to the eye as they do in the code so you’d just have to experiment.

@piinthesky - just picking up on my earlier point. The image you produce depends on the perspective with regard to viewing point. Do you need a sideways view of a light source moving through the environment or are you needing a viewpoint behind the light source which moves with the camera?

The latter could be simulated by a graduated screen just in front of the camera i.e. make the background darker with the centre focus brighter.

One thing you need to do is brighten the image which is at the end of the cone i.e.the broad end (target for the beam), if you are viewing from the side.

@piinthesky - just watched your video again and note that the craft you use is not shaded so it looks like you don’t have a light source in the project although you have produced a lot of interesting light effects.

How have you generated your ‘world’ ? Are you using Craft, if you are then you could add light sources to shade and illuminate to produce the effect you need.

Below are some videos of what i have at the moment for the ROV moving through the underwater detector. Everything is craft models. For the cone i am using two superimposed PseudoMesh Cones with normal and inverted normals with the fading texture plus a craft spot light. The view point can be far away or can be centred on the ROV. I am pretty happy with the results. Thanks for all your help.

https://youtu.be/edz-pjPLaA4

https://youtu.be/MqchYfApsy0

@piinthesky - wow, sorry about suggestions, judging by the new videos you were obviously well beyond where I thought you were. Very impressive!!!

@piinthesky Those are great. Is there any chance you can strip out the code and show a simple example of just the lighting.

@dave1707 here is a stripped down version of the lighting section. Uses @LoopSpace 's PseudoMesh models.

-- Test spotlight lighting with cone
-- PiInTheSky, 8/12/19
-- requires LoopSpace's PseudoMesh Models

function setup()
    parameter.number("e1",0,360,90)
    parameter.number("e2",0,360,0)
    parameter.number("e3",0,360,0)
    
    parameter.number("x",-1000,1000,0)
    parameter.number("y",-1000,1000,0)
    parameter.number("z",-1000,1000,0)
    
    parameter.number("coneAngle",0,90,30)    
    parameter.integer("ambientIntensity",0,255,60)
    parameter.number("sunIntensity",0,1,0.0)   
    parameter.integer("lightIntensity",0,10,5)     
    parameter.number("lightDecay",0,10,5)         
    
-- Create a new craft scene    
    scene = craft.scene()   
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 1000, 0, 2000)
    v.camera.farPlane=3000
    
    scene.ambientColor=color(ambientIntensity,ambientIntensity,ambientIntensity,255)
    sunLight=scene.sun:get(craft.light)

-- create cube of spheres    
     for x=-5,5 do   
        for y=-5,5 do
            for z=-5,5 do
                createSphere(vec3(x*60,y*60,z*60))
            end
        end
    end   
      
--texture for cone transparency
    coneTexture=image(255,255)
    setContext(coneTexture)
    for i=1,255 do
        for j=1,255 do
            mycol=color(38, 104, 243, j*0.5)
            coneTexture:set(i,j,mycol )
        end
    end
    setContext()

--set up PseudoMesh cones with normals normal and inverted 
    local coneMesh = PseudoMesh()
    coneMesh:addCylinder({
                ends=0,
                axis=vec3(0,1,0),
                height=1,
                startRadius=1,
                endRadius=0.001, 
                centre=vec3(0,-0.5,0),
                size=30,   --number of facets
                })
    coneModel= coneMesh:toModel()       
    cone1=scene:entity()
    cone1.model =coneModel
    cone1.material = craft.material("Materials:Basic")
    cone1.position=vec3(0,10,0)
    cone1.material.map=coneTexture
    cone1.material.blendMode=NORMAL   

    
    coneMesh:invertNormals()
    coneModel2= coneMesh:toModel()     
    cone2=scene:entity()
    cone2.parent=cone1    --inherents coord transforms of cone1
    cone2.model =coneModel2
    cone2.material = craft.material("Materials:Basic")
    cone2.material.map=coneTexture
    cone2.material.blendMode=NORMAL    

--add spot light
    conespot=cone1:add(craft.light,SPOT)        
    conespot.distance=500
    conespot.penumbra=1
    conespot.intensity=lightIntensity
    conespot.decay=lightDecay
end

function createSphere(p)
    local s=scene:entity()
    s.position=vec3(p.x,p.y,p.z)
    s.model = craft.model.icosphere(5,3)
    s.material = craft.material("Materials:Specular")
    s.material.diffuse=color(255,0,0)
end
   
function update(dt)  
    cone1.position=vec3(x,y,z)
    cone1.eulerAngles=vec3(e1,e2,e3)
    
    conespot.intensity=lightIntensity
    conespot.decay=lightDecay
    conespot.angle=coneAngle    
    local xzscale=math.tan(math.rad(coneAngle))
    cone1.scale=vec3(xzscale,1,xzscale)*500*math.pow(lightIntensity,0.3)/lightDecay
    
    scene.ambientColor=color(ambientIntensity,ambientIntensity,ambientIntensity,255)
    sunLight.intensity=sunIntensity
    
    scene:update(dt)
end   
 
function draw()
    update(DeltaTime)
    scene:draw()   
end

@piinthesky Thanks for the above code, but I gave up trying to get it to run. Trying to include Loopspaces code, getting errors because it might not be the right code, trying to find the right code, still getting errors. I didn’t think it was going to be that involved or I’m just not finding the correct code to include. I found the code for his models which I assume you’re referring to. I ran that and it showed several different colored shapes rotating. I tried to pull what I thought I needed out of that, but apparently that wasn’t right. Is there just the code of his that I need to include or do I have to pull it out of one of his examples someplace.

@dave1707 you would need to include the PseudoMesh class which @LoopSpace provides in the first entry of this discussion thread. Then you need also his MeshExt library which he also indicates where to find in a later entry in the thread. i have found them very useful libraries, so it is well worth the effort to get them into your collection of code.

@piinthesky Thanks. I’ll try to located them and try again.

@piinthesky Got everything loaded. The code you show above now works. The only problem is the amount of code to look thru to understand how your code works.

@dave1707 - I’ve loaded the above code, PseudoMesh and MeshExt library with dependencies but still not working - fails on coneMesh:addCylinder().