Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --# Heightmap
- -- Heightmap building class --
- ------------------------------------------------------
- Model = class()
- function Model:init(quality, n, wrapping, str)
- mapSize = 512
- step = math.pow(2,quality+2)
- self.vertices = {}
- self.faces = {}
- self.detailTexCoords = {}
- self.colorTexCoords = {}
- self.tris = {}
- self.tex = {}
- self.cols = {}
- local size = (mapSize/step)*(mapSize/step)*6
- for i=1, size do
- self.tris[i] = vec2(0,0)
- if (textured == 1 or wireframe == 1) then
- self.tex[i] = vec2(0,0)
- end
- if colored == 1 then
- self.cols[i] = color(255, 255, 255, 255)
- end
- end
- -- create vertices, the height value of the heightmap is a simple noise
- local i, j
- i=1
- for z=0, mapSize, step do
- for x=0, mapSize, step do
- j = (x + (z) * (mapSize+step)/step)/step+1
- self.vertices[j] = Vertex(x*wrapping,(currentZ)-noise(x/n, z/n)*str, z*wrapping)
- self.detailTexCoords[j] = vec2( x/step, z/step)
- self.colorTexCoords[j] = vec2( x/mapSize, z/mapSize)
- -- filling the face array with the indices of the vertices
- -- 4 points, two triangles
- if (z~=mapSize and x~=mapSize) then
- a = (x + (z) * (mapSize+step)/step)/step + 1
- b = (x + (z + step) * (mapSize+step)/step)/step + 1
- c = (x + step + (z + step) * (mapSize+step)/step)/step + 1
- d = (x + step + (z) * (mapSize+step)/step)/step + 1
- self.faces[i] = Face(a, b, c)
- self.faces[i+1] = Face(c, d, a)
- i = i + 2
- end
- end
- end
- return self
- end
- --# Main
- -- Main program --
- ------------------------------------------------------
- function setup()
- saveProjectInfo("Description", "3D renderer")
- saveProjectInfo("Author", "Xavier de Boysson")
- setupParameters()
- textureQ = defaultTextureQuality*64
- baseW = wrapping
- baseS = str
- baseQ = quality
- baseSp = sphere
- baseP = nPower
- baseX = X
- baseZ = Z
- baseT = textureQ
- baseTex = textured
- baseC = colored
- baseR = Red
- baseG = Green
- baseB = Blue
- baseDTQ = defaultTextureQuality
- cnt = 0
- txt = ""
- lx = 0.1
- lz = 0.1
- baseX = lx
- baseZ = lz
- nbTouches = 0
- touches = {}
- delta = 0
- dx = -256*wrapping
- dy = -256*wrapping
- currentZ = 512
- --stroke(255)
- --strokeWidth(5)
- --noSmooth()
- p = mesh()
- loadMdl()
- detailTexture = genWireframe(32)
- colorTexture = genTexture(textureQ)
- textMode(CORNER)
- end
- function setupParameters()
- iparameter("moveLight", 0, 1, 0)
- -- wrapping is how much the heightmap wraps to the pseudo-sphere
- iparameter("sphere", 0, 1, 1)
- parameter("wrapping", 1, 10, 4)
- -- quality is the heightmap level of detail (step size)
- iparameter("quality", 1, 6, 2)
- -- quality of the texture, warning :P
- iparameter("defaultTextureQuality", 1, 4, 4)
- -- str is the scaling of height values
- iparameter("str", 0, 2048, 768)
- -- npower is the perlin noise scaling
- iparameter("nPower", 10, 200, 100)
- iparameter("wireframe", 0, 1, 0)
- iparameter("textured", 0, 1, 1)
- iparameter("colored", 0, 1, 0)
- parameter("Red", 0, 1, 1)
- parameter("Green", 0, 1, 0)
- parameter("Blue", 0, 1, 0)
- end
- -- Ugly mess to handle parametres
- function readParameters()
- if (baseW ~= wrapping) then
- dx = -256*wrapping
- dy = -256*wrapping
- loadMdl()
- baseW = wrapping
- end
- if (baseSp ~= sphere or baseQ ~= quality or baseS ~= str) then
- loadMdl()
- baseS = str
- baseQ = quality
- baseSp = sphere
- end
- if (baseP ~= nPower ) then
- loadMdl()
- if textured == 1 then
- colorTexture = genTexture(textureQ)
- end
- baseP = nPower
- end
- if (baseTex ~= textured or baseT ~= textureQ or baseX ~= lx or baseZ ~= lz) then
- colorTexture = genTexture(textureQ)
- baseX = lx
- baseZ = lz
- baseT = textureQ
- baseTex = textured
- end
- if baseDTQ ~= defaultTextureQuality then
- baseDTQ = defaultTextureQuality
- textureQ = defaultTextureQuality*64
- colorTexture = genTexture(textureQ)
- baseT = textureQ
- end
- if (baseC ~= colored or baseR ~= Red or baseG ~= Green or baseB ~= Blue) then
- for i=1, #mdl.vertices do
- v = mdl.vertices[i]
- v.done = false
- end
- baseC = colored
- end
- end
- -- Load the heightmap
- function loadMdl()
- mdl = Model:init(quality, nPower, wrapping, str)
- for i=1, #mdl.vertices do
- local v = mdl.vertices[i]
- v.x = v.x + dx
- v.z = v.z + dy
- -- the initial pseudo-sphere wrapping formula
- if sphere == 1 then
- v.y = v.y + (v.x * v.x + v.z * v.z)*0.001
- end
- end
- cnt = 0
- end
- --# Render
- -- Render Loop --
- ------------------------------------------------------
- function draw()
- readParameters()
- background(0)
- local p = p
- local offsetX = WIDTH*.5
- local offsetY = HEIGHT*.5
- -- Apply z ordering every 20 frame
- local zOrder = cnt%20
- local v = mdl.vertices
- local f = mdl.faces
- local tris = mdl.tris
- local cols = mdl.cols
- local tex = mdl.tex
- local t
- local texture
- -- Select texture based on display mode
- if textured == 1 then
- t = mdl.colorTexCoords
- texture = colorTexture
- end
- if wireframe == 1 then
- t = mdl.detailTexCoords
- texture = detailTexture
- end
- local a, b, c, s
- local vfx, vfy, vfz, col
- -- refresh fps every 20 frames so its actually readable
- if zOrder == 0 then
- txt = ((1024/step)*(1024/step)).." polygons at "..(math.floor(1/DeltaTime*10)/10).." FPS"
- end
- text(txt, WIDTH - 250, HEIGHT - 50)
- local n = 1
- -- loop through every face in the model, each face contains the index of 3 vertices
- for i=1, #f do
- local fx = f[i].x
- local fy = f[i].y
- local fz = f[i].z
- -- extract the vertices that define the face
- vfx = v[fx]
- vfy = v[fy]
- vfz = v[fz]
- a = vfx.pt
- b = vfy.pt
- c = vfz.pt
- -- to speed things up, a vertex is only projected once
- -- the raw 3D to 2D screen space formula is x2D = x3D/z3D, y2D = y3D/z3D
- -- base color of each vertex is based on its height
- -- point A
- if not vfx.done then
- s = 600/(600 + vfx.y)
- a.x = vfx.x * s + offsetX
- a.y = vfx.z * s + offsetY
- if colored == 1 then
- col = 255 - vfx.y*.075
- vfx.col = color(col*Red, col*Green, col*Blue, 255)
- end
- vfx.done = true
- end
- -- point B
- if not vfy.done then
- s = 600/(600 + vfy.y)
- b.x = vfy.x * s + offsetX
- b.y = vfy.z * s + offsetY
- if colored == 1 then
- col = 255 - vfy.y*.075
- vfy.col = color(col*Red, col*Green, col*Blue, 255)
- end
- vfy.done = true
- end
- -- point C
- if not vfz.done then
- s = 600/(600 + vfz.y)
- c.x = vfz.x * s + offsetX
- c.y = vfz.z * s + offsetY
- if colored == 1 then
- col = 255 - vfz.y*.075
- vfz.col = color(col*Red, col*Green, col*Blue, 255)
- end
- vfz.done = true
- end
- -- getting distance from center of polygon to camera(0,0,0) to use painter's algorithm
- -- this doesn't need to be that precise, so can easily be simplified
- if zOrder == 0 then
- midx = (vfx.x + vfy.x + vfz.x)
- midy = (vfx.y + vfy.y + vfz.y)
- midz = (vfx.z + vfy.z + vfz.z)
- f[i].dist = (midx*midx+ midy*midy+ midz*midz)
- else
- local nA = n
- local nB = n+1
- local nC = n+2
- -- add vertices
- tris[nA] = a
- tris[nB] = b
- tris[nC] = c
- -- add wireframe texture UVs
- if textured == 1 or wireframe == 1 then
- tex[nA] = t[fx]
- tex[nB] = t[fy]
- tex[nC] = t[fz]
- end
- -- add colors
- if colored == 1 then
- cols[nA] = vfx.col
- cols[nB] = vfy.col
- cols[nC] = vfz.col
- end
- n = n + 3
- end
- end
- -- the painter's algorithm means drawings things closer to the camera last
- -- we sort the polygons based on distance to camera
- if (zOrder == 0) then
- table.sort(f, function (a,b) return a.dist>b.dist end)
- for i=1, #f do
- local fx = f[i].x
- local fy = f[i].y
- local fz = f[i].z
- vfx = v[fx]
- vfy = v[fy]
- vfz = v[fz]
- local nA = n
- local nB = n+1
- local nC = n+2
- -- add vertices
- tris[nA] = vfx.pt
- tris[nB] = vfy.pt
- tris[nC] = vfz.pt
- -- add wireframe texture UVs
- if textured == 1 or wireframe == 1 then
- tex[nA] = t[fx]
- tex[nB] = t[fy]
- tex[nC] = t[fz]
- end
- -- add colors
- if colored == 1 then
- cols[nA] = vfx.col
- cols[nB] = vfy.col
- cols[nC] = vfz.col
- end
- n = n + 3
- end
- end
- p.vertices = tris
- p.texture = nil
- p.texCoords = nil
- p.colors = nil
- if textured == 1 or wireframe == 1 then
- p.texture = texture
- p.texCoords = tex
- if colored == 0 then
- p:setColors(255, 255, 255, 255)
- end
- end
- if colored == 1 then
- p.colors = cols
- end
- p:draw()
- cnt = cnt + 1
- end
- --# Texturing
- -- Texture Generation --
- ------------------------------------------------------
- -- Main texture
- function genTexture(w)
- local r = 512/w
- local t = image(w, w)
- local sx, sy, L, h, R, G, B
- -- define texture colors
- local t1x, t1y, t1z, t1w
- local t2x, t2y, t2z, t2w
- local t3x, t3y, t3z, t3w
- local tW
- t1x = 0
- t1y = 0
- t1z = 1
- t1w = 1
- t2x = 0
- t2y = 1
- t2z = 0
- t2w = 1
- t3x = 1
- t3y = 1
- t3z = 1
- t3w = 1
- local nx, ny, nz
- local col = 0
- local min = math.min
- local max = math.max
- local abs = math.abs
- local sqrt = math.sqrt
- local rs = r/nPower
- local xrs, zrs
- for z=1, w do
- for x=1, w do
- xrs = x*rs
- zrs = z*rs
- h = (128+127*noise(x*rs, z*rs))/255
- -- Get the weight of each texture color type at current height
- t1w = min(1, max(0, 1 - abs(h - 0.2)*4))
- t2w = min(1, max(0, 1 - abs(h - 0.5)*4))
- t3w = min(1, max(0, 1 - abs(h - 0.8)*4))
- tW = 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
- sx = noise(xrs+rs, zrs) - noise(xrs-rs, zrs)
- sy = noise(xrs, zrs+rs) - noise(xrs, zrs-rs)
- 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 = 255*min(1,max(0.1, nx*lx + ny + nz*lz)*2)
- -- Blend lightning with texture color
- t:set(x, z,col*R, col*G, col*B, 255)
- end
- end
- return t
- 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
- --# Touches
- -- Touch Handling --
- ------------------------------------------------------
- function touched(touch)
- 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
- dx = dx + deltaX
- dy = dy + 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])
- currentZ = currentZ + delta
- if currentZ<1 then
- currentZ = 1
- 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
- -- this would normally be processed in a vertex shader, but this is software
- -- i didn't find any other way
- local vs = mdl.vertices
- for i=1, #vs do
- local v = vs[i]
- local vx = v.x
- local vz = v.z
- -- restore the shape of the model (turn it from pseudo-sphere to simple heightmap)
- if sphere == 1 then
- v.y = v.y - (vx * vx + vz * vz)*0.001
- end
- -- due to the way the trick works, translating the vertices rotates the "planet"
- v.x = vx + deltaX
- v.z = vz + deltaY
- v.y = v.y + delta
- -- once the translation work is done, turn it back into a pseudo-sphere
- if sphere == 1 then
- vx = v.x
- vz = v.z
- v.y = v.y + (vx * vx + vz * vz)*0.001
- end
- v.done = false
- end
- end
- end
- --# Types
- -- Custom Types --
- ------------------------------------------------------
- Vertex = class()
- function Vertex:init(x, y, z)
- self.x = x
- self.y = y
- self.z = z
- self.pt = vec2(0,0)
- self.col = color(Red, Green, Blue, 255)
- self.done = false
- end
- ------------------------------------------------------
- Face = class()
- function Face:init(x, y, z)
- self.x = x
- self.y = y
- self.z = z
- self.dist = 0
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement