Running code outside of draw look or setup?

Is there a way that I would be able to run code outside of the draw loop or setup function? The reason I’m asking this is because I’m experimenting with 3d terrain generation, and I would like to be able to display some sort of loading screen as the app starts up (right now the screen goes blank for about 2 seconds while the setup function generates the terrain). I’m sorry if this question isn’t worded properly, I can’t seem to put words on the thought.

EDIT: code deleted. Didn’t work right.

@Mr_Ninja This might work better than my other code.

EDIT: Corrected the code after @Ignatz pointed out my mistake.


function setup()
    load=true
    fill(255)
    text("Loading 3D terrain",WIDTH/2,HEIGHT/2)
end

function draw()  
    background(0) 
    if load then
        load3D()
    end    
    text("Run your other code here",WIDTH/2,HEIGHT/2)
end

function load3D()
    for z=1,10000000 do
        a=math.sqrt(z)
        a=math.sqrt(z)
        a=math.sqrt(z)
    end
    load=false
end

Yes, using a variable to tell Codea when you are ready, is the way to go.

@dave1707 - I don’t see why you need backingMode (and even if you did, wouldn’t you want to turn it off once the work is complete?),

@Ignatz Actually I don’t need “backingMode”. That was leftover from the first program I tried. “backingMode” can be removed and “background(0)” can be moved up after “function draw()”.

@Mr_Ninja - I’m interested in how you’re doing 3D terrain generation, because I’ve done some work on it.

I’ve tried using noise and also the diamond square algorithm, and currently, I’m experimenting with creating a 3D terrain mesh in Blender and importing it.

When texturing your terrain, I strongly recommend a fast tiling shader. I found lighting was also important in making contours visible.

Finally, if you want to walk over your terrain, I have some code for that.

Here’s my code. I’m using your shader @Ignatz for lighting. My goal for this is to make some sort of simple flight game. I am using the noise() function for generating it, and basically draw it into a mesh. It is based similarly on the noise demo given with Codea, but implemented into 3d. currently movement is done by moving a camera around, but I am considering doing something along the lines of removing that edge layers far away from you and generating new layers close to you. Here’s the code;

--# Main
-- Fly

function setup()
Rotation:init()
displayMode(FULLSCREEN)
flight = Flight()
flight:genTerrain()
end

function draw()
flight:draw()
end

function touched(touch)
flight:touched(touch)
end







--# Flight
Flight = class()

function Flight:init()

--background(127, 127, 127, 255)
--text("<<<LOADING>>>", WIDTH/2, HEIGHT/2)
self.render = {width = 100, length = 100}
self.terrain = {}
self.position = vec2(3000,3000)
self.previous = vec2(self.position.x-1, self.position.y-1)
--parameter.watch("flight.position.x")
--parameter.watch("flight.position.y")
parameter.watch("1/DeltaTime")
self.terrainMesh = mesh()
self.vertices = {}
--self.terrainMesh.vertices={}
self:setMeshOne()  
self:setMesh()
parameter.number("rot", -90, 90, 0)
parameter.number("rz", 0,100,1)
end

function Flight:draw()   
if self.state == MOVING then
self:move()
end
background(0, 168, 255, 255)
   perspective(75, WIDTH/HEIGHT, -10,21)  
   camera(self.position.x-3000,100, self.position.y-3000, self.position.x-3000+0, 100, self.position.y-3000+1, rot/30, rz, rz)
       self.terrainMesh.shader.mModel=modelMatrix() 
   self.terrainMesh:draw() 
end

function Flight:genTerrain()
for x = 1, self.render.width do
   self.terrain[x] = {}
   for y = 1,self.render.length do
   self.terrain[x][y] = noise(x*0.1+self.position.x, y*0.1+self.position.y)
       end
   end
end

function Flight:move()
self.position.x = self.position.x + CurrentTouch.deltaX*-1
self.position.y = self.position.y + CurrentTouch.deltaY*-1
end

function Flight:setMesh()
self:genTerrain()
for x=2, self.render.width-1 do
for y=2, self.render.length-1 do
table.insert(self.vertices, vec3(x*40, self.terrain[x][y]*100,y*40))
table.insert(self.vertices, vec3(x*40+40, self.terrain[x+1][y]*100, y*40))
table.insert(self.vertices, vec3(x*40, self.terrain[x][y+1]*100,y*40+40))
table.insert(self.vertices,vec3(x*40+40, self.terrain[x+1][y]*100, y*40))
table.insert(self.vertices,vec3(x*40+40, self.terrain[x+1][y+1]*100, y*40+40))
table.insert(self.vertices,vec3(x*40, self.terrain[x][y+1]*100, y*40+40))
end
end
self.terrainMesh.vertices = self.vertices      
self.terrainMesh:setColors(99,209,63,255)           self.terrainMesh.shader=shader(diffuseShader.vertexShader,diffuseShader.fragmentShader)
       local d=0.7
       self.terrainMesh.shader.directColor=color(245, 255, 0, 255)
       self.terrainMesh.shader.directDirection=vec4(-2,-1,8,0):normalize()
       local a=0.2
       self.terrainMesh.shader.ambientColor=color(209, 205, 177, 255)
       self.terrainMesh.normals=CalculateAverageNormals(self.terrainMesh.vertices,0)
       self.terrainMesh.shader.reflect=1.1

end

function Flight:setMeshOne()
for x=2, self.render.width-1 do
self.terrainMesh[x] ={}
for y=2, self.render.length-1 do
self.terrainMesh=mesh()       


end
end
end

function Flight:touched(touch)
self.state = touch.state
end









--# Plane
Plane = class()

function Plane:init()
self.speed = vec3(0,0,0)
self.position = vec3(0,0,0)
self.direction = vec3(0,0,0)
end

function Plane:getPosition()
self.direction = vec3((self.position+self.speed)-(self.position+self.speed*2)):normalize()
self.speed = self.speed + self.direction 
self.position = self.position + self.speed
end

function Plane:touched(touch)

end

--# Controls
Controls = class()

function Controls:init(x)
self.throttle = 0
self.direction = vec3(0,0,0)
end

function Controls:draw()

end

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

--# Rotation
Rotation = class()

function Rotation:init()
xcal=0
ycal=0
end

function Rotation:getx()
xcal = xcal + RotationRate.z
return xcal 
end
function Rotation:gety()
ycal = ycal + RotationRate.x
return ycal
end 

--# Shader
function CalculateNormals(vertices)
   --this assumes flat surfaces, and hard edges between triangles
   local norm = {}
   for i=1, #vertices,3 do --calculate normal for each set of 3 vertices
       local n = ((vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])):normalize()
       norself.terrainMesh = n --then apply it to all 3
       norm[i+1] = n
       norm[i+2] = n
   end
   return norm
end   

function CalculateAverageNormals(vertices,f)
   --average normals at each vertex
   --first get a list of unique vertices, concatenate the x,y,z values as a key
   local norm,unique= {},{}
   for i=1, #vertices do
       unique[vertices[i].x ..vertices[i].y..vertices[i].z]=vec3(0,0,0)
   end
   --calculate normals, add them up for each vertex and keep count
   for i=1, #vertices,3 do --calculate normal for each set of 3 vertices
       local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i]) 
       for j=0,2 do
           local v=vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z
           unique[v]=unique[v]+n  
       end
   end
   --calculate average for each unique vertex
   for i=1,#unique do
       unique[i] = unique[i]:normalize()
   end
   --now apply averages to list of vertices
   for i=1, #vertices,3 do --calculate average
       local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i]) 
       for j=0,2 do
           local m = unique[vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z]
           norm[i+j]=n*f+m*(1-f)
       end
   end
   return norm 
end      

function CalculateAverageNormals0(vertices)
   --average normals at each vertex
   --first get a list of unique vertices, concatenate the x,y,z values as a key
   local norm,unique= {},{}
   for i=1, #vertices do
       unique[vertices[i].x ..vertices[i].y..vertices[i].z]=vec3(0,0,0)
   end
   --calculate normals, add them up for each vertex and keep count
   for i=1, #vertices,3 do --calculate normal for each set of 3 vertices
       local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i]) 
       for j=0,2 do
           local v=vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z
           unique[v]=unique[v]+n  
       end
   end
   --calculate average for each unique vertex
   for i=1,#unique do
       unique[i] = unique[i]:normalize()
   end
   --now apply averages to list of vertices
   for i=1, #vertices do --calculate average
       norself.terrainMesh = unique[vertices[i].x ..vertices[i].y..vertices[i].z]
   end
   return norm 
end   

diffuseShader={
vertexShader=[[

uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec4 directDirection;

attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;

varying lowp vec4 vColor;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vDirectDiffuse;

void main()
{
   vColor = color;
   gl_Position = modelViewProjection * position;
   vNormal = mModel * vec4( normal, 0.0 );
   vec4 norm = normalize(vNormal);
   vPosition = mModel * position;
   vDirectDiffuse = directColor * max( 0.0, dot( norm, directDirection ));
}

]],

fragmentShader=[[

precision highp float;
uniform vec4 ambientColor;
uniform float reflect;

varying lowp vec4 vColor;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vDirectDiffuse;

vec4 normalizedNormal = normalize(vNormal);

void main()
{
   vec4 ambient = vColor * ambientColor;
   vec4 diffuse = vColor * vDirectDiffuse;
   vec4 totalColor = reflect * (ambient + diffuse);
   totalColor.a=1.;
   gl_FragColor=totalColor;
}
]]
}

@Mr_Ninja - ok, if you add an image texture, you’ll need to tile it. I have that in my shader ebook - just one line of code!

I also considered creating “infinite” 3D terrain on the fly, but the problem is that it is processor intensive and hurts FPS. Also, culling your mesh so it only draws what you can see is not easy either. Generally, you should not try to do that because OpenGL does it very well, but I have found that speed degrades in proportion to the number of vertices in the mesh.

The answer to that may be to draw blocks of terrain as separate meshes, and only draw those that can be seen, but you still have the problem of generating fresh terrain without killing FPS.

You can also cheat in various ways, eg by adding mist to avoid the need to draw anything far away.

Ok. Could the terrain that I call to be generated be generated outside of the draw loop (ie the draw loop would continue to run at around 60 Fps while the 2 or 3 second process of generating terrain is done in the background, and you would still be able to navigate the terrain)

@Mr_Ninja Make these changes to your code for a loading screen.


--# Main
-- Fly

displayMode(FULLSCREEN)

function setup()
    load=true
    fill(255)
    text("Loading 3D terrain",WIDTH/2,HEIGHT/2)
end

function draw()
    background(0)
    if load then
        Rotation:init()
        flight = Flight()
        flight:genTerrain()
        load=false
    else   
        flight:draw()
    end
end

@Mr_Ninja - short answer is no

Codea is single threaded, so any work you do on the terrain will slow down the FPS

It is also event driven, so your draw function will need to manage the timing of creating new terrain

something like this in the draw function

if test_whether_new_terrain_needed then
   --generate new terrain --Codea will not keep drawing while this runs
end

actually, @dave1707, you can simplify your code a little to this, no need for the load variable (EDIT - sepeated initialisation code into separate function to keep draw clean)

displayMode(FULLSCREEN)

function setup()
    fill(255)
    text("Loading 3D terrain",WIDTH/2,HEIGHT/2)
end

function draw()
    background(0)
    if not flight then Initialise() end
    --your drawing code
end

function Initialise()
    Rotation:init()
    flight = Flight()
    flight:genTerrain()
end

One way to simulate multi-threading for a loading screen is to turn your generation function into a coroutine and while it’s loading simply display a progress bar or something like that. For instance:

function setup()
    number_of_times_per_frame = 1 --number of runs through the loop per frame
    max = 1000 --max number of runs through the loop 
    worldgen = coroutine.create(genterrain) --makes a coroutine out of the genterrain function
    load = true --is loading
    numdone = 0 --number of runs through the loop completed
end
function draw() 
    background(40, 40, 50)
    if load then --if it's loading
        coroutine.resume(worldgen,max) --pass the max variable to the coroutine and start it if it's the first time, otherwise pass the max variable to the yield (which is then discarded) and resume the function
        fill(255)
        fontSize(50)
        textAlign(CENTER)
        text("Loading...\
"..math.floor((numdone/max)*100).."%",WIDTH/2,HEIGHT/2)
    else
        fill(255)
        fontSize(50)
        textAlign(CENTER)
        text("DONE!!!",WIDTH/2,HEIGHT/2)
        --run the game and whatnot
    end
end
function genterrain(max)
    for i=1,max do
        --terrain gen stuff
        if i%number_of_times_per_frame == 0 then
            numdone = i
            coroutine.yield() --pause the coroutine to restart at the next coroutine.resume()
        end
    end
    load = false --we are now done loading!
end

I think in this case it wouldn’t help much, it’s better to get terrain generation out of the way early because it hammers FPS

besides, you can’t draw anything interesting until you have terrain

This idea was only for an initial load, to get terrain generation out of the way without having an ugly single-frame loading screen that, depending on terrain complexity, could take a while.

I’ve also used the co-routine approach, I did a demo where I generate my images in a co-routine and then yield every time a new image in the list is complete, I then use that in the draw routine to update a progress bar.

However a simpler solution would be to just change the generation routine so that it just does a small amount of work each frame and then call the function from within your draw routine just as you would for any other function (instant “multithreaded” cheat :slight_smile: )

The only thing you’ll need to be aware of is how you structure your data and how to split up the work.

For instance if you take a fractal approach (like @Ignatz’s diamond square), you could use a quad tree to represent your landscape and do an initial pass filling in the large areas (or start from an image to represent a low density height map) and then as you move around the landscape or get closer to a block you could add in extra detail as and when needed (removing / reusing old blocks so that the memory is free’d up) - if you then use a common seed and the noise function you should be able to go back and forth across the map and generate the same results.

I once wrote a 3D flight sim that did a similar thing and pulled in imagery / height data from disk and split the map into “blocks” and I just lazy loaded the blocks in as you moved across the map - it was quite a PITA to manage but it worked quite well.

Hi All,

@Mr_Ninja - I’m in the middle of ding something very similar. Started with the terrain building and am struggling to build my own Diamond Square algorithm in Lua. I know there are examples on the net and have used them but want to understand how it is programmed so that I can add more twists. @Ignatz, and other members have moved to shaders but, at the moment I don’t want to use them. I’s about half way with the terrain now - just need to get my own DS routine running.

@TechDojo, my system is very similar to your 3D flight sim - did you post the code anywhere?

Keep us up to date on this - nice to have someone working in the same area.

Bri_G

:smiley:

@Bri_G - flying involves rotation, which is extremely complex and requires quaternions.

I had the huge advantage of working with our resident mathematician, LoopSpace, and even he found it difficult. I did several posts on it, and I also included a lot in my ebook on 3D, all of which is there if you need it.

Flying is also rather boring unless there’s something to do like shooting at other planes, so I also did a lot of work on importing 3D models.

I only used shaders to tile an image across the terrain mesh. It is extremely fast and efficient and makes the code much simpler (one line of shader code).

@Bri_G - sorry no, it was for a company I worked for back in the late '90’s (which has subsequently folded), if I ever do find the code I doubt it would be of much use. The core idea was that the “big map” was just a 2D array of blocks (which represented a square chunk of the map) and as the player moved around from chunk to chunk the background engine would start to swap in / out chunks around the player so that there was always a new chunk to fly in / look at - a bit like minecraft really. There was more that could have been done, fractally subdividing the chunks down further and further, but we didn’t go in to too much detail as the camera was a fair way from the ground and it wouldn’t have made that much difference.

I just did a quick google for “Diamond Square Algorthim” and there were over 100,000 hits (https://www.google.co.uk/search?q=diamond+square+algorithm&espv=2&biw=1261&bih=720&source=lnms&sa=X&ei=OAJvVOXGHs_jaIPtgrAM&sqi=2&pjf=1&ved=0CAsQ_AUoAA&dpr=2)

This Java based one looked interesting http://www.javaworld.com/article/2076745/learn-java/3d-graphic-java--render-fractal-landscapes.html

@Ignatz - I’ve been looking at your tile shader and I’m going to play with a TileMap shader and hopefully port this http://blog.tojicode.com/2012/07/sprite-tile-maps-on-gpu.html to Codea. It’d be great to be able to run a full scrollable tile map on a single quad.

Hi @TechDojo,

Thanks for the feedback, already ran a DSA google scan and lots of interesting hits. Problem is most of them are in other coding languages. Javascript not a problem but Java I’m not too hot on. General algorithms similar but I’m struggling with. I did find a lua based one here but I prefer to understand it so that I can add my own twists. You know what it’s like trying to digest other peoples code.

On the TileMap front the example looks very impressive, a little like ‘Rick Dangerous’ from my old Amiga days. Good luck with it.

Thanks

Bri_G

:smiley: