# 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

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

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.

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.

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()

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)
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

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