Third person 3d

I want the box to move in accordance to the height of the terrain. This is my code.

-- Use this function to perform your initial setup
displayMode(FULLSCREEN)
function setup()
    stick = Stick()

    pylon = Wall("Cargo Bot:Crate Red 2")
    wall = Wall("Cargo Bot:Crate Yellow 2")
    hero = Hero(1,0,0)

    TO_DEG = 180/math.pi
    sin,cos,rad=math.sin,math.cos,math.rad
    --UserDefined vars
    TerrainRes=64 --Resolution of terrain
    TerrainSize=9--Size in x and z
    TerrainHeight=4
    --Movement
    Angle=vec2(0,0)
    parameter.number("X",-1,2,1)
    parameter.number("Y",-1,2,1)
    parameter.number("Z",-1,2,0)
    --Variables
    Ires=TerrainSize/TerrainRes
    EpicTerrain=image(TerrainRes/2,TerrainRes/2)
    setContext(EpicTerrain)
    sprite("Documents:height amp") --IMPORTANT! Your heightmap here!!
    setContext()
    --Meshing
    local X,Index,px,py,y1,y2,y3,y4
    m=mesh()
    verts={}
    for x=1,TerrainRes-1 do
        X=(x-1)*(TerrainRes-1)*6+1
        px=(x-1)/TerrainRes px=px*TerrainSize
        for y=1,TerrainRes-1 do
            Index=X+(y-1)*6
            py=(y-1)/TerrainRes py=py*TerrainSize
            --All the heights
            y1=color(EpicTerrain:rawGet(x,y)).x/255*TerrainHeight
            y2=color(EpicTerrain:rawGet(x+1,y)).x/255*TerrainHeight
            y3=color(EpicTerrain:rawGet(x,y+1)).x/255*TerrainHeight
            y4=color(EpicTerrain:rawGet(x+1,y+1)).x/255*TerrainHeight
            --Constructing the vertices
            verts[Index]=vec3(px,y1,py)
            verts[Index+1]=vec3(px+Ires,y2,py)
            verts[Index+2]=vec3(px+Ires,y4,py+Ires)
            verts[Index+3]=vec3(px+Ires,y4,py+Ires)
            verts[Index+4]=vec3(px,y3,py+Ires)
            verts[Index+5]=vec3(px,y1,py)
        end
    end
    m.vertices=verts
    m.shader=shader(MSV,MSF)
    m.shader.TerrainSize=TerrainSize*1.5
end



-- This function gets called once every frame
function draw()
    background(0)
    local TO_DEG = TO_DEG
    local hero = hero
    
    perspective(60)
    camera(hero.x, 2, 1+hero.z, hero.x, 1, hero.z, 0, 1, 0)
    m:draw()
-- Draw world


-- Draw hero
    translate(hero.x, hero.y, hero.z)
    rotate(stick.direction*TO_DEG, 0, 1, 0)

    -- roll animation
    if stick.active then
        rotate(-ElapsedTime*10*TO_DEG, 0, 0, 1)
    end

    scale(.25, .25, .25)
    hero:draw()

-- Restore orthographic projection

    -- fade out overlay

    if stick.active then
        local ceil = math.ceil

        -- move hero based on stick direction
        local mvtx = math.cos(stick.direction)/50*stick.dist
        local mvtz = -math.sin(stick.direction)/50*stick.dist
        hero.x = hero.x + mvtx
        hero.z = hero.z + mvtz
        hero.y = 1
        -- convert to table coordinates
        hero.px = ceil(hero.x - .5)
        hero.py = ceil(hero.z - .5)

        -- lazy collision check
    end
    ortho()
    resetMatrix()
    viewMatrix(matrix())
    sprite(EpicTerrain,0,0,300,300)
end

function touched(touch)
    stick:touched(touch)
end

--# World



--# Hero
Hero = class()

function Hero:init(x,y,z)
    self.x, self.y, self.z = x,y,z
    self.px, self.py = math.ceil(.5+x), math.ceil(.5+z)

    self.mdl = Wall("Cargo Bot:Crate Blue 2")
end

function Hero:draw()
    self.mdl:draw()
end


--# Wall
Wall = class()


function Wall:init(tex)
    -- all the unique vertices that make up a cube
    local vertices =
    {
        vec3(-0.5, -0.5,  0.5), -- Left  bottom front
        vec3( 0.5, -0.5,  0.5), -- Right bottom front
        vec3( 0.5,  0.5,  0.5), -- Right top    front
        vec3(-0.5,  0.5,  0.5), -- Left  top    front
        vec3(-0.5, -0.5, -0.5), -- Left  bottom back
        vec3( 0.5, -0.5, -0.5), -- Right bottom back
        vec3( 0.5,  0.5, -0.5), -- Right top    back
        vec3(-0.5,  0.5, -0.5), -- Left  top    back
    }

    -- now construct a cube out of the vertices above
    local verts =
    {
        -- Front
        vertices[1], vertices[2], vertices[3],
        vertices[1], vertices[3], vertices[4],
        -- Right
        vertices[2], vertices[6], vertices[7],
        vertices[2], vertices[7], vertices[3],
        -- Back
        vertices[6], vertices[5], vertices[8],
        vertices[6], vertices[8], vertices[7],
        -- Left
        vertices[5], vertices[1], vertices[4],
        vertices[5], vertices[4], vertices[8],
        -- Top
        vertices[4], vertices[3], vertices[7],
        vertices[4], vertices[7], vertices[8],
       -- Bottom
        vertices[5], vertices[6], vertices[2],
        vertices[5], vertices[2], vertices[1],
    }

    -- all the unique texture positions needed
    local texvertices =
    {
        vec2(0,0),
        vec2(1,0),
        vec2(0,1),
        vec2(1,1)
    }

    -- apply the texture coordinates to each triangle
    local texCoords =
    {
        -- Front
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Right
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Back
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Left
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Top
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Bottom
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
    }

    self.model = mesh()
    self.model.vertices = verts
    self.model.texture = tex
    self.model.texCoords = texCoords
    self.model:setColors(255,255,255,255)
end

function Wall:draw()
    self.model:draw()
end
--# Floor


--# Stick
Stick = class()

function Stick:init()
    self.direction = 0
    self.dist = 0

    self.active = false
    self.origin = vec2(150, 150)
    self.center = self.origin
    self.pos = self.origin


end


function Stick:touched(touch)
    if touch.state == BEGAN then
        self.center = vec2(touch.x, touch.y)
        self.active = true
    end

    self.pos = vec2(touch.x, touch.y)
    self.direction = math.atan2(self.pos.y - self.center.y, self.pos.x - self.center.x)

    self.dist = math.min(2, self.pos:dist(self.center)/32)

    if touch.state == ENDED then
        self.center = self.origin
        self.pos = self.center
        self.active = false
    end


end
MSV=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
varying vec3 Pos;

void main() {
    Pos=position.xyz;
    gl_Position=modelViewProjection*position;
}
]]

MSF=[[
precision highp float;
varying vec3 Pos;
uniform vec3 Player;
uniform float TerrainSize;

void main() {
    gl_FragColor=vec4(pow(vec3(length(Player-Pos)/TerrainSize),vec3(0.45)),1.);
}
]]

@dmoeller - what you are doing is very similar to something I am working on. I put one of my heightmap in it but the range is too high, looks like yours is set to 4 and mine is set to 255. Since the terrain is not coloured it’s very difficult to asses the heights in general. But, in my case, the hero as you call him will stay in one place and the terrain will move downwards.

You may need to change the camera viewpoint to see the effect more. Also I’m assuming you will be using a shadow to show the depth etc.

How do you color the terrain?

@dmoeller - I read the colour of the heightmap and attribute that colour to each of vertices for the two triangles in each rectangle used for the terrain - in a mesh. You could use shaders for this, it’s a bit of a patchwork effect, you can reduce that by narrowing the colour difference across your terrain. What kind of terrain have you in mind?

You can also apply textures to the terrain to pattern the areas.

I have also experimented with gradient shading across the terrain but so far I’m not impressed, it sort of glows and features are blended away.

Also it’ better if you keep your hero central and scroll your landscape.

I want to make a 3d person shooter with grassy terrain, could I see your code so I can build off it?

@dmoeller - my code is not that advanced, it would be better to identify component parts and build them up in the forum then everyone can learn from them.

If you’d like to start with a terrain give us an outline of what you need and your problems, a demo of something similar to your own terrain for us to start with. There are lots of experienced programmers on this forum, so progress should be good.

Are you planning to base it on Craft or mainly on meshes?

Also have you checked out Ignatz’s post on Coolcodea - covers most of what you’ll need.

First I would want to make a noise function for the terrain and shade it with a grassy textur

This can be the texture

I have checked out @Ignatz posts on cool codea but, It always says invalid sprite pack name

@dmoeller - right, thanks for the input. The first thing to do is rename your texture to something easier say grass01, and copy it to your documents folder or Dropbox root folder. Have you got a small heightmap to use as a demo?

I’ll start looking at code.

My height map was going to be a 3d noise function

@dmoeller - which module of Ignatz were you referring to with the spritepack error?

Any one with this function

function LoadImage(fileName,url)
        local i=readImage(fileName)
        if i~=nil then return i end 
        --not found, we need to download, add to queue (ie table)
        if imageTable==nil then imageTable={} end
        imageTable[#imageTable+1]={name=fileName,url=url}
        print('Queueing',fileName)
        imageStatus='Loading'
        --if the first one, go ahead and download
        if #imageTable==1 then 
            http.request(imageTable[1].url,ImageDownloaded) 
            print('loading',imageTable[1].name)
        end
end

@dmoeller - That routine looks like one he used for setting up his 3D demos with a host of graphics for building up a village which you could walk around. Is that what you’re baseing your RPG on?

The graphics there are all downloaded from a web page, if the web page is deleted or moved address you won’t be able to access the graphics. I’ll check this out.

Edit: Yeah, that routine is part of several routines to download specific graphics from his photobucket drive. Have you downloaded all of the graphics he has on there for his early 3D terrains? If so I believe his code also downloads all his graphics down to you iPad, you should be able to address them from your own storage if so. I have them all in my Dropbox account, you may find them in your Documents resource.

Sorry, I posted the wrong function, this is the one my iPad doesn’t like

function ImageDownloaded(img)
    print(imageTable[1].name,'loaded')
    saveImage(imageTable[1].name,img)  --save
    table.remove(imageTable,1)
    --load next one if we have any more to do
    if #imageTable>0 then
        http.request(imageTable[1].url,ImageDownloaded)
        print('loading',imageTable[1].name)
    else
        LoadImages()
    end
end

How do you make a 3d noise function for terrain?

I tried modifying @Ignatz code and this is what I got, for some reason the table is nil, could you help? I attached the pictures I used in the setup function

Here is my code

--# Main
function setup()
    gravel=readImage("Documents:gravel")
    tree=readImage("Documents:tree")
    grass=readImage("Documents:grass")
    imageStatus="Ready"
    setup2()
end
    
function setup2()
    parameter.integer("Viewpoint",-2000,2000,20)
    speed,angle=0,0
    ds,da=0,0
    posX,posY,posZ=-20,0,10 --starting position
    rad=math.pi/180
    meshTable={}
    count=0
    meshTable[1],meshTable[2]={},{}
    AddLevel(-500,0,0,1000,1000,gravel,.1)
    HM={} --holds height maps, one for each terrain area we build -- NEW
    --add a terrain area --NEW
    AddTerrain({x=-400,y=0.5,z=100,width=800,depth=800,tileSize=50,minY=10,maxY=100,random=false,
                noiseLevel=.2,noiseStep=.5,img=grass,scaleFactor=.1,heightMaps=HM})
    --add 200 trees
    for i=1,200 do
        local x=math.random(-375,375)
        local z=math.random(150,750)
        local w=math.random(20,60)
        AddItem(x,0,z,w,tree) --rotating to face us
    end
    count=0
    FPS=60
end

function draw()
    if imageStatus~="Ready" then return end
    count=count+1
    background(220)
    perspective(45,WIDTH/HEIGHT)
    if Viewpoint<10 then Viewpoint=10 end
    angle=angle+da*DeltaTime
    posX,posZ=posX+ds*DeltaTime*math.sin(angle*rad),posZ+ds*DeltaTime*math.cos(angle*rad)
    local h0=posY  --NEW
    posY=math.max(Viewpoint,HeightAtPos(posX,posZ)+10) --NEW
    local h1=posY  --NEW
    --try to look up if climbing or down if descending --NEW
    if posY==Viewpoint then lookY=20 else lookY=(h1-h0)*100/ds+10 end
    --look in the same direction as we are facing
    lookX,lookY,lookZ=posX+1000*math.sin(angle*rad),20,posZ+1000*math.cos(angle*rad)  
    camera(posX,posY,-posZ,lookX,lookY,-lookZ, 0,1,0)
    if count%20==1 then 
        table.sort(meshTable[2],
        function(a,b) return 
            vec2(posX,-posZ):dist(vec2(a.pos.x,-a.pos.z))>vec2(posX,-posZ):dist(vec2(b.pos.x,-b.pos.z)) end)
    end
    for k=1,2 do
        for i,m in pairs(meshTable[k]) do
            --rotate free standing objects if required, so they face us
            if m.rotate==true then
                pushMatrix()
                translate(m.pos.x,m.pos.y,-m.pos.z)
                local dx,dz=m.pos.x-posX,-m.pos.z+posZ
                local ang=math.atan(dx/-dz)/rad
                rotate(-ang,0,1,0)
                m:draw()
                popMatrix() 
            elseif m.ang==nil then m:draw()
            else
                pushMatrix()
                translate(m.pos.x,m.pos.y,-m.pos.z)
                rotate(-m.ang,0,1,0)
                m:draw()
                popMatrix() 
            end
        end
    end
    ortho() --this is needed to get text written on the screen
    viewMatrix(matrix()) --and this
    pushStyle()
    fill(0)
    fontSize(18)
    strokeWidth(3)
    textMode(CORNER)
    text("x="..string.format("%  d",posX),50,HEIGHT-70)
    text("y="..string.format("%  d",posY),50,HEIGHT-90)
    text("z="..string.format("%  d",posZ),50,HEIGHT-110)
    popStyle()
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(...)
    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","scaleFactor","heightMaps","noiseStep"},arg) 
    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(1,math.min(i-1,j-1,nw+1-i,nd+1-j)*ns)
                --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
    local wx,wz=img.width*sc,img.height*sc
    v,t={},{}
    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)  --bottom left
            v[#v+1]=vec3(x2,y2,-z2) t[#t+1]=vec2(x2/wx,z2/wz)  --bottom right
            v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz)  --top right
            v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz)  --top right
            v[#v+1]=vec3(x4,y4,-z4) t[#t+1]=vec2(x4/wx,z4/wz)  --top left
            v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(x1/wx,z1/wz)  --bottom left
        end 
    end
    local m=mesh()
    m.texture=img
    m.vertices=v
    m:setColors(color(255))
    m.texCoords=t 
    m.pos=vec3(x,y,z)
    m.shader = shader(autoTilerShader.vertexShader, autoTilerShader.fragmentShader)
    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
    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,mz=1+math.floor((x-v.x1)/v.p),1+math.floor((z-v.z1)/v.p)
            --use bilinear interpolation (most common method) for interpolating 4 corner values
            local px,pz=1+(x-v.x1)/v.p-mx,1+(z-v.z1)/v.p-mz
            h=v.t[mx][mz]*(1-px)*(1-pz)+
                    v.t[mx+1][mz]*px*(1-pz)+
                    v.t[mx+1][mz+1]*px*pz+
                    v.t[mx][mz+1]*(1-px)*pz
            break
        end
    end
    return h
end

function AddItem(x,y,z,w,i,r) --centred on x
    y=HeightAtPos(x,z)
    local h=i.height*w/i.width
    local m=mesh()
    m.texture=i
    local v,t={},{}
    v,t=AddImage(-w/2,0,0,w,h,0,v,t)
    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.texCoords=t 
    table.insert(meshTable[2],m)
end

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

function touched(touch)
    local center=true
    if touch.x<WIDTH/4 then
        da=da-2
        center=false
    elseif touch.x>WIDTH*3/4 then 
        da=da+2
        center=false
    end
      
    if touch.y<HEIGHT/4 then 
        ds=ds-3
        center=false
    elseif touch.y>HEIGHT*3/4 then 
        ds=ds+3
        center=false
    end
    
    if center then ds=0 da=0 end
end

function Params(list,tbl) --tbl is params
    if type(tbl[1])=="table" then --was named table
        local t={} --match the named params with the list provided
        for n,v in pairs(tbl[1]) do
            for i=1,#list do
               if n==list[i] then 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 LoadImages()
    imageStatus="Ready" --tells draw it's ok to draw the scene (will be turned off if we have to download images)
    output.clear()
    --pass through Codea name of image and internet url
    --not in Codea, will be downloaded and saved
    grass=LoadImage("Dropbox:3D-grass2",
            "http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-grass10_zps7573c68c.png")
    gravel=LoadImage("Dropbox:3D-gravel",
            "http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-gravel11_zpsc3a6ac7d.png")
    tree=LoadImage("Dropbox:3D-tree2",
            "http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-tree21c_zps655e421c.png")
    if imageStatus=="Ready" then setup2() end
end

--downloads images one by one
function LoadImage(fileName,url)
        local i=readImage(fileName)
        if i~=nil then return i end 
        --not found, we need to download, add to queue (ie table)
        if imageTable==nil then imageTable={} end
        imageTable[#imageTable+1]={name=fileName,url=url}
        print('Queueing',fileName)
        imageStatus='Loading'
        --if the first one, go ahead and download
        if #imageTable==1 then 
            http.request(imageTable[1].url,ImageDownloaded) 
            print('loading',imageTable[1].name)
        end
end

--saves downloaded images
function ImageDownloaded(img)
    print(imageTable[1].name,'loaded')
    saveImage(imageTable[1].name,img)  --save
    table.remove(imageTable,1)
    --load next one if we have any more to do
    if #imageTable>0 then
        http.request(imageTable[1].url,ImageDownloaded)
        print('loading',imageTable[1].name)
    else
        LoadImages()
    end
end
--# Tiler
autoTilerShader = {
vertexShader = [[
//
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    vTexCoord = texCoord;

    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}

]],
fragmentShader = [[
//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
    //Sample the texture at the interpolated coordinate
    lowp vec4 col = texture2D( texture, vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))) * vColor;

    //Set the output color to the texture color
    gl_FragColor = col;
}

]]}

@dmoeller I modified some code I had. Can you use this. Slide your finger around to move the image. I copied your grass image to my Dropbox folder and named it grass.

displayMode(FULLSCREEN)

function setup()
    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(255, 255, 255, 255)
    skyMaterial.horizon=color(255, 255, 255, 255)
    scene.sun.rotation=quat.eulerAngles(30,30,0)
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 100, 30, 2000)
    v.rx=25
    
    texture=readImage("Dropbox:grass")  -- your grass image
    
    groundHeight=30
    createGround()
end

function draw()
    update(DeltaTime)
    scene:draw() 
end

function update(dt)
    scene:update(dt)
end

function golfBall(p,c)
    pt=scene:entity()
    pt.position=vec3(p.x,p.y+1,p.z)
    pt.model = craft.model.icosphere(1,2,1)
    pt.material = craft.material("Materials:Standard")
    pt.material.diffuse=color(255)
end

function createGround()
    local tab,pos,ind,col,uv={},{},{},{},{}
    local h=craft.noise.perlin()
    local step=.1
    local xx=0
    for x=-1,1,step do
        xx=xx+1
        tab[xx]={}
        local zz=0
        for z=-1,1,step do
            zz=zz+1
            local v=h:getValue(x,0,z)
            tab[xx][zz]=vec3(x*300,v*groundHeight,z*300)
            if math.random(100)>80 then -- draw random golf balls on grass
                golfBall(vec3(x*300,v*groundHeight-.2,z*300))
            end
        end
    end
    for x=1,#tab-1 do
        for z=1,#tab-1 do
            table.insert(pos,tab[x][z])
            table.insert(pos,tab[x+1][z+1])
            table.insert(pos,tab[x][z+1])
            table.insert(pos,tab[x][z])
            table.insert(pos,tab[x+1][z])
            table.insert(pos,tab[x+1][z+1])
        end
    end
    for z=1,#pos do
        table.insert(ind,z)
        table.insert(col,color(255,255,255,255))
    end
    for z=#pos,1,-1 do
        table.insert(ind,z)
    end
    for z=1,#pos/6 do
        table.insert(uv,vec2(0,0))
        table.insert(uv,vec2(1,1))
        table.insert(uv,vec2(0,1))  
        table.insert(uv,vec2(0,0))
        table.insert(uv,vec2(1,0))
        table.insert(uv,vec2(1,1))  
    end
    local pt=scene:entity()
    pt.model = craft.model()
    pt.model.positions=pos
    pt.model.indices=ind
    pt.model.colors=col
    pt.model.uvs=uv
    pt.material = craft.material("Materials:Basic")  
    pt.material.map=texture
end