shape.lua 5,79 Kio
require("kara3d.vector")
require("kara3d.matrix")
require("kara3d.quaternion")
require("kara3d.transform")
require("kara3d.lights")
require("utils")
re = require("re")
-- ********** DEFINITION AND GENERIC FUNCTIONS **********
shape = {class = "shape"}
shape.__index = shape
face = {class = "face"}
face.__index = face
material = {class = "material"}
material.__index = material
function material.new(a, d, sp, sh)
local m = {ambient = a or vector.new(3, {1, 1, 1}),
diffuse = d or vector.new(3, {1, 1, 1}),
specular = sp or vector.new(3, {1, 1, 1}),
shininess = sh or 1}
setmetatable(m, material)
return m
end
-- Creates a new face with vertices c, normal n and tags t
function face.new(v, n, t)
local f = {vertices = v or {}, normal = n or -vector.cardinal(3, 3), tags = t}
setmetatable(f, face)
return f
end
function face:mean()
sum = vector.zero(3)
for i = 1, #self.vertices do
sum = sum + self.vertices[i]
end
return (1/(#self.vertices)) * sum
end
function face:shade(mat, lights, ambient)
local Ka = mat.ambient
local Kd = mat.diffuse
local Ks = mat.specular
local shine = mat.shininess
local N = self.normal:normalized()
local P = self:mean():normalized()
local V = vector.new(3, {0, 0, -1})
local I = Ka * ambient
for i = 1, #lights do
local l = lights[i]
local L = (light.transform:position(true) - P):normalized()
local R = 2 * vector.dot(L, N) * N - L
-- Diffuse component
I = I + Kd * vector.dot(L, N) * l.diffuse
-- Specular component
I = I + Ks * math.pow(vector.dot(R, V), shine) * l.specular
end
return I
end
-- Creates a new shape with transform t and faces f
function shape.new(t, f)
local s = {transform = t, faces = f or {}}
setmetatable(s, shape)
return s
end
-- Clones this shape
function shape:clone()
return shape.new(self.transform, table.copy(self.faces))
end
-- ********** INSTANCE FUNCTIONS **********
-- Generates a line for each face of this shape, with the indicated tags
-- The boolean cull determines if the faces facing away from the screen are
-- drawn or not
function shape:draw(subs, line, tags, cull)
local l = table.copy(line)
local p = self.transform:position(true)
local r = self.transform:rotation(true)
local m = self.transform:matrix(true)
local c = vector.cardinal(4, 4)
local layer = line.layer
for i = 1, #self.faces do
local face = self.faces[i]
local n = quaternion.mul_vector(r, face.normal)
if not (cull and n:z() > 0) then
local vs = face.vertices
local v = matrix.mul_vector(m, vs[#vs] + c) - p
local sum = vector.zero(4)
local str = (tags and "{" .. tags .. "}" or "")
.. (face.tags and "{" .. face.tags .. "}" or "")
.. "{\\an7\\pos(" .. p:x() .. ", " .. p:y() .. ")\\p1}"
.. "m " .. v:x() .. " " .. v:y() .. " "
for i = 1, #vs do
v = matrix.mul_vector(m, vs[i] + c) - p
sum = sum + v
str = str .. "l " .. v:x() .. " " .. v:y() .. " "
end
l.text = str
-- layers must be between 0 and 9999999999
l.layer = 500000000 + math.floor(-(p + sum):z() / #vs)
l.effect = "fx"
subs.append(l)
end
end
return ""
end
-- ********** BASIC SHAPE FUNCTIONS **********
-- Returns a cube shape of side length size, that follows the transform t
-- Schematic of the vertices:
-- 1________5
-- /| /|
-- 2/__|____/6 |
-- | 3|_ _|_ _|7 __ X
-- | / | / /|
-- 4|/______|/8 Z Y
function shape.cube(t, size)
local hs = size / 2
local vs = {vector.new(3, {-hs, -hs, -hs}), vector.new(3, {-hs, -hs, hs}),
vector.new(3, {-hs, hs, -hs}), vector.new(3, {-hs, hs, hs}),
vector.new(3, { hs, -hs, -hs}), vector.new(3, { hs, -hs, hs}),
vector.new(3, { hs, hs, -hs}), vector.new(3, { hs, hs, hs})}
local left = face.new({vs[1], vs[2], vs[4], vs[3]}, -vector.cardinal(3, 1))
local right = face.new({vs[5], vs[6], vs[8], vs[7]}, vector.cardinal(3, 1))
local bottom = face.new({vs[1], vs[2], vs[6], vs[5]}, -vector.cardinal(3, 2))
local top = face.new({vs[3], vs[4], vs[8], vs[7]}, vector.cardinal(3, 2))
local back = face.new({vs[1], vs[5], vs[7], vs[3]}, -vector.cardinal(3, 3))
local front = face.new({vs[2], vs[6], vs[8], vs[4]}, vector.cardinal(3, 3))
return shape.new(t, {left, right, bottom, top, back, front})
end
--[[
Load shape from mesh file. Only .off files supported for now, though
]]--
function shape.load(t, file)
local vertices = {}
local faces = {}
local vertexNumber = nil
local faceNumber = nil
local vertexRead = 0
local facesRead = 0
local linenumber = 0
for line in io.lines(file) do
linenumber = linenumber + 1
local blankMatch = re.match(line, "OFF|#.*")
if blankMatch == nil then
local matches = re.split(line, "[[:blank:]]+", true)
if matches ~= nil then
if vertexNumber == nil or faceNumber == nil then
vertexNumber = tonumber(matches[1])
faceNumber = tonumber(matches[2])
else
if vertexRead < vertexNumber then
vertices[#vertices+1] = vector.new(3, {tonumber(matches[1]), tonumber(matches[2]), tonumber(matches[3])})
vertexRead = vertexRead + 1
else
local faceSize = tonumber(matches[1])
local faceVertices = {}
for i = 1, faceSize do
faceVertices[i] = vertices[tonumber(matches[i+1]) + 1]
end
local normal = vector.cross(faceVertices[2] - faceVertices[1],
faceVertices[3] - faceVertices[1])
:normalized()
faces[#faces+1] = face.new(faceVertices, normal)
facesRead = facesRead + 1
end
end
end
end
end
return shape.new(t, faces)
end