Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --# Frustum
- -- Frustum Culling --
- ------------------------------------------------------
- -- point distance from plane: distance = a*x + b*y + c*z + d
- function sphereInFrustum(f, s)
- local d
- local fp
- local sx = s.x
- local sy = s.y
- local sz = s.z
- local sr = s.r
- for p=1, 6 do
- fp = f[p]
- d = fp.a * sx + fp.b * sy + fp.c * sz + fp.d
- if d<= -sr then
- return 0
- end
- end
- d = d + sr
- -- distance based...not great but damn cheap
- if (d < 250) then
- return 1
- else
- if (d < 500) then
- return 2
- else
- if (d < 750) then
- return 3
- else
- return 4
- end
- end
- end
- return d
- end
- -- Extract view frustum planes
- function extractFrustum()
- local mat = viewMatrix() * projectionMatrix()
- local a, b, c, d
- local frustum = {}
- local mat1 = mat[ 1]
- local mat2 = mat[ 2]
- local mat3 = mat[ 3]
- local mat4 = mat[ 4]
- local mat5 = mat[ 5]
- local mat6 = mat[ 6]
- local mat7 = mat[ 7]
- local mat8 = mat[ 8]
- local mat9 = mat[ 9]
- local mat10 = mat[10]
- local mat11 = mat[11]
- local mat12 = mat[12]
- local mat13 = mat[13]
- local mat14 = mat[14]
- local mat15 = mat[15]
- local mat16 = mat[16]
- -- Right plane
- a = mat4 - mat1
- b = mat8 - mat5
- c = mat12 - mat9
- d = mat16 - mat13
- frustum[1] = Plane(a, b, c, d)
- frustum[1]:normalize()
- -- Left plane
- a = mat4 + mat1
- b = mat8 + mat5
- c = mat12 + mat9
- d = mat16 + mat13
- frustum[2] = Plane(a, b, c, d)
- frustum[2]:normalize()
- -- Bottom plane
- a = mat4 + mat2
- b = mat8 + mat6
- c = mat12 + mat10
- d = mat16 + mat14
- frustum[3] = Plane(a, b, c, d)
- frustum[3]:normalize()
- -- Top plane
- a = mat4 - mat2
- b = mat8 - mat6
- c = mat12 - mat10
- d = mat16 - mat14
- frustum[4] = Plane(a, b, c, d)
- frustum[4]:normalize()
- -- Far plane
- a = mat4 - mat3
- b = mat8 - mat7
- c = mat12 - mat11
- d = mat16 - mat15
- frustum[5] = Plane(a, b, c, d)
- frustum[5]:normalize()
- -- Near plane
- a = mat4 + mat3
- b = mat8 + mat7
- c = mat12 + mat11
- d = mat16 + mat15
- frustum[6] = Plane(a, b, c, d)
- frustum[6]:normalize()
- return frustum
- end
- --# Heightmap
- -- Heightmap building class --
- ------------------------------------------------------
- Terrain = class()
- function Terrain:init(quality, str)
- local mapSize = mapSize
- local colorTexture = colorTexture
- nbChunks = 64
- local nP = 1/nPower
- local mS = 1/mapSize
- local sqrt = math.sqrt
- self.mdl = {}
- for i=1, nbChunks do
- self.mdl[i] = {}
- end
- local ratio = mapSize/sqrt(nbChunks)
- local ins = table.insert
- local v, A, B, C, D
- local va, vb, vc, vd
- local ta, tb, tc, td
- local lowx, highx, lowz, highz
- for lod=1, 4 do
- local step = math.pow(2, lod+1)
- local stp = 1/step
- local vertex = {}
- local tex = {}
- local n = 1
- for valx = 0, mapSize-ratio, ratio do
- lowx = 0 + valx
- highx = ratio + valx
- for valz = 0, mapSize-ratio, ratio do
- lowz = 0 + valz
- highz = ratio + valz
- local polygons = {}
- local texCoords = {}
- -- create vertices, the height value of the heightmap is a simple noise
- for z=lowz, highz-step, step do
- for x=lowx, highx-step, step do
- A = (x + (z) * (mapSize+step)*stp)*stp+1
- B = (x + (z+step) * (mapSize+step)*stp)*stp+1
- C = (x+step + (z+step) * (mapSize+step)*stp)*stp+1
- D = (x+step + (z) * (mapSize+step)*stp)*stp+1
- if not vertex[A] then
- if (loop == 1) then
- v = Noise(mapSize, x*nP, z*nP, 0, 0)*str
- else
- v = noise(x*nP, z*nP)*str
- end
- vertex[A] = vec3(x, v, z)
- end
- if not vertex[B] then
- if (loop == 1) then
- v = Noise(mapSize, x*nP, (z+step)*nP, 0, 0)*str
- else
- v = noise(x*nP, (z+step)*nP)*str
- end
- vertex[B] = vec3(x, v, z+step)
- end
- if not vertex[C] then
- if (loop == 1) then
- v = Noise(mapSize, (x+step)*nP, (z+step)*nP, 0, 0)*str
- else
- v = noise((x+step)*nP, (z+step)*nP)*str
- end
- vertex[C] = vec3(x+step, v, z+step)
- end
- if not vertex[D] then
- if (loop == 1) then
- v = Noise(mapSize, (x+step)*nP, (z+step)*nP, 0, 0)*str
- else
- v = noise((x+step)*nP, (z+step)*nP)*str
- end
- vertex[D] = vec3(x+step, v, z)
- end
- ins(polygons, vertex[A])
- ins(polygons, vertex[B])
- ins(polygons, vertex[C])
- ins(polygons, vertex[C])
- ins(polygons, vertex[D])
- ins(polygons, vertex[A])
- if texture == 1 then
- if not tex[A] then tex[A] = vec2( x*stp, z*stp) end
- if not tex[B] then tex[B] = vec2( x*stp, (z+step)*stp) end
- if not tex[C] then tex[C] = vec2( (x+step)*stp, (z+step)*stp) end
- if not tex[D] then tex[D] = vec2( (x+step)*stp, z*stp) end
- else
- if not tex[A] then tex[A] = vec2( x*mS, z*mS) end
- if not tex[B] then tex[B] = vec2( x*mS, (z+step)*mS) end
- if not tex[C] then tex[C] = vec2( (x+step)*mS, (z+step)*mS) end
- if not tex[D] then tex[D] = vec2( (x+step)*mS, z*mS) end
- end
- ins(texCoords, tex[A])
- ins(texCoords, tex[B])
- ins(texCoords, tex[C])
- ins(texCoords, tex[C])
- ins(texCoords, tex[D])
- ins(texCoords, tex[A])
- end
- end
- self.mdl[n][lod] = mesh()
- self.mdl[n][lod].vertices = polygons
- if texture == 1 then
- self.mdl[n][lod].texture = detailTexture
- else
- if texture == 2 then
- self.mdl[n][lod].texture = colorTexture
- end
- end
- self.mdl[n][lod].texCoords = texCoords
- self.mdl[n][lod]:setColors(255, 255, 255, 255)
- mx = ((lowx + highx)/2)
- mz = ((lowz + highz)/2)
- mr = sqrt( (lowx-mx)*(lowx-mx) + (lowz-mz)*(lowz-mz) )
- self.mdl[n].boundingSphere = Sphere(mx, 0, mz, mr)
- n = n + 1
- end
- end
- end
- print("Terrain loaded OK")
- return self
- end
- function Terrain:draw()
- if not frustum then frustum = extractFrustum() end
- local d
- nbPolygon=0
- local mdl = self.mdl
- for i=1, #mdl do
- local m = mdl[i]
- d = sphereInFrustum(frustum, m.boundingSphere)
- if d~=0 then
- -- ignoring distance based LOD for now
- d = quality
- local s = math.pow(2, d+1)
- p = ((mapSize/s)*(mapSize/s)*2)/nbChunks
- nbPolygon = nbPolygon + p
- m[d]:draw()
- end
- end
- end
- --# Main
- -- Main program --
- ------------------------------------------------------
- function setup()
- -- displayMode(FULLSCREEN)
- supportedOrientations(LANDSCAPE_ANY)
- saveProjectInfo("Description", "3D renderer")
- saveProjectInfo("Author", "Xavier de Boysson")
- setupParameters()
- texWidth = 256
- tex1 = image(texWidth, texWidth)
- tex2 = image(texWidth, texWidth)
- tex3 = image(texWidth, texWidth)
- genTextures(texWidth)
- watch("camX")
- watch("camY")
- watch("camZ")
- textureQ = defaultTextureQuality*64
- baseL = loop
- baseS = str
- baseQ = quality
- baseP = nPower
- baseX = X
- baseZ = Z
- baseT = textureQ
- baseTex = texture
- baseDTQ = defaultTextureQuality
- baseBl = blending
- cnt = 0
- txt = ""
- nbPolygon = 0
- lx = 0.1
- lz = 0.1
- baseX = lx
- baseZ = lz
- nbTouches = 0
- touches = {}
- delta = 0
- frustum = nil
- camX = 280
- camY = 200
- camZ = 0
- lookX = 280
- lookY = 60
- lookZ = 256
- mapSize = 512
- detailTexture = genWireframe(32)
- colorTexture = nil
- genTexture(textureQ)
- scene = nil
- skyTex = image(4, 4)
- sky = mesh()
- sky.texture = skyTex
- sky:addRect(1,1,WIDTH*2,HEIGHT*2)
- --[[
- z = -10
- sky.vertices = {vec3(1, 1, z),
- vec3(1, HEIGHT, z),
- vec3(WIDTH, HEIGHT, z),
- vec3(1, 1, z),
- vec3(WIDTH, 1, z),
- vec3(WIDTH, HEIGHT, z)}
- sky.texCoords = {vec2(0, 0),
- vec2(0, 1),
- vec2(1, 1),
- vec2(0, 0),
- vec2(1, 0),
- vec2(1, 1)}
- --]]
- sky:setColors(255,255,255,255)
- loadMdl()
- textMode(CORNER)
- spriteMode(CORNER)
- end
- function setupParameters()
- parameter("blending", 1, 8, 2.2)
- iparameter("loop", 0, 1, 0)
- iparameter("moveLight", 0, 1, 0)
- -- quality is the heightmap level of detail (step size)
- iparameter("quality", 1, 4, 1)
- -- quality of the texture, warning :P
- iparameter("defaultTextureQuality", 1, 4, 4)
- -- str is the scaling of height values
- iparameter("str", 1, 256, 100)
- -- npower is the perlin noise scaling
- iparameter("nPower", 40, 110, 50)
- iparameter("texture", 0, 2, 2)
- end
- -- Ugly mess to handle parametres
- function readParameters()
- if (baseS ~= str) then
- clearOutput()
- loadMdl()
- baseS = str
- end
- if (baseP ~= nPower or baseL ~= loop) then
- clearOutput()
- if texture == 2 then
- genTexture(textureQ)
- end
- loadMdl()
- baseL = loop
- baseP = nPower
- end
- if (blending ~= baseBl or baseT ~= textureQ or baseX ~= lx or baseZ ~= lz) then
- clearOutput()
- genTexture(textureQ)
- applyTex()
- baseX = lx
- baseZ = lz
- baseBl = blending
- baseT = textureQ
- end
- if baseDTQ ~= defaultTextureQuality then
- clearOutput()
- baseDTQ = defaultTextureQuality
- textureQ = defaultTextureQuality*64
- genTexture(textureQ)
- applyTex()
- baseT = textureQ
- end
- if baseTex ~= texture then
- clearOutput()
- loadMdl()
- baseTex = texture
- end
- end
- -- Load the heightmap
- function loadMdl()
- scene = Terrain:init(quality, str)
- cnt = 0
- end
- function applyTex()
- local detailTexture = detailTexture
- local colorTexture = colorTexture
- for n=1, #scene.mdl do
- for lod=1, 4 do
- if texture == 1 then
- scene.mdl[n][lod].texture = detailTexture
- else
- if texture == 2 then
- scene.mdl[n][lod].texture = colorTexture
- end
- end
- end
- end
- end
- --# Render
- -- Render Loop --
- ------------------------------------------------------
- function draw()
- local skyTex = skyTex
- local sky = sky
- local scene = scene
- readParameters()
- background(0, 0, 0, 255)
- for y=1, 4 do
- for x=1, 4 do
- local col = 128+128*noise((x+cnt/256), y+cnt/256)
- skyTex:set(x, y, 255-col, 255-col, col, 255)
- end
- end
- -- refresh fps every 20 frames so its actually readable
- if cnt%20 == 0 then
- txt = (nbPolygon).." polygons at "..(math.floor(1/DeltaTime*10)/10).." FPS"
- end
- -- transparent pixels only show bg color or stuff in its transformation matrix
- sprite("Planet Cute:Character Boy", 0, HEIGHT - 171)
- translate(0, 0, -10)
- sky:draw()
- sprite("Planet Cute:Character Boy", 101, HEIGHT - 171)
- text(txt, WIDTH - 250, HEIGHT - 50)
- perspective(60)
- camera(camX, camY, camZ, lookX, lookY, lookZ, 0,1,0)
- scene:draw()
- cnt = cnt + 1
- end
- --# Texturing
- -- Texture Generation --
- ------------------------------------------------------
- function Noise(w,x,y, a, b)
- rs = (mapSize/w)/nPower
- x = x/rs
- y = y/rs
- if x>w/2 then
- x = (w/2)-(x-(w/2))+1
- end
- if y>w/2 then
- y = (w/2)-(y-(w/2))+1
- end
- x = x*rs
- y = y*rs
- x = x+a
- y = y+b
- return noise(x,y)
- end
- -- Main texture
- function genTexture(w)
- local r = mapSize/w
- local ratio = texWidth/w
- colorTexture = image(w, w)
- local sx, sy, L, h, R, G, B
- local colorTexture = colorTexture
- -- define texture colors
- local t1x, t1y, t1z, t1w
- local t2x, t2y, t2z, t2w
- local t3x, t3y, t3z, t3w
- local tW
- local tex1 = tex1
- local tex2 = tex2
- local tex3 = tex3
- local nx, ny, nz
- local col = 0
- local min = math.min
- local max = math.max
- local abs = math.abs
- local sqrt = math.sqrt
- local floor = math.floor
- local a, b
- local rs = r/nPower
- local xrs, zrs
- local blending = blending
- for z=1, w do
- for x=1, w do
- xrs = x*rs
- zrs = z*rs
- if (loop == 1) then
- h = min(1,max(0,(1+Noise(w, xrs, zrs, 0, 0))/2))
- else
- h = min(1,max(0,(1+noise(xrs, zrs))/2))
- end
- -- Get the weight of each texture color type at current height
- -- a = min(texWidth,max(1,x))
- -- b = min(texWidth,max(1,z))
- t1x, t1y, t1z = tex1:get(x, z)
- t2x, t2y, t2z = tex2:get(x, z)
- t3x, t3y, t3z = tex3:get(x, z)
- t1w = min(1,max(0, 1 - abs(h-0.3)*blending))
- t2w = min(1,max(0, 1 - abs(h-0.55)*blending))
- t3w = min(1,max(0, 1 - abs(h-0.8)*blending))
- tW = 1/(t1w + t2w + t3w)
- t1w = t1w*tW
- t2w = t2w*tW
- t3w = t3w*tW
- -- Blend the texture colors together
- R = (t1x*t1w + t2x*t2w + t3x*t3w)
- G = (t1y*t1w + t2y*t2w + t3y*t3w)
- B = (t1z*t1w + t2z*t2w + t3z*t3w)
- -- Calculate the normal for current pixel
- if (loop == 1) then
- sx = Noise(w, xrs, zrs, rs , 0) - Noise(w, xrs, zrs, -rs, 0)
- sy = Noise(w, xrs, zrs, 0, rs) - Noise(w, xrs, zrs, 0, -rs)
- else
- sx = noise(xrs+rs, zrs) - noise(xrs-rs, zrs)
- sy = noise(xrs, zrs+rs) - noise(xrs, zrs-rs)
- end
- nx = -sx*w
- nz = sy*w
- L = 1/sqrt(nx*nx + 4 + nz*nz)
- nx = nx*L
- ny = 2*L
- nz = nz*L
- -- Calculate lightning
- col = min(1,max(0.1, nx*lx + ny + nz*lz)*h*4)
- -- Blend lightning with texture color
- colorTexture:set(x, z,col*R, col*G, col*B, 255)
- -- t:set(x, z,col, col, col, 255)
- end
- end
- print("Color texture loaded OK")
- end
- -- Wireframe texture
- function genWireframe(w)
- local t = image(w, w)
- for y=1, w do
- for x=1, 3 do
- t:set(x, y, 255, 255, 255, 255)
- end
- end
- for x=1, w do
- for y=1, 3 do
- t:set(x, y, 255, 255, 255, 255)
- end
- end
- for x=2, w-1 do
- t:set(x+1, x, 255, 255, 255, 255)
- t:set(x, x, 255, 255, 255, 255)
- t:set(x-1, x, 255, 255, 255, 255)
- end
- t:set(w, w, 255, 255, 255, 255)
- t:set(w-1, w, 255, 255, 255, 255)
- return t
- end
- function genTextures()
- local tex1 = tex1
- local tex2 = tex2
- local tex3 = tex3
- local min = math.min
- local max = math.max
- local n1p = 1/10
- local n2p = 1/4
- local n3p = 1/20
- for y=1, texWidth do
- for x=1, texWidth do
- local n1 = min(1, max(0.3, (1+noise(x*n1p, y*n1p))*.5)*2)
- local n2 = min(1, max(0.6, (1+noise(x*n2p, y*n2p))*.5))
- local n3 = min(1, max(0.3, (1+noise(x*n3p, y*n3p))*.5)*2)
- tex1:set(x, y, 0, 0, n1*255)
- tex2:set(x, y, 0, n2*255, 0)
- tex3:set(x, y, n3*255, n3*255, n3*255)
- end
- end
- print("Detail textures loaded OK")
- end
- --# Touches
- -- Touch Handling --
- ------------------------------------------------------
- function touched(touch)
- frustum = extractFrustum()
- local state = touch.state
- local touches = touches
- -- Get number of touches
- if state == BEGAN then
- nbTouches = nbTouches + 1
- end
- if state == ENDED then
- nbTouches = nbTouches - 1
- touches[touch.id] = nil
- else
- touches[touch.id] = touch
- end
- local delta = delta
- -- need to save delta before looping through vertices, else laggy on high quality model
- local deltaX = touch.deltaX
- local deltaY = touch.deltaY
- -- Handle pinch to zoom
- if nbTouches == 2 then
- local newPt = {}
- local lastPt = {}
- local ins = table.insert
- for k, p in pairs(touches) do
- local px = p.x
- local py = p.y
- ins(newPt, vec2(px, py))
- if state == BEGAN then
- ins(lastPt, vec2(px, py))
- else
- ins(lastPt, vec2(px - p.deltaX, py - p.deltaY))
- end
- end
- delta = lastPt[1]:dist(lastPt[2]) - newPt[1]:dist(newPt[2])
- camY = camY + delta
- if camY<lookY then
- camY = lookY
- delta = 0
- end
- end
- -- change light vector and reduce texture quality for smoother animation
- if moveLight == 1 then
- if state == BEGAN then
- textureQ = 64
- end
- lx = -((WIDTH*.5 - touch.x)/WIDTH)*.5
- lz = -((HEIGHT*.5 - touch.y)/HEIGHT)*.5
- if state == ENDED then
- textureQ = defaultTextureQuality*64
- moveLight = 0
- end
- else
- camX = camX + deltaX
- camZ = camZ - deltaY
- lookX = lookX + deltaX
- lookZ = lookZ - deltaY
- end
- end
- --# Types
- -- Custom Types --
- ------------------------------------------------------
- Plane = class()
- function Plane:init(a, b, c, d)
- self.a = a
- self.b = b
- self.c = c
- self.d = d
- end
- function Plane:normalize()
- local d = 1/math.sqrt(self.a*self.a + self.b*self.b + self.c*self.c)
- self.a = self.a * d
- self.b = self.b * d
- self.c = self.c * d
- self.d = self.d * d
- end
- ------------------------------------------------------
- Sphere = class()
- function Sphere:init(x, y, z, r)
- self.x = x
- self.y = y
- self.z = z
- self.r = r
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement