@simeon: I see. Mesh looks pretty advance and interesting. I’ll take a look at it. A simple sample code, for example draw a star using mesh, will be much appreciated. Thank you.
@simeon - Oh wow yes that would be fantastic !
3D rendering can be scary for beginners, but having it all wrapped inside Codea will make the whole concept easier to grasp I’m sure
Btw, as I was replying to your post I was playing with vectors, and I have a quick question regarding the speed of vec2 and vec3 classes
Any reason why using the built in vec2/vec3 class is so much slower than making your own (although vec2/vec3 initialisation is obviously faster since it’s built in)?
v3 = class()
function v3:init(x, y, z)
self.x = x
self.y = y
self.z = z
end
For example, using custom v3 instead of vec3 is around 7-8 times faster
local v = v3(0,0,0)
for i=1, 100000 do
v.x = i
v.y = i
v.z = i
end
Like I said, the same goes for vec2. I’m thinking about rewriting some of my code but I’m wondering if it’s not just a bug and not an actual limitation ?
Cheers,
Xavier
@Xavier I suspect that assignment, such as v.x = 4
is slower than a Lua class, but operations are faster. For example:
v1 = vec2(1,2)
v2 = vec2(3,4)
v = v1 + v2
Will be much faster than writing your own “add” function that adds two vectors.
I tried the following benchmark:
v2 = class()
function v2:init(x,y)
self.x = x
self.y = y
end
function v2:add(v)
return v2( self.x + v.x, self.y + v.y )
end
function setup()
local vn = vec2(1,1)
local vc = v2(1,1)
local startTime = os.clock()
for i = 1,1000000 do
--vc.x = i
--vc.y = i
vc = vc:add(vc)
end
print("Time taken for custom vec2: ".. (os.clock() - startTime))
startTime = os.clock()
for i = 1,1000000 do
--vn.x = i
--vn.y = i
vn = vn + vn
end
print("Time taken for native vec2: " .. (os.clock() - startTime))
end
function draw()
end
Time taken for custom vec2: 3.05869
Time taken for native vec2: 1.09686
As you can see, native is about 3 times faster than the custom class. However for assignment, you are correct, the native class is slower. I will have to look at ways to fix this.
(Edit: I ran these benchmarks on my desktop, not on an actual iPad, so these times will be much faster. But the differences should be similar)
@Simeon - Yop I get similar results on device.
I changed my code to use a custom vec2 class, and it is indeed much faster (8fps gain with 12k+ points) since I’m not doing any operations with them, just storing x,y values.
The catch is that I can’t draw since mesh.texture only takes an array of native vec2, so can’t use an array of custom vec2
Its not that big of a deal for me since I reached my fps goal already, I just thought it might be a glitch so I brought it up.
However If you ever find a way to make vector assignment faster, then all the better
Isn’t it quicker to do v = vec2(10,10)
instead of v = vec2(0,0) v.x = 10 v.y = 10
?
Regarding vector and matrix implementation, I really, really hope that you’ll multiply matrices on the left. Otherwise you’ll make life very hard for people as that’s the expected way around. I have recently written lua classes for vectors and matrices of arbitrary size and can let you have them if it would be of any use. I’ve been working on a little app to show how a 2x2 matrix acts on the Euclidean plane - but got distracted by number spinners.
When I was playing with Xavier’s code then the biggest influence on speed was the sorting (I still claim that you only need to do the z-sorting when you create the grid). It’s the same for me with my 3D shape explorer. If this could be done natively, it would be great. In looking at speed savings I learnt more about algorithms than I feel happy knowing. In particular, for both situations there’s an initial sort but then after that, the data is only slightly perturbed so the sort doesn’t have to do much. As I understand it, quicksort performs badly under these circumstances when compared to, say, a merge sort. But the time taken to set up and tear down a merge sort makes it impractical to implement in pure lua - at least, I didn’t get a good response time from it. Bucket sorts would also be useful as one can use them to sort stuff as it is generated, rather than having to generate it and then sort it.
@Andrew_Stacey - No it’s still faster to do v.x, v.y assignments than to create a new vec2, but not by much (which is weird)
As for the zsorting, I indeed need it when I first display the model, but I also need it when it rotates. I think thats what you mentionned in an earlier post ( when adding vertices and when rotating).
Like you pointed out, my code currently also does it when it’s not needed, planning on fixing that
Edit: It is my understanding that once Codea supports vec3 for meshes, it will use the zbuffer, but only for its own polygons. Meaning that drawing multiple meshes won’t sort them out, but a single mesh wouldn’t have any z error artifacts.
Thats is if each mesh is some independant render to texture object, which is then drawn into the Codea buffer. Im probably be wrong though, as I’m not sure how it’s setup
Actually, if you handle the rotation correctly then you don’t have to do it then either.
@Andrew_Stacey - mmm I’m actually not using proper wording here. The rotation in my app is fake, it’s just translation (the pseudo sphere gives it a rotation effect).
I’m only projecting vertices to screen, not doing any rotation. Could you clarify if your point still stands ? If so and if you have the time, I’m interested in reading more
I know, and that’s how you get to do the saving.
Your data is arranged in a lattice. When you translate a lattice by a whole unit then it looks just like the original lattice again. So you can exploit this since then the ordering required by the new positions are the same as the orderings of the old positions but reassigned to different vertices.
I’m not explaining this very well!
Model = class()
function Model:init(
quality,
n,
dist,
wrapping,
str,
Red,
Green,
Blue,
wireframe,
texture
)
self.quality = quality
self.nPower = n
self.dist = dist
self.wrapping = wrapping
self.str = str
self.Red = Red
self.Green = Green
self.Blue = Blue
self.wireframe = wireframe
self.texture = texture
self.x = 0
self.y = 0
self.mesh = mesh()
self:create()
self:generate()
self:colour()
self:setwireframe()
end
function Model:update(
quality,
n,
dist,
wrapping,
str,
Red,
Green,
Blue,
wireframe
)
local recreate, regenerate, recolour, rewire
if quality ~= self.quality then
recreate = true
regenerate = true
recolour = true
rewire = true
end
if n ~= self.nPower
or dist ~= self.dist
or wrapping ~= self.wrapping
or str ~= self.str
then
regenerate = true
recolour = true
end
if Red ~= self.Red
or Green ~= self.Green
or Blue ~= self.Blur
then
recolour = true
end
if wireframe ~= self.wireframe then
rewire = true
end
self.quality = quality or self.quality
self.nPower = n or self.nPower
self.dist = dist or self.dist
self.wrapping = wrapping or self.wrapping
self.str = str or self.str
self.Red = Red or self.Red
self.Green = Green or self.Green
self.Blue = Blue or self.Blue
self.wireframe = wireframe or self.wireframe
if recreate then
self:create()
end
if regenerate then
self:generate()
end
if recolour then
self:colour()
end
if rewire then
self:setwireframe()
end
end
function Model:create()
local numSteps = math.pow(2,6-self.quality)
local t = {}
local hn = (numSteps)/2
for i = 1,numSteps do
for j = 1,numSteps do
table.insert(t,{(i-hn)^2 + (j-hn)^2,i,j})
end
end
table.sort(t,function(a,b) return a[1] > b[1] end)
self.grid = t
self.numSteps = numSteps
end
function Model:generate()
local n = self.nPower
local dist = self.dist
local wrapping = self.wrapping
local str = self.str
local f = {}
local ver = {}
local vv,x,y,z,s,col,ix,iy
local g = self.grid
local numSteps = self.numSteps
local gridSize = 512/numSteps
local hn = numSteps*gridSize/2
local sx = self.x or 0
local sy = self.y or 0
local isx = math.floor(sx/gridSize)*gridSize
local isy = math.floor(sy/gridSize)*gridSize
local offsetX = WIDTH*.5
local offsetY = HEIGHT*.5
for i = 0,numSteps do
vv = {}
ix = i*gridSize - isx
for j = 0,numSteps do
iz = j*gridSize - isy
x = (ix - hn + sx)*wrapping
z = (iz - hn + sy)*wrapping
y = dist - noise(ix/n, iz/n)*str
y = math.max(y,100)
y = y + (x * x + z * z)*0.001
s = 600/(600 + y)
col = 255 - y*.075
table.insert(vv,{
vec2(x * s + offsetX,z * s + offsetY),
col
})
end
table.insert(ver,vv)
end
local meshver = {}
local meshtex = {}
local numver = 0
local tri = {
{
{0,0},
{1,0},
{1,1},
{0,0},
{0,1},
{1,1}
},
{
{0,0},
{0,1},
{1,1},
{0,0},
{1,0},
{1,1}
}
}
local i
for k,v in ipairs(g) do
if v[2] > v[1] then
i = 2
else
i = 1
end
for l,u in ipairs(tri[i]) do
table.insert(meshver,ver[v[2]+u[1]][v[3]+u[2]][1])
table.insert(meshtex,vec2(u[1],u[2]))
numver = numver + 1
end
end
self.numver = numver
self.vertices = ver
self.mesh.vertices = meshver
self.mesh.texCoords = meshtex
end
function Model:setwireframe()
if self.wireframe == 1 then
self.mesh.texture = self.texture
else
self.mesh.texture = nil
end
end
function Model:colour()
local Red = self.Red
local Green = self.Green
local Blue = self.Blue
local g = self.grid
local ver = self.vertices
local meshcol = {}
local col
local a = 0
local tri = {
{
{0,0},
{1,0},
{1,1},
{0,0},
{0,1},
{1,1}
},
{
{0,0},
{0,1},
{1,1},
{0,0},
{1,0},
{1,1}
}
}
local i
for k,v in ipairs(g) do
if v[2] > v[1] then
i = 2
else
i = 1
end
for l,u in ipairs(tri[i]) do
col = ver[v[2]+u[1]][v[3]+u[2]][2]*a/self.numver
a = a + 1
table.insert(meshcol,color(col*Red,col*Green,col*Blue,255))
end
end
self.mesh.colors = meshcol
end
function Model:shift(x,y)
self.x = self.x + x/self.wrapping
self.y = self.y + y/self.wrapping
self:generate(self.nPower, self.dist, self.wrapping, self.str)
self:colour(self.Red,self.Green,self.Blue)
end
function Model:draw()
self.mesh:draw()
end
function setup()
iparameter("quality", 1, 4, 3)
iparameter("str", 0, 2048, 512)
iparameter("nPower", 10, 200, 100)
parameter("wrapping", 1, 20, 4)
-- distance from camera
iparameter("dist", 256, 2048, 512)
iparameter("wireframe", 0, 1, 0)
parameter("Red", 0, 1, 1)
parameter("Green", 0, 1, 0.3)
parameter("Blue", 0, 1, 0.2)
local w = 64
wire = image(w,w)
setContext(wire)
pushStyle()
resetStyle()
strokeWidth(2)
noSmooth()
stroke(255, 255, 255, 255)
fill(255, 204, 0, 134)
rect(1,1,w,w)
strokeWidth(4)
line(1,1,w,w)
popStyle()
setContext()
model = Model(
quality,
nPower,
dist,
wrapping,
str,
Red,
Green,
Blue,
wireframe,
wire
)
fps = {}
fpsn = 100
for i = 1,fpsn do
fps[i] = 60
end
fpsi = 1
end
function draw()
background(0, 0, 0, 255)
fps[fpsi] = 1/DeltaTime
local tfps = 0
for i = 1,fpsn do
tfps = tfps + fps[i]
end
fpsi = fpsi%fpsn + 1
textMode(CORNER)
text("FPS: "..math.floor(1/DeltaTime*10)/10, WIDTH - 200, HEIGHT - 30)
text("FPS: "..math.floor(tfps/fpsn*10)/10, WIDTH - 200, HEIGHT - 60)
text("Vertices: "..model.numver, WIDTH - 200, HEIGHT - 90)
text("Double tap for Fullscreen", WIDTH/2, 30)
model:update(
quality,
nPower,
dist,
wrapping,
str,
Red,
Green,
Blue,
wireframe
)
model:draw()
end
function touched(touch)
if touch.state == BEGAN and touch.tapCount == 2 then
if fullscreen then
displayMode(STANDARD)
fullscreen = false
else
displayMode(FULLSCREEN)
fullscreen = true
end
model:update()
else
model:shift(touch.deltaX,touch.deltaY)
end
end
end
Ran out of space.
I’m sufficiently bad a programmer than in order to understand what you were doing then I had to rewrite it.
I haven’t ironed out all of the wrinkles - I still get some edge effects. Also, it’s based on a previous version of your code - before you started playing with textures.
@Andrew_Stacey - Nice work ! I don’t think you’re a bad programmer, and I don’t think it’s ever a bad thing to turn someone-else’s messy code into your own to better read it
I’m actually working on endless terrain myself right now
I do it by shifting the noise values on movement, then regenerating the entire mesh/texture.
It works but isn’t efficient, because texture generation is so taxing. I’m therefore forced to reduce the texture quality when rotating if I want to keep it smooth, and that’s not acceptable
My idea was to use the something similar to geometry clipmaps or a tile based game where you crop only rows/colomns and recalculate new rows/colomns based on movement, not needing to calculate the rest.
The principle is cool and it looked great in my dumb head, but implementing is is proving to be tricky due to texture generation speed
Subdividing my terrain into tiles and adding/removing tiles when needed works, but the speed issue remains: I still have to render a fair amount of texture area, or have way too many tiles which slows it down.
I think I’m screwed
As for the order, that’s really cool. It works great because you’re always “centered” on your grid, so (i-hn)^2 + (j-hn)^2 where hn = (numSteps)/2 is a functional way to get distance from eye to cell. Really really cool way to get rid of sorting, love it
Unfortunately, if I wanted to use the same algorithm, I’d have to recalculate the grid on movement like you do, but also the textures. Like I said earlier on, I haven’t found a way to do that efficiently with textures at a reasonable quality
I’m super screwed
edit: tab space ftl… wasn’t done
Well if I find a way to make the perlin noise seamless, then the problem goes away, since I can just reuse the tiles of textures, which means I only need them rendered once.
Else, I’m just going to have to wait for Codea custom textures to make a tiled heightmap
Cheers,
Xavier
oh boy… New Codea version makes this run sooooooooooo fast it’s not right hahah.
I was hitting mid 30 fps with 4096 polygons, and boy I was proud.
After removing all the software z ordering and perspective projection, it’s now at 40/50fps with 32768 polygons…
Working on something cool
/cheers
Xavier
You got me excited…
thread necro inc !
Well I have soft shadows working in real time (as in montains casting shadows over other montains). Managed to get it to run quite fast using low res textures for the shadowmap and a very cool and very fast blur trick. Simulating a sunrise is very cool looking while having smooth framerate
However since there is no multi texturing, it only runs smoothly when using low level terrain texture… The thing is that I need to blend the textures, so it gets super slow the higher resolution the terrain texture gets.
It’s actually much much much faster to just render the scene twice (once with terrain textures, and another time with transparent shadow texture), but it’s pretty lame
Anyway, I’m pretty much done with this project for now, working on other things
Cheers,
Xavier