SphereQuest!

So - I’m on a neverending sphere quest now. Talk about yak shaving. At least it’s fun.

But I clearly don’t understand something.

First - here’s a link to the sphere code - it’s a bit too big to post here: https://gist.github.com/2589065

I don’t like it for lots of reasons, but that’s neither here nor there right now - it’s good enough I can test some things.

My question is - why doesn’t 3D rotation work the way I think it should?

If I do “rotate(ElapsedTime/10)” - I get it rotating slowly about an axis sticking straight out of the screen, as you would expect, and that’s fine.

If I do “rotate(ElapsedTime/10, 0, 1, 0)”, like the “3d Lab” demo does… well, try it. The sphere clips from the sides, the comes back, but nothing appears to rotate.

I’m missing something simple, I know I am.

Awesome! Love it! Looks great! The problem is that you need to move the scale before the rotate. Then it works like a magic ball. Love it a lot! 3D looks awesome!

You’re right - that worked. But I don’t understand why. Huh.

Wait until I add the color mapping :slight_smile:

I was wondering the same thing when I saw that mistake. IDK.

Hi Guys,

Excellent post Bortels, I too am on a SphereQuest and this was a big help to me. Played around with your code a little, looking for a simpler colouring scheme. Still playing, but you can get some interesting effects. Re-arranged your code for convenience and annotated for my own needs. Data transferred onto a separate module. The code now:

-- a few variations on Bortels sphere model

-- Use this function to perform your initial setup
function setup()
    -- dimension vertex point locations, face IDs
    -- and colour data then read data from data file
    vertex = {}
    faces = {}
    colors = {}
    vertexData() 
    cc = 0
    -- set up some control parameters to play
    x = WIDTH/2
    y = HEIGHT/2
    z = 0
    iparameter("x")
    iparameter("y")
    iparameter("z",1,100,10)
    iparameter("h",1,100,10)      
    iparameter("v",1,100,10)
    iparameter("t",10,100,10)
    -- define the triangle colouring                   
    for i=1,table.getn(vertexlist),6 do
        red = color(255, 0, 0, 255)
        white = color(255, 255, 255, 255)
        table.insert(faces, vertex[vertexlist[i]])              
        table.insert(colors, red)      
        table.insert(faces, vertex[vertexlist[i+1]])              
        table.insert(colors, red)
        table.insert(faces, vertex[vertexlist[i+2]])  
        table.insert(colors, red)
        table.insert(faces, vertex[vertexlist[i+3]])
        table.insert(colors, white)        
        table.insert(faces, vertex[vertexlist[i+4]])              
        table.insert(colors, white)
        table.insert(faces, vertex[vertexlist[i+5]])  
        table.insert(colors, white)    
    end
    -- initialise mesh and add the faces and colors arrays to it
    m = mesh()
    m.vertices = faces
    m.colors = colors
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    -- Do your drawing here
    translate(x,y)
    scale(100)    
    rotate(ElapsedTime*t,h,v,z) 
    m:draw()
end

This allows me to play and build up my own project. Thanks again - good luck with the planet building.
I think the need for the order of commands is required so that all changes in the mesh data are performed, i.e. sphere built and located, before you can rotate the data. Scaling would obviously change the locations of active pixels… Hmmmm need to add a variable into scale !!!

Thanks again.

Bri_G

=D>

Hi Guys,

Added a zoom parameter to the scale and it works superbly.

Bri_G

:-))

Yeah, the colors were simply so I could see the triangles - in the end, I intend to heightmap the colors, so this is purly placeholder so I can see how the sphere is moving.

I went thru a weird process with the data - first, I was going to generate it on-the-fly, but doing so is noticably slow. So I said “well, this is stupid, I can pre-generate it all, and someone must have done so already - all I need to do is find that data”. This sphere data is actually from Wingz 3d. But I don’t like how they triangulated it, and it’s not fine enough for me to be happy.

So I’m going back now to generating a sphere mathematically from first-principles - I’m going to generate an icosahedron (20-sided die), then recursively subdivide the faces (1 into 4). This will let me make a sphere of arbitrary fineness - at the expense of generation time. But - here’s the key - I’m going to add in data caching to local storage, so once I ask for a sphere of given fineness (on a given ipad) - the next time, it should be MUCH faster to start, because it’ll just be a data fetch. (There’s a part of me that says that TLL should add some common pre-generated meshes - cube, sphere, and the ever-loving teapot - as part of the base Codea install, JUST BECAUSE. :slight_smile: )

The upshot of this is that when I do my planets game, the “beauty shot” of a planet will be rotating, W00T! I might, in fact, go back to rotating planets in the game proper, simply because once I have a texture, a good way to use the spare cycles will be to render each different planet in the background, rotate it a bit, save the image for display, and move to the next one - so each world will slowly rotate. or not. Note 100% sure. BUT IT’S ALL FUN!

I said it before, I’ll say it again - Codea is my favorite meta-game.

Progress has been made!

http://www.youtube.com/watch?v=UL6vbRD-xo4

I’m needing lighting. And my first attempt to uvmap a texture resulted in my actually crashing codea (I think my experiments with REALLY BIG meshes may have caused some instability - the number of triangles in my spheres grows… exponentially?)

I wonder what the max size of a mesh in Codea is.

Next step here - take my terrain code (the 3d noise plus colormap) and apply it to the video above.

OMG CODE I need it for my game :-bd

I beg for the code (I’m Macking a 3d fiying sim)•o•

@Bortels - Wow! Nice! Lighting would be nice though. But make it so there is a sun then.
Nice!
P.S. This is a pretty… colorful… space. Add some unicorns to top it off. :smiley:

I :X that code :-bd

You can easily implement a basic lighting by multiplying the color with the cosine of the angle between a light direction and the normal in a vertex. Add some ambient light and voila…


--# Main
-- 3D example

-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    
    parameter("FieldOfView", 10, 140, 20)
    parameter("Ambient", 0, 1, 0.4)
    
    scn=Scene()
    
    scn:add(Sphere(30,vec3(0,0,0),"SpaceCute:Background"))

    zoom3d=Zoom3D(120,20,vec3(0,0,0))
    
    ambient=nil
end

function touched(touch)
    zoom3d:touched(touch)
end

-- This function gets called once every frame
function draw()
    if (ambient~=Ambient) then
        ambient=Ambient
        scn:setLighting(ambient)
    end
    
    zoom3d:draw()

    -- This sets a dark background color 
    background(0, 0, 0, 255)

    -- Do your 3D drawing here
    scn:draw()

    -- Restore orthographic projection
    ortho()
    
    -- Restore the view matrix to the identity
    viewMatrix(matrix())

    -- Draw a label at the top of the screen
    fill(255)
    font("MyriadPro-Bold")
    fontSize(30)

    text("3D example", WIDTH/2, HEIGHT - 30)
end


--# Sphere
Sphere = class()

function Sphere:init(size,pos, texture,colour,lightDir)
    -- you can accept and set parameters here
    self.size = size or 1
    self.pos = pos or vec3(0,0,0)
    self.color = colour or color(255, 255, 255, 255)
    
    
    -- all the unique vertices that make up a cube
    local vertices = {
        vec3(-1, 0,  0),
        vec3( 1, 0,  0),
        vec3( 0,  1, 0),
        vec3( 0, -1, 0),
        vec3( 0, 0,  1),
        vec3( 0, 0, -1)
    }

    self.verts = {
      vertices[1], vertices[5], vertices[3],
      vertices[3], vertices[5], vertices[2],
      vertices[2], vertices[5], vertices[4],
      vertices[4], vertices[5], vertices[1],
      vertices[1], vertices[3], vertices[6],
      vertices[3], vertices[2], vertices[6],
      vertices[2], vertices[4], vertices[6],
      vertices[4], vertices[1], vertices[6]
    }

    for i=1,3 do
        self:subdivide()
    end
    self.texCoords ={}
    
    for i=1,#self.verts do
        local v=math.acos(-self.verts[i].y)/math.pi
        local u= ( math.acos(-self.verts[i].x/(math.sin(v*math.pi))) ) / (2*math.pi)
        if 1.0 - math.abs(self.verts[i].y) <0.05 then
            u=0.5
        end
        if self.verts[i].z>0 then
            u=1-u;
        end
        table.insert(self.texCoords,vec2(u*0.94+0.03,v*0.45+0.24))
    end
    
    self.mesh = mesh()
    self.mesh.vertices = self.verts
    --sprite("Planet Cute:Wood Block")
    self.mesh.texture = texture
    self.mesh.texCoords = self.texCoords
    self:setLighting(ambient,lightDir)
end

function Sphere:setLighting(ambient,lightDir)
    ambient = ambient or 0.4
    lightDir = lightDir or vec3(2,1,1)
    local vcolors = {}
    lightDir=lightDir:normalize()
    for i=1,#self.verts do
        local f=ambient
        local n=self.verts[i]:normalize()
        local i1=n:dot(lightDir)
        if i1>0 then
            f = f + i1*(1.0-ambient)
        end
        table.insert(vcolors,color(self.color.r*f,self.color.g*f,self.color.b*f,self.color.a))
    end
    self.mesh.colors=vcolors
end

function Sphere:subdivide()
    local verts={}
    for i=1,#self.verts,3 do
        local arr={self.verts[i],self.verts[i+1],self.verts[i+2]}
        table.insert(arr,(arr[1]+arr[2]):normalize())
        table.insert(arr,(arr[2]+arr[3]):normalize())
        table.insert(arr,(arr[1]+arr[3]):normalize())
        table.insert(verts,arr[1])
        table.insert(verts,arr[4])
        table.insert(verts,arr[6])
        table.insert(verts,arr[4])
        table.insert(verts,arr[2])
        table.insert(verts,arr[5])
        table.insert(verts,arr[5])
        table.insert(verts,arr[3])
        table.insert(verts,arr[6])
        table.insert(verts,arr[4])
        table.insert(verts,arr[5])
        table.insert(verts,arr[6])
    end
    self.verts=verts
end

function Sphere:draw()
    pushMatrix()
    translate(self.pos.x,self.pos.y,self.pos.z)
    scale(self.size/2,self.size/2,self.size/2)
    self.mesh:draw()
    popMatrix()
end

function Sphere:touched(touch)
    -- Codea does not automatically call this method
end

--# Scene
Scene = class()

function Scene:init()
    self.objs = {}
end

function Scene:add(obj)
    self.objs[obj]=1
end

function Scene:remove(obj)
    self.objs[obj]=nil
end

function Scene:setLighting(ambient,lightDir)
    for obj,_ in pairs(self.objs) do
        obj:setLighting(ambient,lightDir)
    end
end

function Scene:draw()
    for obj,_ in pairs(self.objs) do
        obj:draw()
    end
end

The example also uses my zoom3D library.

--# Zoom3D
-- Zoom3D library v1.1
-- Herwig Van Marck
-- usage:
--[[
function setup()
    -- 
    zoom3d=Zoom3D(20,30,vec3(0,0,0))
end
function touched(touch)
    zoom3d:touched(touch)
end
function draw()
    zoom3d:draw()
end
]]--

Zoom3D = class()

function Zoom3D:init(theta,phi,lookat)
    -- you can accept and set parameters here
    self.touches = {}
    self.touchCnt = 0
    self.initx=theta*WIDTH/360 or 0;
    self.inity=(phi+90)*HEIGHT/180 or 0;
    self.initl=lookat or vec3(0,0,0)
    self:clear()
    print("Tap and drag to rotate around center\
Double tap and drag to move center")
    print("Pinch to zoom\
Tripple tap to reset zoom")
end

function Zoom3D:clear()
    self.lastPinchDist = 0
    self.pinchDelta = 1.0
    self.center = vec2(self.initx,self.inity)
    self.lcenter = vec2(WIDTH/2,HEIGHT/2)
    self.lookat = self.initl
    self.offset = vec2(0,0)
    --self.loffset = vec2(0,0)
    self.zoom = 1
    self.started = false
    self.started2 = false
    self.dbl = false
    self.fly = false
    pushMatrix()
    resetMatrix()
    self:setCamera()
    self.matrix=modelMatrix()*viewMatrix()*projectionMatrix()
    self.invMatrix=self.matrix:inverse()
    popMatrix()
    self.reset=false
end

function Zoom3D:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == ENDED then
        self.touchCnt = self.touchCnt - 1
        self.touches[touch.id] = nil
        if self.dbl then
            self.lcenter = vec2(WIDTH/2,HEIGHT/2)
            self.reset=true
            self.dbl=false
        elseif self.fly then
            self.reset=true
            self.fly=false
        else
            self:saveLocalData()
        end
    else
        if touch.state == BEGAN then
            self.touchCnt = self.touchCnt + 1
        end
        self.touches[touch.id] = touch
        if (touch.tapCount==2) then
            if self.touchCnt==1 then
                self.dbl=true
            else
                self.fly=true
            end
        elseif (touch.tapCount==3) then
            self:clear()
        end
    end
end

function Zoom3D:processTouches()
    local touchArr = {}
    for k,touch in pairs(self.touches) do
        -- push touches into array
        table.insert(touchArr,touch)
    end

    if #touchArr == 2 then
        if self.dbl then
            self.lcenter = vec2(WIDTH/2,HEIGHT/2)
            self.reset=true
            self.dbl=false
        end
        self.started = false
        local t1 = vec2(touchArr[1].x,touchArr[1].y)
        local t2 = vec2(touchArr[2].x,touchArr[2].y)

        local dist = t1:dist(t2)
        if self.started2 then
        --if self.lastPinchDist > 0 then 
            self.pinchDelta = dist/self.lastPinchDist          
        else
            self.offset= self.offset + ((t1 + t2)/2-self.center)
            self.started2 = true
        end
        self.center = (t1 + t2)/2
        if self.center.y- self.offset.y>= HEIGHT -1 then
            self.offset.y=self.center.y- HEIGHT +1
        elseif self.center.y- self.offset.y <1 then
            self.offset.y=self.center.y +1
        end
        self.lastPinchDist = dist
        --self.reset=true
    elseif (#touchArr == 1) then
        self.started2 = false
        self.pinchDelta = 1.0
        self.lastPinchDist = 0
        local t1 = vec2(touchArr[1].x,touchArr[1].y)
        if self.dbl then
            if not(self.started) then
                self.loffset = (t1-self.lcenter)
                self.started = true
            end
            self.lcenter=t1
        else
            if not(self.started) then
                self.offset = self.offset + (t1 - self.center)
                self.started = true
            end
            self.center=t1
            if self.center.y - self.offset.y>= HEIGHT -1 then
                self.offset.y=self.center.y- HEIGHT +1
            elseif self.center.y - self.offset.y <1 then
                self.offset.y=self.center.y -1
            end
        end
        --self.reset=true
    else
        self.pinchDelta = 1.0
        self.lastPinchDist = 0
        self.started = false
        self.started2 = false
        self.reset=true
    end
end

function Zoom3D:setCamera()
    local Theta=math.fmod((self.center.x- self.offset.x)*360/WIDTH,360)
    local Phi=(self.offset.y - self.center.y)*180/HEIGHT +90
    if Phi>90 then
        Phi=90
    elseif Phi<-90 then
        Phi=-90
    end
    if self.dbl then
        self.lookat= self:getLocalPoint2(vec2(WIDTH,HEIGHT)-self.lcenter +self.loffset,
            self:getWorldPointZ2(self.lookat))
    end
    -- First arg is FOV, second is aspect
    perspective(FieldOfView, WIDTH/HEIGHT)
    if not(self.fly) then
        self.zoom = math.max( self.zoom*self.pinchDelta, 0.2 )
        self.pinchDelta = 1.0
        local CamDistance = 300/self.zoom
        self.camera=vec3(CamDistance*math.cos(Theta*math.pi/180)
                *math.cos(Phi*math.pi/180)+self.lookat.x,
            CamDistance*math.sin(Phi*math.pi/180)+self.lookat.y,
            CamDistance*math.sin(Theta*math.pi/180)*math.cos(Phi*math.pi/180)+self.lookat.z)
    else
        local CamDistance = 300/self.zoom
        self.camera=vec3(CamDistance*math.cos(Theta*math.pi/180)
                *math.cos(Phi*math.pi/180)+self.lookat.x,
            CamDistance*math.sin(Phi*math.pi/180)+self.lookat.y,
            CamDistance*math.sin(Theta*math.pi/180)*math.cos(Phi*math.pi/180)+self.lookat.z)
        local zshift=(self.lookat-self.camera)*(1-1/self.pinchDelta)
        self.camera = self.camera + zshift
        self.lookat = self.lookat + zshift
        self.pinchDelta = 1.0
    end
    camera(self.camera.x,self.camera.y,self.camera.z,
        self.lookat.x,self.lookat.y,self.lookat.z, 0,1,0)
    if self.reset then
        self.matrix=modelMatrix()*viewMatrix()*projectionMatrix()
        self.invMatrix=self.matrix:inverse()
        self.reset=false
        self:saveLocalData()
    end
end

function Zoom3D:draw()
    -- compute pinch delta
    self:processTouches()
    self:setCamera()
end

function Zoom3D:getWorldPoint2(pt)
    local m=self.matrix
    local sc=pt.x*m[4]+pt.y*m[8]+pt.z*m[12]+m[16]
    return vec2(((pt.x*m[1]+pt.y*m[5]+pt.z*m[9]+m[13])/sc+1)*WIDTH/2,
        ((pt.x*m[2]+pt.y*m[6]+pt.z*m[10]+m[14])/sc+1)*HEIGHT/2)
end

function Zoom3D:getWorldPoint(pt)
    m=modelMatrix()*viewMatrix()*projectionMatrix()
    local sc=pt.x*m[4]+pt.y*m[8]+pt.z*m[12]+m[16]
    return vec2(((pt.x*m[1]+pt.y*m[5]+pt.z*m[9]+m[13])/sc+1)*WIDTH/2,
        ((pt.x*m[2]+pt.y*m[6]+pt.z*m[10]+m[14])/sc+1)*HEIGHT/2)
end

function Zoom3D:getWorldPointZ2(pt)
    local m=self.matrix
    local sc=pt.x*m[4]+pt.y*m[8]+pt.z*m[12]+m[16]
    return (pt.x*m[3]+pt.y*m[7]+pt.z*m[11]+m[15])/sc
end

function Zoom3D:getLocalPoint2(pt2,ptz)
    local pt=vec3(pt2.x*2/WIDTH -1,pt2.y*2/HEIGHT -1,ptz)
    local m=self.invMatrix

    local sc=pt.x*m[4]+pt.y*m[8]+pt.z*m[12]+m[16]
    return vec3((pt.x*m[1]+pt.y*m[5]+pt.z*m[9]+m[13])/sc,
        (pt.x*m[2]+pt.y*m[6]+pt.z*m[10]+m[14])/sc,
        (pt.x*m[3]+pt.y*m[7]+pt.z*m[11]+m[15])/sc)
end


@Herwig, thank you! I’ll look at this tonight - I had already gone the route of subdividing (I start with a isohedron instead of a cube), but I only realized earlier today why I’ve had ugly issues - I’ve been trying to color based on vertex, and it comes out all “triangly”. I did an abortive attempt to texture map last night - but I see you did that above. I clearly need to texture map for “coloring”, and then light the vertices…

For funsies - this is what I’m getting coloring vertices only: https://twitter.com/#!/bortels/status/199636173805723648/photo/1

Planet of the triangles!

PS. Ok - I grabbed your code, couldn’t wait. Wish I’d seen it 3 days ago :slight_smile: