Scrolling a large mesh

Hi All,

I’ve been looking at scrolling a large mesh by selecting a section for display and changing the coordinates on the mesh itself so only part of the mesh is visible.@dave1707 has demonstrated scrolling on a flat plane with a single texture stretched over the plane. It works for flat planes. I would also like to mask out the surrounding area so the plane appears like a table-top view.

@Ignatz did something with his 3D racing course but filled the screen with the view.

Anyone delved into this area?

what do you mean by changing the coords? tell us more about the idea? and maybe the purpose.

@RonJeffries - I have scrolled images by taking a grid and reading a subset by using an offset from x and y zeros. But, it’s just a 2D image. I’d like to know if it’s possible to build a mesh, which could introduce 3D, and scroll using a subset and offsets in a similar way to 2D. Your comments on flood filling with chunks sounded promising. I think that Craft will have the capabilities to do this but I’m slow in picking up Craft.

It’s basically like displaying a map with a magnifying glass and moving the map around looking for features.

the voxel stuff is pretty nice if you can live with the minecraft look. here’s an article based on dave’s example, with some pics.

@RonJeffries - I appreciate the amount of work you put into that article and unravelling the mysteries of the threaded voxel terrain generator.

What I could potentially do is add a craft.noise.custom noise generator, that takes in a Lua function and runs that when :getValue(x, y, z) is called, which could then be passed to volume:setWithNoise() allowing you to populate a volume fairly easily

Potential Example:

local myVolume = scene:entity():add(craft.volume, 100, 25, 100)
local air = 0
local dirt = myVolume:blockID("dirt") -- Assuming block types have already been defined
local myNoise = craft.noise.custom(function(x, y, z) 
  if y > 0.5 then 
    return air
  else
    return dirt
  end
end)
myVolume:setWithNoise(myNoise)

@Bri_G It seems like what you want is a terrain system. This can be done by making a series of square chunks, which you make visible depending on whether or not they are visible to the camera (via frustum culling).

Craft already does frustum culling, although depending on the map size you would need to disable entities that are not visible for performance reasons.

Generally for generic terrain you’ll want to make each section a grid mesh, where you vary the y (or height) to get the shape you want.

@Bri_G i think you have used @Ignatz terrain generation, is that not okay? I use his code, but replace the mesh with a @LoopSpace pseudoMesh so that it is compatible with the craft framework.

@piinthesky - I have looked at the @ignatz code and generally found it really good I sometimes struggle to adapt and get lost or distracted - so I tend to shelve partially finished projects. When you mention the @Ignatz terrain code which particular code are you referring to - he has demonstrated a walk through village, a racing game, a battle at sea and a tank battle - all very well presented?

On the @Loopspace PseudoMesh I have tried to get that working but haven’t been able to integrate it into my own code. I was hoping to build a mesh to .obj exporter so that I could convert old mesh objects to Craft loadable objects.

I use this from @Ignatz modified to use PseudoMesh from @LoopSpace.
I think it from Ignatz project with the trees (but not used here).

function terrain_setup()
    print("in terrain setup")
    rad=math.pi/180
    col=color(138, 92, 36, 255)
    meshTable={}
    meshTable[1]={}
    meshTable[2]={}   
 --   AddLevel(-500,0,0,1000,1000,gravel,.1)
    HM={} --holds height maps, one for each terrain area we build
    --add a terrain area --NEW
-- x,z = bottom left corner
-- y = height around the edges
-- w,d = width and depth of area to be terrained (pixels)
-- p = pixels per tile
-- f = minimum height (pixels), can be lower than y
-- h = max height (pixels)
-- r = whether to randomise terrain each run (true/false)
-- n = noise level (bigger numbers result in bumpy terrain, smaller gives smoother results)
-- tbl = table of heights, if provided
-- img = image or string name of image (do not include library name)
-- sc = image scaling factor (see above) 0-1
-- hm = height map storing heights of each point in 
-- ns = the fraction by which noise level increases for each tile as we go deeper into the terrain
--      eg a value of 0.3 means that the outside ring of tiles has nil noise (as always, so these tiles
--      exactly meet the surface around), the next ring of tiles has noise of 0.3 of the full level,
--      the third ring of tiles has noise of 0.6, and so on, up to a maximum of 1.0. The bigger ns, the
--      the more you will get cliffs at the edges of the terrain
    terrainWidthOver2=1000
    terrainDepthOver2=1000
    AddTerrain( {x=-terrainWidthOver2, y=0.0, z=-terrainWidthOver2,  
                width=terrainWidthOver2*2,depth=terrainDepthOver2*2,
                tileSize=25,minY=0,maxY=50,random=false,
                noiseLevel=0.05,noiseStep=0.3,sc=0.11,
--                img=gravel,
                heightMaps=HM} )

    terrainEnt=scene:entity()
    terrainEnt.active=True
  
    terrainEnt.position=offset
--     terrainEnt.position=vec3(0,0,0)
    local tmodel=meshTable[1][1]:toModel()
    terrainEnt.model=tmodel
      
 --terrainEnt.material = craft.material("Materials:Standard")
 terrainEnt.material = craft.material.preset("Surfaces:Desert Cliff")         
 --    terrainEnt.material = craft.material.preset("Surfaces:Stone Brick")         
--   terrainEnt.material.map = gravel    
 --  terrainEnt.material.map =readImage("Project:sand-seamless"):copy(1,1,512,512)
   terrainEnt.material.map =readImage("Project:TexturesCom_MuddySand2_2x2_1K_albedo")

--    print("vertex, index count",terrainEnt.model.vertexCount, indexCount)
    
end

-- x,z = bottom left corner
-- y = height around the edges
-- w,d = width and depth of area to be terrained (pixels)
-- p = pixels per tile
-- f = minimum height (pixels), can be lower than y
-- h = max height (pixels)
-- r = whether to randomise terrain each run (true/false)
-- n = noise level (bigger numbers result in bumpy terrain, smaller gives smoother results)
-- tbl = table of heights, if provided
-- img = image or string name of image (do not include library name)
-- sc = image scaling factor (see above) 0-1
-- hm = height map storing heights of each point in 
-- ns = the fraction by which noise level increases for each tile as we go deeper into the terrain
--      eg a value of 0.3 means that the outside ring of tiles has nil noise (as always, so these tiles
--      exactly meet the surface around), the next ring of tiles has noise of 0.3 of the full level,
--      the third ring of tiles has noise of 0.6, and so on, up to a maximum of 1.0. The bigger ns, the
--      the more you will get cliffs at the edges of the terrain
 
function AddTerrain(myarg)
    local x,y,z,w,d,p,f,h,r,n,tbl,--img,
    sc,hm,ns=
    Params({"x","y","z","width","depth","tileSize","minY","maxY","random","noiseLevel",
         "heightTable",
--    "img",
        "sc", "heightMaps","noiseStep"},myarg) 
    
   local nw,nd=math.floor(w/p),math.floor(d/p) --number of tiles in each direction
 
    --if no table of heights provided, generate one with noise
    if tbl==nil then
        n=n or 0.2 --default noise level
        tbl1,tbl2={},{} 
        --noise starts at 0 on the outside, increases by the step as we move inward, max of 1
        --noise is nil at edge, increases by 30% per tile inwards, to 100% 
        local min,max=9999,0
        --if user wants a random result each time. add a random number to the noise results
        if r==true then rnd=math.random(1,10000) else rnd=0 end
        for i=1,nw+1 do
            tbl1[i],tbl2[i]={},{}
            for j=1,nd+1 do
                --noise fraction for this tile, based on how close to the edge it is
                --this formula counts how many rows in from the edge this tile is
 --               tbl1[i][j]=math.min(i-1,j-1,nw+1-i,nd+1-j)
  --              tbl1[i][j]=10-math.min(i-1,j-1,nw+1-i,nd+1-j)    --added by me
                tbl1[i][j]=20-j*2   --added by me
                if tbl1[i][j]<0 then tbl1[i][j]=0.0 end        --added by me
                tbl1[i][j]=tbl1[i][j]*ns
 --               print(i,j,  math.min(i-1,j-1,nw+1-i,nd+1-j) )
                --the noise itself
                tbl2[i][j]=noise(rnd+i*n, rnd+j*n) 
                --keep track of min and max values of noise
                if tbl2[i][j]<min then min=tbl2[i][j] end
                if tbl2[i][j]>max then max=tbl2[i][j] end
            end
        end
        --now go back through the whole array and scale it
        --we know the user wants values between f and h
        --we know our noise varies between min and max
        --so now we pro rate all our values so they vary between f and h, based on the noise values
        for i=1,nw+1 do
            for j=1,nd+1 do
                local f1=y
                --pro rate
                local f2=f+(h-f)*(tbl2[i][j]-min)/(max-min)
                --now apply noise fraction, to reduce noise around the edges
                tbl2[i][j]=f1*(1-tbl1[i][j])+f2*tbl1[i][j]
            end
        end       
    end
    --store details for later use in determining height
    table.insert(hm,{x1=x,z1=z,x2=x+w,z2=z+d,p=p,t=tbl2})
    
    --create the vectors and mesh
 --   print("sc",sc)
    
 --   local wx, wz=img.width*sc, img.height*sc    --connecred with bug   
    local wx,wz= 400,400
    v,t,c={},{},{}
--    col=color(208, 160, 42, 255)

    for i=1,nw do
        for j=1,nd do
            local x1,x2=x+(i-1)*p,x+i*p  local x3,x4=x2,x1
            local y1,y2,y3,y4=tbl2[i][j],tbl2[i+1][j],tbl2[i+1][j+1],tbl2[i][j+1]
            local z1,z3=z+(j-1)*p,z+j*p  local z2,z4=z1,z3
            v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(x1/wx,z1/wz)   c[#c+1]=col--bottom left
            v[#v+1]=vec3(x2,y2,-z2) t[#t+1]=vec2(x2/wx,z2/wz)   c[#c+1]=col--bottom right
            v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz)   c[#c+1]=col--top right
            v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz)   c[#c+1]=col--top right
            v[#v+1]=vec3(x4,y4,-z4) t[#t+1]=vec2(x4/wx,z4/wz)   c[#c+1]=col--top left
            v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(x1/wx,z1/wz)   c[#c+1]=col--bottom left
            
        end 
    end
    local norms=CalculateNormals(v)
    local m=PseudoMesh()
--    local m=mesh()
--    m.texture=img
    m.vertices=v
    m.normals=norms
--    m:setColors(color(255))
    m.colors=c
    m.texCoords=t 
    m.pos=vec3(x,y,z)
    print("terrain x y z",x,y,z)
--    m.shader = shader(autoTilerShader.vertexShader, autoTilerShader.fragmentShader)  
--    m.shader=mega()
    
    table.insert(meshTable[1],m)
end

--calculates height at x,z
function HeightAtPos(x,z)
    --identify the location and calculate height
    local h=0 --set default as 0
    local i,v
    for i,v in pairs(HM) do  --look in each terrain area
        if x>=v.x1 and x<=v.x2 and z>=v.z1 and z<=v.z2 then --if in this area...
            --calc which square we are in
            local mx=1+math.floor((x-v.x1)/v.p)
            local mz=1+math.floor((z-v.z1)/v.p)
            --use bilinear interpolation (most common method) for interpolating 4 corner values
--            local px,pz=(x-v.x1)/v.p-mx,(z-v.z1)/v.p-mz

            local px,pz=1+(x-v.x1)/v.p-mx,1+(z-v.z1)/v.p-mz
--            output.clear()
--            print(x,z)
--            print (mx+px,mz+pz)

            h=      v.t[mx][mz]*     (1-px)*(1-pz)+
                    v.t[mx+1][mz]*   px*(1-pz)+                 
                    v.t[mx][mz+1]*   (1-px)*pz+
                    v.t[mx+1][mz+1]* px*pz
            break
        end
    end
    return h
end

Need these as well

function AddItem(x,y,z,w,i,r) --centred on x
    y=HeightAtPos(x,z)
    local h=i.height*w/i.width
    local m=PseudoMesh()
    m.texture=i
    local v,t,c={},{},{}
    v,t=AddImage(-w/2,0,0,w,h,0,v,t,c)
    m.pos=vec3(x,y,z)
    if r~=nil then m.ang=r else m.rotate=true end
    m.vertices=v
 --   m:setColors(color(255))
    m.colors=c
    m.texCoords=t 
    table.insert(meshTable[2],m)
end

function AddImage(x,y,z,w,h,d,v,t,c)
    v[#v+1]=vec3(x,y,z)  t[#t+1]=vec2(0,0)  c[#c+1]=color(255)
    v[#v+1]=vec3(x+w,y,z-d)  t[#t+1]=vec2(1,0)   c[#c+1]=color(255)
    v[#v+1]=vec3(x+w,y+h,z-d)  t[#t+1]=vec2(1,1)   c[#c+1]=color(255)
    v[#v+1]=vec3(x+w,y+h,z-d)  t[#t+1]=vec2(1,1)   c[#c+1]=color(255)
    v[#v+1]=vec3(x,y+h,z)  t[#t+1]=vec2(0,1)   c[#c+1]=color(255)
    v[#v+1]=vec3(x,y,z)  t[#t+1]=vec2(0,0)   c[#c+1]=color(255)
    return v,t,c
end
            
function AddLevel(x,y,z,w,d,i,s)
    local m=PseudoMesh()
--    m.texture=i
    local v,t,c={},{},{}
    local nx,nz=w/i.width/s,d/i.height/s
    v[#v+1]=vec3(x,y,-z)  t[#t+1]=vec2(0,0) c[#c+1]=color(255)
    v[#v+1]=vec3(x+w,y,-z)  t[#t+1]=vec2(nx,0) c[#c+1]=color(255)
    v[#v+1]=vec3(x+w,y,-z-d)  t[#t+1]=vec2(nx,nz) c[#c+1]=color(255)
    v[#v+1]=vec3(x+w,y,-z-d)  t[#t+1]=vec2(nx,nz) c[#c+1]=color(255)
    v[#v+1]=vec3(x,y,-z-d)  t[#t+1]=vec2(0,nz) c[#c+1]=color(255)
    v[#v+1]=vec3(x,y,-z)  t[#t+1]=vec2(0,0) c[#c+1]=color(255)
    m.vertices=v
 --   m:setColors(color(255))
    m.colors=c
    m.texCoords=t 
    m.pos=vec3(x,y,z)
--    m.shader = shader(autoTilerShader.vertexShader, autoTilerShader.fragmentShader)
    table.insert(meshTable[1],m)
end

function Params(list,tbl) --tbl is params
if type(tbl)=="table" then --was named table
        print("type is table")
        local t={} --match the named params with the list provided
        for n,v in pairs(tbl) do
            for i=1,#list do
               if n==list[i] then 
                    print(list[i],v)
                    t[i]=v 
                    break 
                end 
            end     
        end  
        return unpack(t)
  else --was an unnamed list, return it as is
     return unpack(tbl) 
  end
end


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()
        norm[i] = n --then apply it to all 3
        norm[i+1] = n
        norm[i+2] = n
    end
    return norm
end  

@piinthesky - thanks for the posts, well annotated and will take a little digesting. I’ve been trolling the net for terrain links for a week or so (have to make a list of references) all good info but describing principles isn’t as good as presenting well annotated working code

@Simeon - just reading through a number of posted code examples and find that sometimes the annotations can detract from reading code when added to Codea. I like the idea of collapsible functions to tidy code whilst developing and wonder if it would be possible to try applying it to comments so that we can have annotations local but collapsible.

I have examples where annotations have covered a couple of iPad screen pages. I tend to number big annotations and append a numbered list in a tab to use for that purpose.

@John the noise.custom would be nifty and possibly useful. I would imagine it wouldn’t be terribly hard to do. I’d have (first) used it as a debug tool to learn what was going on, but I can see it might be useful for other purposes. It might even be possible to use it to hook things like tree generators directly into a noise tree. That’s over my head at the moment but it would certainly open some doors.

@piinthesky - first impressions - woah, so many options. Busy trying to put my images in place - so far got it up and running no errors but no image at the moment. Dig deeper!!!

@RonJeffries If you let me know what the problem was with PseudoMesh I could take a look for you.

thanks @LoopSpace but it wasn’t me with a pseudo mesh prob.

@LoopSpace - Hi, I made a comment of not being able to integrate Pseudomesh into a project - I don’t think the issue was with Pseudomesh it was more a lack of understanding on my part how to integrate it into my code. My intention was to use Pseudomesh to essentially build .obj files from a mesh in order to save the .obj and .mtl files for use in the Craft environment directly. It was some time ago when Craft had just been launched and I was looking for a way to upgrade my projects to Craft. In the end I searched the internet for similar free .obj models and found a few to play with. Thanks for the interest not sure if conversion is relevant now although Craft coding isn’t as prominent as I thouht it would be and there may be a few interested members.

Sorry … lost track of who’s who in the thread.