Skip to content
Extraits de code Groupes Projets
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