mirror of
https://github.com/Reuh/wirefame.git
synced 2025-10-27 09:39:30 +00:00
Rewrite
This commit is contained in:
parent
b4798c8c16
commit
77cfbaad52
24 changed files with 3033 additions and 1545 deletions
34
init.lua
34
init.lua
|
|
@ -1 +1,33 @@
|
|||
return require((...)..".wirefame")
|
||||
--- WireFame v0.4.0 by Reuh: initially a wireframe software rendering engine, now also a 3D framework for Löve (optional).
|
||||
-- Should be compatible with Lua 5.1 to 5.3. There are 3 functions you will need to implement yourself to make
|
||||
-- this works with your graphical backend.
|
||||
-- Name found by Lenade Lamidedi.
|
||||
-- Thanks to tinyrenderer (https://github.com/ssloy/tinyrenderer/wiki) for explaining how a 3D renderer works.
|
||||
|
||||
local wirefame = {
|
||||
--- Default drawing color used when creating models.
|
||||
defaultColor = { 1, 1, 1, 1 },
|
||||
--- Model loaders
|
||||
loader = {
|
||||
gltf = require((...)..".loader.gltf")
|
||||
},
|
||||
-- Math classes
|
||||
m4 = require((...)..".math.m4"),
|
||||
v3 = require((...)..".math.v3"),
|
||||
bb3 = require((...)..".math.bb3"),
|
||||
bs3 = require((...)..".math.bs3"),
|
||||
-- Shader code
|
||||
shader = {
|
||||
render = require((...)..".shader.render"),
|
||||
pick = require((...)..".shader.pick")
|
||||
}
|
||||
}
|
||||
package.loaded[(...)] = wirefame
|
||||
|
||||
--- Load Wirefame functions.
|
||||
require((...)..".wirefame.model")
|
||||
require((...)..".wirefame.light")
|
||||
require((...)..".wirefame.group")
|
||||
require((...)..".wirefame.scene")
|
||||
|
||||
return wirefame
|
||||
|
|
|
|||
1
lib/json
Submodule
1
lib/json
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d1e3b0f5d0f3d3493c7dadd0bb54135507fcebd7
|
||||
391
loader/gltf.can
Normal file
391
loader/gltf.can
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
let json = require((...):match("^(.*)loader%.gltf$").."lib.json")
|
||||
let dunpack = string.unpack or love.data.unpack
|
||||
let v3 = require((...):match("^(.*)loader%.gltf$").."math.v3")
|
||||
let m4 = require((...):match("^(.*)loader%.gltf$").."math.m4")
|
||||
let qt = require((...):match("^(.*)loader%.gltf$").."math.qt")
|
||||
let bb3 = require((...):match("^(.*)loader%.gltf$").."math.bb3")
|
||||
let bs3 = require((...):match("^(.*)loader%.gltf$").."math.bs3")
|
||||
let lg = love.graphics
|
||||
|
||||
let drawNode = :(shader)
|
||||
if @mesh then
|
||||
shader:send("model_transform", @finalMatrix)
|
||||
for _, primitive in ipairs(@mesh.primitives) do
|
||||
if shader:hasUniform("baseColorFactor") then
|
||||
shader:send("baseColorFactor", primitive.material.pbrMetallicRoughness.baseColorFactor)
|
||||
end
|
||||
lg.draw(primitive.mesh)
|
||||
end
|
||||
end
|
||||
for _, child in ipairs(@children) do
|
||||
drawNode(child, shader)
|
||||
end
|
||||
end
|
||||
|
||||
let setTransform = :(tr)
|
||||
@finalMatrix = tr * @matrix
|
||||
for _, child in ipairs(@children) do
|
||||
setTransform(child, tr)
|
||||
end
|
||||
end
|
||||
|
||||
let boundingBox = :(bb)
|
||||
if @mesh then
|
||||
for _, primitive in ipairs(@mesh.primitives) do
|
||||
if not bb then
|
||||
bb = bb3.fromMesh(primitive.mesh)
|
||||
else
|
||||
bb:include(bb3.fromMesh(primitive.mesh))
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, child in ipairs(@children) do
|
||||
bb = boundingBox(child, bb)
|
||||
end
|
||||
return bb
|
||||
end
|
||||
|
||||
let boundingSphere = :(bs)
|
||||
if @mesh then
|
||||
for _, primitive in ipairs(@mesh.primitives) do
|
||||
if not bs then
|
||||
bs = bs3.fromMesh(primitive.mesh)
|
||||
else
|
||||
bs:include(bs3.fromMesh(primitive.mesh))
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, child in ipairs(@children) do
|
||||
bs = boundingBox(child, bs)
|
||||
end
|
||||
return bs
|
||||
end
|
||||
|
||||
let gltf_mt = {
|
||||
data = nil,
|
||||
draw = :(shader)
|
||||
for _, node in ipairs(@data.scene.nodes) do
|
||||
drawNode(node, shader)
|
||||
end
|
||||
end,
|
||||
setTransform = :(tr)
|
||||
for _, node in ipairs(@data.scene.nodes) do
|
||||
setTransform(node, tr)
|
||||
end
|
||||
end,
|
||||
boundingBox = :()
|
||||
local bb
|
||||
for _, node in ipairs(@data.scene.nodes) do
|
||||
bb = boundingBox(node, bb)
|
||||
end
|
||||
return bb
|
||||
end,
|
||||
boundingSphere = :()
|
||||
local bs
|
||||
for _, node in ipairs(@data.scene.nodes) do
|
||||
bs = boundingSphere(node, bs)
|
||||
end
|
||||
return bs
|
||||
end
|
||||
}
|
||||
gltf_mt.__index = gltf_mt
|
||||
|
||||
let attributeName = {
|
||||
POSITION = "VertexPosition",
|
||||
NORMAL = "VertexNormal",
|
||||
TANGENT = "VertexTangent",
|
||||
TEXCOORD_0 = "VertexTexCoord",
|
||||
TEXCOORD_1 = "VertexTexCoord1",
|
||||
COLOR_0 = "VertexColor",
|
||||
JOINTS_0 = "VertexJoints",
|
||||
WEIGHTS_0 = "VertexWeights"
|
||||
}
|
||||
|
||||
let componentType = {
|
||||
[5120] = "byte",
|
||||
[5121] = "unsigned byte",
|
||||
[5122] = "short",
|
||||
[5123] = "unsigned short",
|
||||
[5125] = "int",
|
||||
[5126] = "float"
|
||||
}
|
||||
|
||||
let mode = {
|
||||
[0] = "points",
|
||||
[1] = "lines",
|
||||
[2] = "line_loop",
|
||||
[3] = "line_strip",
|
||||
[4] = "triangles",
|
||||
[5] = "strip",
|
||||
[6] = "fan"
|
||||
}
|
||||
|
||||
--- Load a glTF file and returns it.
|
||||
-- The Lua table returned mirror the glTF structure, except:
|
||||
-- * nodes, buffers, etc. referenced using indices are replaced by an actual reference to the object
|
||||
-- * node.matrix are replaced by corresponding mat4 objects and is calculated from TRS when undefined
|
||||
-- * node.rotation, node.translation, node.scale replaced by qt and v3
|
||||
-- * optional fields are intialized whith their standard default value (if any)
|
||||
-- * enums number are replaced by the corresponding string (accessor.componentType, primitive.mode)
|
||||
-- new fields:
|
||||
-- * camera.matrix: the projection matrix
|
||||
-- * node.finalMatrix: the full transformation matrix for this node, including stuff done from wirefame
|
||||
-- * set a data field in buffer with the decoded/loaded data as a string
|
||||
-- * set a data field in accessors as a list of components (either list of scalar or list of list)
|
||||
-- * accessor.components contains component size
|
||||
-- * the default material is created at materials[0]
|
||||
-- * objects with name are will have an associated field in list where they are present
|
||||
-- This implementation will not perform data consistency checks and have absolute trust in the exporter.
|
||||
let gltf = (file)
|
||||
let f = assert(io.open(file, "r"))
|
||||
let t = json.decode(f:read("*a"))
|
||||
f:close()
|
||||
|
||||
-- asset
|
||||
if t.asset.minVersion then
|
||||
let maj, min = t.asset.minVersion:match("^(%d+)%.(%d+)$")
|
||||
assert(maj == "2" and min == "0", "asset require at least glTF version %s.%s but we only support 2.0":format(maj, min))
|
||||
else
|
||||
let maj, min = t.asset.version:match("^(%d+)%.(%d+)$")
|
||||
assert(maj == "2", "asset require glTF version %s.%s but we only support 2.x":format(maj, min))
|
||||
end
|
||||
|
||||
-- empty lists
|
||||
t.nodes or= {}
|
||||
t.scenes or= {}
|
||||
t.cameras or= {}
|
||||
t.meshes or= {}
|
||||
t.buffers or= {}
|
||||
t.bufferViews or = {}
|
||||
t.accessors or= {}
|
||||
t.materials or= {}
|
||||
t.textures or= {}
|
||||
t.images or= {}
|
||||
t.samplers or= {}
|
||||
t.skins or= {}
|
||||
t.animations or= {}
|
||||
|
||||
-- scenes
|
||||
for _, scene in ipairs(t.scenes) do
|
||||
if scene.name then t.scenes[scene.name] = scene end
|
||||
for i, node in ipairs(scene.nodes) do
|
||||
scene.nodes[i] = t.nodes[node+1]
|
||||
if scene.nodes[i].name then scene.nodes[scene.nodes[i].name] = scene.nodes[i] end
|
||||
end
|
||||
end
|
||||
|
||||
-- scene
|
||||
if t.scene then
|
||||
t.scene = t.scenes[t.scene+1]
|
||||
end
|
||||
|
||||
-- nodes
|
||||
for _, node in ipairs(t.nodes) do
|
||||
if node.name then t.nodes[node.name] = node end
|
||||
node.children or= {}
|
||||
for i, child in ipairs(node.children) do
|
||||
node.children[i] = t.nodes[child+1]
|
||||
end
|
||||
if node.matrix then
|
||||
node.matrix = m4.fromColumnMajor(node.matrix)
|
||||
else
|
||||
node.translation or= {0,0,0}
|
||||
node.rotation or= {0,0,0,1}
|
||||
node.scale or= {1,1,1}
|
||||
|
||||
node.translation = v3(node.translation)
|
||||
node.rotation = qt(node.rotation)
|
||||
node.scale = v3(node.scale)
|
||||
|
||||
node.matrix = m4.translate(node.translation) * node.rotation:toM4() * m4.scale(node.scale)
|
||||
end
|
||||
if node.mesh then
|
||||
node.mesh = t.meshes[node.mesh+1]
|
||||
end
|
||||
if node.camera then
|
||||
node.camera = t.cameras[node.camera+1]
|
||||
end
|
||||
end
|
||||
|
||||
-- buffers
|
||||
for i, buffer in ipairs(t.buffers) do
|
||||
if i == 1 and not buffer.uri then
|
||||
error("no support for glb-stored buffer") -- TODO
|
||||
end
|
||||
if buffer.uri:match("data:") then
|
||||
local data = buffer.uri:match("^data:.-,(.*)$")
|
||||
if buffer.uri:match("^data:.-;base64,") then
|
||||
buffer.data = love.data.decode("string", "base64", data):sub(1, buffer.byteLength+1)
|
||||
else
|
||||
buffer.data = data:gsub("%%(%x%x)", (hex)
|
||||
return love.data.decode("string", "hex", hex)
|
||||
end):sub(1, buffer.byteLength+1)
|
||||
end
|
||||
else
|
||||
let f = assert(io.open(buffer.uri, "r"), "can't find ressource %s":format(buffer.uri))
|
||||
let s = f:read("*a")
|
||||
f:close()
|
||||
buffer.data = s:sub(1, buffer.byteLength+1)
|
||||
end
|
||||
end
|
||||
|
||||
-- bufferViews
|
||||
for _, view in ipairs(t.bufferViews) do
|
||||
view.buffer = t.buffers[view.buffer+1]
|
||||
view.byteOffset or= 0
|
||||
-- TODO target
|
||||
end
|
||||
|
||||
-- accessors
|
||||
for _, accessor in ipairs(t.accessors) do
|
||||
accessor.bufferView = t.bufferViews[accessor.bufferView+1]
|
||||
accessor.byteOffset or= 0
|
||||
|
||||
let view = accessor.bufferView
|
||||
let data = view.buffer.data
|
||||
|
||||
let fmt, size
|
||||
accessor.componentType = componentType[accessor.componentType]
|
||||
if accessor.componentType == "byte" then
|
||||
fmt, size = "b", 1
|
||||
elseif accessor.componentType == "unsigned byte" then
|
||||
fmt, size = "B", 1
|
||||
elseif accessor.componentType == "short" then
|
||||
fmt, size = "h", 2
|
||||
elseif accessor.componentType == "unsigned short" then
|
||||
fmt, size = "H", 2
|
||||
elseif accessor.componentType == "unsigned int" then
|
||||
fmt, size = "I4", 4
|
||||
elseif accessor.componentType == "float" then
|
||||
fmt, size = "f", 4
|
||||
end
|
||||
|
||||
if accessor.type == "SCALAR" then
|
||||
accessor.components, fmt = 1, fmt
|
||||
elseif accessor.type == "VEC2" then
|
||||
accessor.components, fmt = 2, fmt:rep(2)
|
||||
elseif accessor.type == "VEC3" then
|
||||
accessor.components, fmt = 3, fmt:rep(3)
|
||||
elseif accessor.type == "VEC4" then
|
||||
accessor.components, fmt = 4, fmt:rep(4)
|
||||
elseif accessor.type == "MAT2" then
|
||||
accessor.components = 4
|
||||
fmt = (fmt:rep(2) .. "x":rep(4 - (size*2)%4)):rep(2) -- padding at each column start
|
||||
elseif accessor.type == "MAT3" then
|
||||
accessor.components = 9
|
||||
fmt = (fmt:rep(3) .. "x":rep(4 - (size*3)%4)):rep(3)
|
||||
elseif accessor.type == "MAT4" then
|
||||
accessor.components = 16
|
||||
fmt = (fmt:rep(4) .. "x":rep(4 - (size*4)%4)):rep(4)
|
||||
end
|
||||
|
||||
fmt =.. "<" -- little endian
|
||||
|
||||
accessor.data = {}
|
||||
let i = view.byteOffset+1 + accessor.byteOffset
|
||||
let stop = view.byteOffset+1 + view.byteLength
|
||||
let count = 0
|
||||
while i < stop and count < accessor.count do
|
||||
local d = { dunpack(fmt, data, i) }
|
||||
d[#d] = nil
|
||||
if accessor.components > 1 then
|
||||
table.insert(accessor.data, d)
|
||||
else
|
||||
table.insert(accessor.data, d[1])
|
||||
end
|
||||
count += 1
|
||||
i += view.byteStride or (size * accessor.components)
|
||||
end
|
||||
|
||||
-- TODO sparse accessor
|
||||
end
|
||||
|
||||
-- default material
|
||||
t.materials[0] = {
|
||||
pbrMetallicRoughness = {
|
||||
baseColorFactor = {1,1,1,1},
|
||||
metallicFactor = 1,
|
||||
roughnessFactor = 1
|
||||
},
|
||||
emissiveFactor = {0,0,0},
|
||||
alphaMode = "OPAQUE",
|
||||
alphaCutoff = .5,
|
||||
doubleSided = false
|
||||
}
|
||||
for _, material in ipairs(t.materials) do
|
||||
material.pbrMetallicRoughness or= {}
|
||||
material.pbrMetallicRoughness.baseColorFactor or= {1,1,1,1}
|
||||
material.pbrMetallicRoughness.metallicFactor or= 1
|
||||
material.pbrMetallicRoughness.roughnessFactor or= 1
|
||||
material.emissiveFactor or= {0,0,0}
|
||||
material.alphaMode or= "OPAQUE"
|
||||
material.alphaCutoff or= .5
|
||||
material.doubleSided or= false
|
||||
end
|
||||
|
||||
-- meshes
|
||||
for _, mesh in ipairs(t.meshes) do
|
||||
for _, primitive in ipairs(mesh.primitives) do
|
||||
let vertexformat = {}
|
||||
let vertices = {}
|
||||
for n, v in pairs(primitive.attributes) do
|
||||
let accessor = t.accessors[v+1]
|
||||
primitive.attributes[n] = accessor
|
||||
table.insert(vertexformat, { attributeName[n] or n, accessor.componentType, accessor.components })
|
||||
for i, f in ipairs(accessor.data) do
|
||||
let vertex = vertices[i]
|
||||
if not vertex then
|
||||
table.insert(vertices, i, {})
|
||||
vertex = vertices[i]
|
||||
end
|
||||
for _, c in ipairs(f) do
|
||||
table.insert(vertex, c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if primitive.mode then
|
||||
primitive.mode = mode[primitive.mode]
|
||||
else
|
||||
primitive.mode = "triangles"
|
||||
end
|
||||
|
||||
primitive.mesh = lg.newMesh(vertexformat, vertices, primitive.mode)
|
||||
if primitive.indices then
|
||||
primitive.indices = [ for _, i in ipairs(t.accessors[primitive.indices+1].data) do i+1 end ]
|
||||
primitive.mesh:setVertexMap(primitive.indices)
|
||||
end
|
||||
|
||||
primitive.material = t.materials[(primitive.material or -1)+1]
|
||||
|
||||
-- TODO targets
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO skins
|
||||
|
||||
-- TODO textures, images, samplers
|
||||
|
||||
-- cameras
|
||||
for _, camera in ipairs(t.cameras) do
|
||||
if camera.name then t.cameras[camera.name] = camera end
|
||||
if camera.type == "perspective" then
|
||||
camera.perspective.aspectRatio or= 16/9
|
||||
camera.matrix = m4.perspective(camera.perspective.yfov, camera.perspective.aspectRatio, camera.perspective.znear, camera.perspective.zfar)
|
||||
elseif camera.type == "orthographic" then
|
||||
camera.matrix = m4.orthographic(camera.orthographic.xmag, camera.orthographic.ymag, camera.orthographic.znear, camera.orthographic.zfar)
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO animations
|
||||
|
||||
-- TODO extensions
|
||||
|
||||
-- TODO glb
|
||||
|
||||
return setmetatable({
|
||||
data = t
|
||||
}, gltf_mt)
|
||||
end
|
||||
|
||||
return gltf
|
||||
146
math/bb3.lua
Normal file
146
math/bb3.lua
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
--- Axis aligned 3D bounding box
|
||||
local bb3
|
||||
|
||||
local v3 = require((...):match("^(.*)bb3$").."v3")
|
||||
|
||||
-- bb3 methods
|
||||
-- Unless specified otherwise, all operations are done in place and do not create a bounding box.
|
||||
local bb3_mt = {
|
||||
-- Clone the bounding box. Returns a new bounding box.
|
||||
clone = function(self)
|
||||
return bb3(self.min:clone(), self.max:clone())
|
||||
end,
|
||||
|
||||
-- Set bounding box
|
||||
set = function(self, min, max)
|
||||
self.min, self.max = v3(min), v3(max)
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Retrieve min and max vectors
|
||||
unpack = function(self)
|
||||
return self.min, self.max
|
||||
end,
|
||||
|
||||
-- Test if the bounding box collide with another bounding box, bounding sphere or point
|
||||
collide = function(self, other)
|
||||
if other.min then -- bb3
|
||||
return self.min[1] <= other.max[1] and self.max[1] >= other.min[1] and
|
||||
self.min[2] <= other.max[2] and self.max[2] >= other.min[2] and
|
||||
self.min[3] <= other.max[3] and self.max[3] >= other.min[3]
|
||||
elseif other.radius then -- bs3
|
||||
return other:collide(self)
|
||||
else -- v3
|
||||
return self.min[1] <= other[1] and self.max[1] >= other[1] and
|
||||
self.min[2] <= other[2] and self.max[2] >= other[2] and
|
||||
self.min[3] <= other[3] and self.max[3] >= other[3]
|
||||
end
|
||||
end,
|
||||
|
||||
-- Extend the bounding box by v:
|
||||
-- * number: extend by a certain distance from the center
|
||||
-- * v3: extend each axis by the associated vector coordinate
|
||||
extend = function(self, v)
|
||||
if type(v) == "number" then
|
||||
self.min = self.min - { v, v, v }
|
||||
self.max = self.max + { v, v, v }
|
||||
else
|
||||
self.min = self.min - v
|
||||
self.max = self.max + v
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Modify the bounding box so that it include v (v3 or bb3)
|
||||
include = function(self, v)
|
||||
if v.min then -- bounding box
|
||||
self.min[1] = math.min(self.min[1], v.min[1])
|
||||
self.min[2] = math.min(self.min[2], v.min[2])
|
||||
self.min[3] = math.min(self.min[3], v.min[3])
|
||||
self.max[1] = math.max(self.max[1], v.max[1])
|
||||
self.max[2] = math.max(self.max[2], v.max[2])
|
||||
self.max[3] = math.max(self.max[3], v.max[3])
|
||||
else -- vector
|
||||
self.min[1] = math.min(self.min[1], v[1])
|
||||
self.min[2] = math.min(self.min[2], v[2])
|
||||
self.min[3] = math.min(self.min[3], v[3])
|
||||
self.max[1] = math.max(self.max[1], v[1])
|
||||
self.max[2] = math.max(self.max[2], v[2])
|
||||
self.max[3] = math.max(self.max[3], v[3])
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Common operations with vectors. Returns a new bounding box.
|
||||
__sub = function(self, other)
|
||||
return bb3(self.min - other, self.max - other)
|
||||
end,
|
||||
__add = function(self, other)
|
||||
return bb3(self.min + other, self.max + other)
|
||||
end,
|
||||
__unm = function(self)
|
||||
return bb3(-self.min, -self.max)
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return ("bb3(%s,%s)"):format(self.min, self.max)
|
||||
end
|
||||
}
|
||||
bb3_mt.__index = bb3_mt
|
||||
|
||||
bb3 = setmetatable({
|
||||
-- Calculate the mesh's bounding box. Returns a new bounding box.
|
||||
fromMesh = function(mesh, tr)
|
||||
local bb
|
||||
|
||||
-- Get VertexPosition attribute index in vertex data
|
||||
local iposition
|
||||
for i, t in ipairs(mesh:getVertexFormat()) do
|
||||
if t[1] == "VertexPosition" then
|
||||
if t[3] ~= 3 then error("mesh vertices don't have a 3 dimensional position") end
|
||||
iposition = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if not iposition then error("mesh doesn't have VertexPosition attributes") end
|
||||
|
||||
-- Retrieve vertices
|
||||
local vmap = mesh:getVertexMap()
|
||||
if vmap then
|
||||
local min, max = mesh:getDrawRange()
|
||||
if not min then
|
||||
min, max = 1, #vmap
|
||||
end
|
||||
local v = v3(mesh:getVertexAttribute(vmap[min], iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bb = bb3(v:clone(),v:clone())
|
||||
for i=min+1, max do
|
||||
v = v3(mesh:getVertexAttribute(vmap[i], iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bb:include(v)
|
||||
end
|
||||
else
|
||||
local min, max = mesh:getDrawRange()
|
||||
if not min then
|
||||
min, max = 1, mesh:getVertexCount()
|
||||
end
|
||||
local v = v3(mesh:getVertexAttribute(min, iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bb = bb3(v:clone(),v:clone())
|
||||
for i=min+1, max do
|
||||
v = v3(mesh:getVertexAttribute(i, iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bb:include(v)
|
||||
end
|
||||
end
|
||||
|
||||
return bb
|
||||
end
|
||||
}, {
|
||||
-- min and max will not be copied.
|
||||
__call = function(self, min, max)
|
||||
return setmetatable({ min = v3(min), max = v3(max) }, bb3_mt)
|
||||
end
|
||||
})
|
||||
|
||||
return bb3
|
||||
145
math/bs3.lua
Normal file
145
math/bs3.lua
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
--- 3D bounding sphere
|
||||
local bs3
|
||||
|
||||
local v3 = require((...):match("^(.*)bs3$").."v3")
|
||||
|
||||
-- bs3 methods
|
||||
-- Unless specified otherwise, all operations are done in place and do not create a bounding sphere.
|
||||
local bs3_mt = {
|
||||
-- Clone the bounding sphere. Returns a new bounding sphere.
|
||||
clone = function(self)
|
||||
return bs3(self.center:clone(), self.radius)
|
||||
end,
|
||||
|
||||
-- Set bounding sphere
|
||||
set = function(self, center, radius)
|
||||
self.center:set(center)
|
||||
self.radius = radius
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Retrieve center vectors and radius
|
||||
unpack = function(self)
|
||||
return self.center, self.radius
|
||||
end,
|
||||
|
||||
-- Test if the bounding sphere collide with another bounding sphere, bounding box or point
|
||||
collide = function(self, other)
|
||||
if other.radius then -- bs3
|
||||
return (self.center[1] - other.center[1]) * (self.center[1] - other.center[1]) +
|
||||
(self.center[2] - other.center[2]) * (self.center[2] - other.center[2]) +
|
||||
(self.center[3] - other.center[3]) * (self.center[3] - other.center[3])
|
||||
<= (self.radius + other.radius) * (self.radius + other.radius)
|
||||
elseif other.min then -- bb3
|
||||
local x = math.max(other.min[1], math.min(self.center[1], other.max[1]))
|
||||
local y = math.max(other.min[2], math.min(self.center[2], other.max[2]))
|
||||
local z = math.max(other.min[3], math.min(self.center[3], other.max[3]))
|
||||
return (self.center[1] - x) * (self.center[1] - x) +
|
||||
(self.center[2] - y) * (self.center[2] - y) +
|
||||
(self.center[3] - z) * (self.center[3] - z)
|
||||
<= self.radius * self.radius
|
||||
else -- v3
|
||||
return (self.center[1] - other[1]) * (self.center[1] - other[1]) +
|
||||
(self.center[2] - other[2]) * (self.center[2] - other[2]) +
|
||||
(self.center[3] - other[3]) * (self.center[3] - other[3])
|
||||
<= self.radius * self.radius
|
||||
end
|
||||
end,
|
||||
|
||||
-- Extend the bounding sphere by v (number)
|
||||
extend = function(self, v)
|
||||
self.radius = self.radius + v
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Modify the bounding sphere so that it include v (v3 or bs3)
|
||||
include = function(self, v)
|
||||
if v.min then -- bounding sphere
|
||||
self.radius = (self.center:distance(v.center) + self.radius + v.radius) / 2
|
||||
self.center = v.center + (self.center - v.center):normalize() * (self.radius - v.radius)
|
||||
else -- vector
|
||||
self.radius = (self.center:distance(v) + self.radius) / 2
|
||||
self.center = v + (self.center - v):normalize() * self.radius
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Common operations with vectors. Returns a new bounding box.
|
||||
__sub = function(self, other)
|
||||
return bs3(self.center - other, self.radius)
|
||||
end,
|
||||
__add = function(self, other)
|
||||
return bs3(self.center + other, self.radius)
|
||||
end,
|
||||
__unm = function(self)
|
||||
return bs3(-self.center, self.radius)
|
||||
end,
|
||||
__div = function(self, other)
|
||||
return bs3(self.center, self.radius / other)
|
||||
end,
|
||||
__mul = function(self, other)
|
||||
return bs3(self.center, self.radius * other)
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return ("bs3(%s,%s)"):format(self.center, self.radius)
|
||||
end
|
||||
}
|
||||
bs3_mt.__index = bs3_mt
|
||||
|
||||
bs3 = setmetatable({
|
||||
-- Calculate the mesh's bounding sphere. Returns a new bounding sphere.
|
||||
fromMesh = function(mesh, tr)
|
||||
local bs
|
||||
|
||||
-- Get VertexPosition attribute index in vertex data
|
||||
local iposition
|
||||
for i, t in ipairs(mesh:getVertexFormat()) do
|
||||
if t[1] == "VertexPosition" then
|
||||
if t[3] ~= 3 then error("mesh vertices don't have a 3 dimensional position") end
|
||||
iposition = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if not iposition then error("mesh doesn't have VertexPosition attributes") end
|
||||
|
||||
-- Retrieve vertices
|
||||
local vmap = mesh:getVertexMap()
|
||||
if vmap then
|
||||
local min, max = mesh:getDrawRange()
|
||||
if not min then
|
||||
min, max = 1, #vmap
|
||||
end
|
||||
local v = v3(mesh:getVertexAttribute(vmap[min], iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bs = bs3(v:clone(),0)
|
||||
for i=min+1, max do
|
||||
v = v3(mesh:getVertexAttribute(vmap[i], iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bs:include(v)
|
||||
end
|
||||
else
|
||||
local min, max = mesh:getDrawRange()
|
||||
if not min then
|
||||
min, max = 1, mesh:getVertexCount()
|
||||
end
|
||||
local v = v3(mesh:getVertexAttribute(min, iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bs = bs3(v:clone(),0)
|
||||
for i=min+1, max do
|
||||
v = v3(mesh:getVertexAttribute(i, iposition))
|
||||
if tr then v:transform(tr) end
|
||||
bs:include(v)
|
||||
end
|
||||
end
|
||||
|
||||
return bs
|
||||
end
|
||||
}, {
|
||||
-- center will not be copied.
|
||||
__call = function(self, center, radius)
|
||||
return setmetatable({ center = v3(center), radius = radius }, bs3_mt)
|
||||
end
|
||||
})
|
||||
|
||||
return bs3
|
||||
246
math/m4.lua
Normal file
246
math/m4.lua
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
--- 4x4 matrix.
|
||||
-- Reminder: they are represented in row-major order, **unlike** GLM which is column-major.
|
||||
local m4
|
||||
|
||||
local cos, sin, tan = math.cos, math.sin, math.tan
|
||||
local v3 = require((...):match("^(.*)m4$").."v3")
|
||||
|
||||
local tmpv3 = v3(0,0,0) -- temporary vector used in our computations
|
||||
|
||||
-- m4 methods
|
||||
-- Unless specified otherwise, all operations are done in place and do not create a new matrix.
|
||||
local m4_mt = {
|
||||
-- Clone the matrix. Returns a new matrix.
|
||||
clone = function(self)
|
||||
return m4{
|
||||
self[1], self[2], self[3], self[4],
|
||||
self[5], self[6], self[7], self[8],
|
||||
self[9], self[10], self[11], self[12],
|
||||
self[13], self[14], self[15], self[16]
|
||||
}
|
||||
end,
|
||||
|
||||
-- Set matrix
|
||||
set = function(self, other)
|
||||
self[1], self[2], self[3], self[4] = other[1], other[2], other[3], other[4]
|
||||
self[5], self[6], self[7], self[8] = other[5], other[6], other[7], other[8]
|
||||
self[9], self[10], self[11], self[12] = other[9], other[10], other[11], other[12]
|
||||
self[13], self[14], self[15], self[16] = other[13], other[14], other[15], other[16]
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Retrieve values
|
||||
unpack = function(self)
|
||||
return self[1], self[2], self[3], self[4],
|
||||
self[5], self[6], self[7], self[8],
|
||||
self[9], self[10], self[11], self[12],
|
||||
self[13], self[14], self[15], self[16]
|
||||
end,
|
||||
|
||||
-- Apply a transformation matrix to our matrix
|
||||
transform = function(self, tr)
|
||||
local s1, s2, s3, s4 = self[1], self[2], self[3], self[4]
|
||||
local s5, s6, s7, s8 = self[5], self[6], self[7], self[8]
|
||||
local s9, s10, s11, s12 = self[9], self[10], self[11], self[12]
|
||||
local s13, s14, s15, s16 = self[13], self[14], self[15], self[16]
|
||||
|
||||
self[1] = tr[1] * s1 + tr[2] * s5 + tr[3] * s9 + tr[4] * s13
|
||||
self[2] = tr[1] * s2 + tr[2] * s6 + tr[3] * s10 + tr[4] * s14
|
||||
self[3] = tr[1] * s3 + tr[2] * s7 + tr[3] * s11 + tr[4] * s15
|
||||
self[4] = tr[1] * s4 + tr[2] * s8 + tr[3] * s12 + tr[4] * s16
|
||||
|
||||
self[5] = tr[5] * s1 + tr[6] * s5 + tr[7] * s9 + tr[8] * s13
|
||||
self[6] = tr[5] * s2 + tr[6] * s6 + tr[7] * s10 + tr[8] * s14
|
||||
self[7] = tr[5] * s3 + tr[6] * s7 + tr[7] * s11 + tr[8] * s15
|
||||
self[8] = tr[5] * s4 + tr[6] * s8 + tr[7] * s12 + tr[8] * s16
|
||||
|
||||
self[9] = tr[9] * s1 + tr[10] * s5 + tr[11] * s9 + tr[12] * s13
|
||||
self[10] = tr[9] * s2 + tr[10] * s6 + tr[11] * s10 + tr[12] * s14
|
||||
self[11] = tr[9] * s3 + tr[10] * s7 + tr[11] * s11 + tr[12] * s15
|
||||
self[12] = tr[9] * s4 + tr[10] * s8 + tr[11] * s12 + tr[12] * s16
|
||||
|
||||
self[13] = tr[13] * s1 + tr[14] * s5 + tr[15] * s9 + tr[16] * s13
|
||||
self[14] = tr[13] * s2 + tr[14] * s6 + tr[15] * s10 + tr[16] * s14
|
||||
self[15] = tr[13] * s3 + tr[14] * s7 + tr[15] * s11 + tr[16] * s15
|
||||
self[16] = tr[13] * s4 + tr[14] * s8 + tr[15] * s12 + tr[16] * s16
|
||||
|
||||
return self
|
||||
end,
|
||||
translate = function(self, v)
|
||||
return self:transform(m4.translate(v))
|
||||
end,
|
||||
scale = function(self, v)
|
||||
return self:transform(m4.scale(v))
|
||||
end,
|
||||
rotate = function(self, angle, v)
|
||||
return self:transform(m4.rotate(angle, v))
|
||||
end,
|
||||
shear = function(self, vxy, vyz)
|
||||
return self:transform(m4.shear(vxy, vyz))
|
||||
end,
|
||||
|
||||
-- Common operations. Returns a new matrix.
|
||||
__mul = function(self, other)
|
||||
return m4{
|
||||
self[1] * other[1] + self[2] * other[5] + self[3] * other[9] + self[4] * other[13],
|
||||
self[1] * other[2] + self[2] * other[6] + self[3] * other[10] + self[4] * other[14],
|
||||
self[1] * other[3] + self[2] * other[7] + self[3] * other[11] + self[4] * other[15],
|
||||
self[1] * other[4] + self[2] * other[8] + self[3] * other[12] + self[4] * other[16],
|
||||
|
||||
self[5] * other[1] + self[6] * other[5] + self[7] * other[9] + self[8] * other[13],
|
||||
self[5] * other[2] + self[6] * other[6] + self[7] * other[10] + self[8] * other[14],
|
||||
self[5] * other[3] + self[6] * other[7] + self[7] * other[11] + self[8] * other[15],
|
||||
self[5] * other[4] + self[6] * other[8] + self[7] * other[12] + self[8] * other[16],
|
||||
|
||||
self[9] * other[1] + self[10] * other[5] + self[11] * other[9] + self[12] * other[13],
|
||||
self[9] * other[2] + self[10] * other[6] + self[11] * other[10] + self[12] * other[14],
|
||||
self[9] * other[3] + self[10] * other[7] + self[11] * other[11] + self[12] * other[15],
|
||||
self[9] * other[4] + self[10] * other[8] + self[11] * other[12] + self[12] * other[16],
|
||||
|
||||
self[13] * other[1] + self[14] * other[5] + self[15] * other[9] + self[16] * other[13],
|
||||
self[13] * other[2] + self[14] * other[6] + self[15] * other[10] + self[16] * other[14],
|
||||
self[13] * other[3] + self[14] * other[7] + self[15] * other[11] + self[16] * other[15],
|
||||
self[13] * other[4] + self[14] * other[8] + self[15] * other[12] + self[16] * other[16]
|
||||
}
|
||||
end,
|
||||
__add = function(self, other)
|
||||
return m4{
|
||||
self[1] + other[1], self[2] + other[2], self[3] + other[3], self[4] + other[4],
|
||||
self[5] + other[5], self[6] + other[6], self[7] + other[7], self[8] + other[8],
|
||||
self[9] + other[9], self[10] + other[10], self[11] + other[11], self[12] + other[12],
|
||||
self[13] + other[13], self[14] + other[14], self[15] + other[15], self[16] + other[16],
|
||||
}
|
||||
end,
|
||||
__sub = function(self, other)
|
||||
return m4{
|
||||
self[1] - other[1], self[2] - other[2], self[3] - other[3], self[4] - other[4],
|
||||
self[5] - other[5], self[6] - other[6], self[7] - other[7], self[8] - other[8],
|
||||
self[9] - other[9], self[10] - other[10], self[11] - other[11], self[12] - other[12],
|
||||
self[13] - other[13], self[14] - other[14], self[15] - other[15], self[16] - other[16],
|
||||
}
|
||||
end,
|
||||
__unm = function(self)
|
||||
return m4{
|
||||
-self[1], -self[2], -self[3], -self[4],
|
||||
-self[5], -self[6], -self[7], -self[8],
|
||||
-self[9], -self[10], -self[11], -self[12],
|
||||
-self[13], -self[14], -self[15], -self[16]
|
||||
}
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
local mt = getmetatable(self)
|
||||
setmetatable(self, nil)
|
||||
local str = "m4: "..(tostring(self):gsub("^table: ", ""))
|
||||
setmetatable(self, mt)
|
||||
return str
|
||||
end
|
||||
}
|
||||
m4_mt.__index = m4_mt
|
||||
|
||||
m4 = setmetatable({
|
||||
-- Common transformation to create new matrices from
|
||||
identity = function()
|
||||
return m4{
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
translate = function(v)
|
||||
return m4{
|
||||
1, 0, 0, v[1],
|
||||
0, 1, 0, v[2],
|
||||
0, 0, 1, v[3],
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
scale = function(v)
|
||||
return m4{
|
||||
v[1], 0, 0, 0,
|
||||
0, v[2], 0, 0,
|
||||
0, 0, v[3], 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
rotate = function(angle, v)
|
||||
local c = cos(angle)
|
||||
local s = sin(angle)
|
||||
local x, y, z = tmpv3:set(v):normalize():unpack()
|
||||
local tx, ty, tz = (1 - c) * x, (1 - c) * y, (1 - c) * z
|
||||
return m4{
|
||||
c + tx * x, ty * x - s * z, tz * x + s * y, 0,
|
||||
tx * y + s * z, c + ty * y, tz * y - s * x, 0,
|
||||
tx * z - s * y, ty * z + s * x, c + tz * z, 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
-- vxy: shearing vector in the (xy) plane
|
||||
-- vyz: shearing vector in the (yz) plane
|
||||
shear = function(vxy, vyz)
|
||||
return m4{
|
||||
1, vxy[1], vyz[1], 0,
|
||||
vxy[2], 1, vyz[2], 0,
|
||||
vxy[3], vyz[3], 1, 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
|
||||
-- Projection matrix constructor (right-handed).
|
||||
perspective = function(fovy, aspect, zNear, zFar) -- If zFar is not specified, will return an infinite perspective matrix.
|
||||
local f = 1 / tan(fovy / 2)
|
||||
if zFar then
|
||||
return m4{
|
||||
f / aspect, 0, 0, 0,
|
||||
0, f, 0, 0,
|
||||
0, 0, (zFar + zNear) / (zNear - zFar), (2 * zFar * zNear) / (zNear - zFar),
|
||||
0, 0, -1, 0
|
||||
}
|
||||
else
|
||||
return m4{
|
||||
f / aspect, 0, 0, 0,
|
||||
0, f, 0, 0,
|
||||
0, 0, -1, -2 * zNear,
|
||||
0, 0, -1, 0
|
||||
}
|
||||
end
|
||||
end,
|
||||
orthographic = function(xmag, ymag, znear, zfar)
|
||||
return m4{
|
||||
1 / xmag, 0, 0, 0,
|
||||
0, 1 / ymag, 0, 0,
|
||||
0, 0, 2 / (znear - zfar), (zfar + znear) / (znear - zfar),
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
lookAt = function(eye, center, up)
|
||||
center, up = v3(center), v3(up)
|
||||
local f = (center - eye):normalize()
|
||||
local s = f:cross(up):normalize()
|
||||
local u = s:cross(f)
|
||||
return m4{
|
||||
s[1], s[2], s[3], -s:dot(eye),
|
||||
u[1], u[2], u[3], -u:dot(eye),
|
||||
-f[1], -f[2], -f[3], f:dot(eye),
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
|
||||
-- Create a new matrix from column major list
|
||||
fromColumnMajor = function(m)
|
||||
return m4{
|
||||
m[1], m[5], m[9], m[13],
|
||||
m[2], m[6], m[10], m[14],
|
||||
m[3], m[7], m[11], m[15],
|
||||
m[4], m[8], m[12], m[16]
|
||||
}
|
||||
end
|
||||
}, {
|
||||
-- Will not copy the table.
|
||||
__call = function(self, t)
|
||||
return setmetatable(t, m4_mt)
|
||||
end
|
||||
})
|
||||
|
||||
return m4
|
||||
90
math/qt.lua
Normal file
90
math/qt.lua
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
--- Quaternion
|
||||
local qt
|
||||
|
||||
local sin, cos = math.sin, math.cos
|
||||
local m4 = require((...):match("^(.*)qt$").."m4")
|
||||
|
||||
-- qt methods
|
||||
-- Unless specified otherwise, all operations are done in place and do not create a new quaternion.
|
||||
local qt_mt = {
|
||||
-- Clone the quaternion. Returns a new quaternion.
|
||||
clone = function(self)
|
||||
return qt(self[1], self[2], self[3], self[4])
|
||||
end,
|
||||
|
||||
-- Set quaternion
|
||||
set = function(self, other)
|
||||
self[1], self[2], self[3], self[4] = other[1], other[2], other[3], other[4]
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Retrieve coordinates
|
||||
unpack = function(self)
|
||||
return self[1], self[2], self[3], self[4]
|
||||
end,
|
||||
|
||||
-- Convert to m4 rotation matrix. Returns a new 4x4 matrix.
|
||||
toM4 = function(self)
|
||||
local x, y, z, w = self[1], self[2], self[3], self[4]
|
||||
local xx, yy, zz = x*x, y*y, z*z
|
||||
local xw, xy, xz = x*w, x*y, x*z
|
||||
local yw, yz = y*w, y*z
|
||||
local zw = z*w
|
||||
return m4{
|
||||
1-2*(yy+zz), 2*(xy-zw), 2*(xz+yw), 0,
|
||||
2*(xy+zw), 1-2*(xx+zz), 2*(yz-xw), 0,
|
||||
2*(xz-yw), 2*(yz+xw), 1-2*(xx+yy), 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end,
|
||||
|
||||
-- Common operations. Returns a new quaternion.
|
||||
__sub = function(self, other)
|
||||
return qt(self[1] - other[1], self[2] - other[2], self[3] - other[3], self[4] - other[4])
|
||||
end,
|
||||
__add = function(self, other)
|
||||
return qt(self[1] + other[1], self[2] + other[2], self[3] + other[3], self[4] - other[4])
|
||||
end,
|
||||
__unm = function(self)
|
||||
return qt(-self[1], -self[2], -self[3], -self[4])
|
||||
end,
|
||||
__mul = function(self, other)
|
||||
local sx, sy, sz, sw = self[1], self[2], self[3], self[4]
|
||||
local ox, oy, oz, ow = other[1], other[2], other[3], other[4]
|
||||
return qt(sw * ow - sx * ox - sy * oy - sz * oz, sx * ow + sw * ox + sy * oz - sz * oy, sy * ow + sw * oy + sz * ox - sx * oz, sz * ow + sw * oz + sx * oy - sy * ox)
|
||||
end,
|
||||
|
||||
__eq = function(self, other)
|
||||
return self[1] == other[1] and self[2] == other[2] and self[3] == other[3] and self[4] == other[4]
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return ("qt(%s,%s,%s,%s)"):format(self[1], self[2], self[3], self[4])
|
||||
end
|
||||
}
|
||||
qt_mt.__index = qt_mt
|
||||
|
||||
qt = setmetatable({
|
||||
--- Create a new identity quaternion
|
||||
identity = function()
|
||||
return qt(0, 0, 0, 1)
|
||||
end,
|
||||
|
||||
--- Create a new quaternion from a rotation
|
||||
fromAngleAxis = function(angle, axis)
|
||||
local halfAngle = angle / 2
|
||||
local s = sin(halfAngle)
|
||||
return qt(axis[1] * s, axis[2] * s, axis[3] * s, cos(halfAngle))
|
||||
end,
|
||||
}, {
|
||||
-- If x is a table, will reuse it without copying.
|
||||
__call = function(self, x, y, z, w)
|
||||
if type(x) == "number" then
|
||||
return setmetatable({ x, y, z, w }, qt_mt)
|
||||
else
|
||||
return setmetatable(x, qt_mt)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
return qt
|
||||
114
math/v3.lua
Normal file
114
math/v3.lua
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
--- 3D vector
|
||||
local v3
|
||||
|
||||
local sqrt = math.sqrt
|
||||
|
||||
-- v3 methods
|
||||
-- Unless specified otherwise, all operations are done in place and do not create a new vector.
|
||||
local v3_mt = {
|
||||
-- Clone the vector. Returns a new vector.
|
||||
clone = function(self)
|
||||
return v3(self[1], self[2], self[3])
|
||||
end,
|
||||
|
||||
-- Set vector
|
||||
set = function(self, other)
|
||||
self[1], self[2], self[3] = other[1], other[2], other[3]
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Retrieve coordinates
|
||||
unpack = function(self)
|
||||
return self[1], self[2], self[3]
|
||||
end,
|
||||
|
||||
-- Normalize
|
||||
normalize = function(self)
|
||||
local x, y, z = self[1], self[2], self[3]
|
||||
local l = sqrt(x*x + y*y + z*z)
|
||||
self[1], self[2], self[3] = x/l, y/l, z/l
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Vector product. Returns a new vector.
|
||||
cross = function(self, other)
|
||||
local x1, y1, z1 = self[1], self[2], self[3]
|
||||
local x2, y2, z2 = other[1], other[2], other[3]
|
||||
return v3(
|
||||
y1*z2 - z1*y2,
|
||||
z1*x2 - x1*z2,
|
||||
x1*y2 - y1*x2
|
||||
)
|
||||
end,
|
||||
dot = function(self, other)
|
||||
return self[1] * other[1] + self[2] * other[2] + self[3] * other[3]
|
||||
end,
|
||||
|
||||
-- Transform by a mat4
|
||||
transform = function(self, matrix)
|
||||
local x, y, z = self[1], self[2], self[3]
|
||||
-- Transform matrix * Homogeneous coordinates
|
||||
local mx = matrix[1] * x + matrix[2] * y + matrix[3] * z + matrix[4]
|
||||
local my = matrix[5] * x + matrix[6] * y + matrix[7] * z + matrix[8]
|
||||
local mz = matrix[9] * x + matrix[10] * y + matrix[11] * z + matrix[12]
|
||||
local mw = matrix[13] * x + matrix[14] * y + matrix[15] * z + matrix[16]
|
||||
-- Go back to euclidian coordinates
|
||||
self[1], self[2], self[3] = mx/mw, my/mw, mz/mw
|
||||
return self
|
||||
end,
|
||||
|
||||
-- Length
|
||||
len2 = function(self)
|
||||
local x, y, z = self[1], self[2], self[3]
|
||||
return x*x + y*y + z*z
|
||||
end,
|
||||
len = function(self)
|
||||
local x, y, z = self[1], self[2], self[3]
|
||||
return sqrt(x*x + y*y + z*z)
|
||||
end,
|
||||
|
||||
-- Distance
|
||||
distance = function(self, other)
|
||||
return (self - other):len()
|
||||
end,
|
||||
distance2 = function(self, other)
|
||||
return (self - other):len2()
|
||||
end,
|
||||
|
||||
-- Common operations. Returns a new vector.
|
||||
__sub = function(self, other)
|
||||
return v3(self[1] - other[1], self[2] - other[2], self[3] - other[3])
|
||||
end,
|
||||
__add = function(self, other)
|
||||
return v3(self[1] + other[1], self[2] + other[2], self[3] + other[3])
|
||||
end,
|
||||
__unm = function(self)
|
||||
return v3(-self[1], -self[2], -self[3])
|
||||
end,
|
||||
__div = function(self, other)
|
||||
return v3(self[1] / other, self[2] / other, self[3] / other)
|
||||
end,
|
||||
__mul = function(self, other)
|
||||
return v3(self[1] * other, self[2] * other, self[3] * other)
|
||||
end,
|
||||
|
||||
__eq = function(self, other)
|
||||
return self[1] == other[1] and self[2] == other[2] and self[3] == other[3]
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return ("v3(%s,%s,%s)"):format(self[1], self[2], self[3])
|
||||
end
|
||||
}
|
||||
v3_mt.__index = v3_mt
|
||||
|
||||
-- If x is a table, will reuse it without copying.
|
||||
v3 = function(x, y, z)
|
||||
if type(x) == "number" then
|
||||
return setmetatable({ x, y, z }, v3_mt)
|
||||
else
|
||||
return setmetatable(x, v3_mt)
|
||||
end
|
||||
end
|
||||
|
||||
return v3
|
||||
21
shader/pick.lua
Normal file
21
shader/pick.lua
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
return [[
|
||||
|
||||
# ifdef VERTEX
|
||||
uniform mat4 scene_transform; // world -> view transform
|
||||
uniform mat4 model_transform; // model -> world transform
|
||||
|
||||
vec4 position(mat4 love_transform, vec4 vertex_position) {
|
||||
return scene_transform * model_transform * vertex_position;
|
||||
}
|
||||
# endif
|
||||
|
||||
# ifdef PIXEL
|
||||
uniform vec3 pick_color;
|
||||
|
||||
vec4 effect(vec4 color, sampler2D texture, vec2 texture_coords, vec2 screen_coords) {
|
||||
if (texture2D(texture, texture_coords).a < 0.1) discard;
|
||||
return vec4(pick_color, 1);
|
||||
}
|
||||
# endif
|
||||
|
||||
]]
|
||||
74
shader/render.lua
Normal file
74
shader/render.lua
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
return [[
|
||||
|
||||
varying vec3 normal_world; // vertex normal in world space
|
||||
varying vec4 position_world; // fragment position in world space
|
||||
|
||||
# ifdef VERTEX
|
||||
uniform mat4 scene_transform; // world -> view transform
|
||||
uniform mat4 model_transform; // model -> world transform
|
||||
|
||||
attribute vec3 VertexNormal;
|
||||
|
||||
vec4 position(mat4 love_transform, vec4 vertex_position) {
|
||||
normal_world = (model_transform * vec4(VertexNormal, 0)).xyz;
|
||||
position_world = model_transform * vertex_position;
|
||||
return scene_transform * position_world;
|
||||
}
|
||||
# endif
|
||||
|
||||
# ifdef PIXEL
|
||||
# if LIGHT_COUNT > 0
|
||||
struct Light {
|
||||
int type; // 1 for ambient, 2 for directional, 3 for point
|
||||
vec3 position; // position for point lights, or direction for directional lights
|
||||
vec4 color; // diffuse color for point and directional lights, or ambient color for ambient light (alpha is intensity)
|
||||
};
|
||||
|
||||
uniform Light[LIGHT_COUNT] lights;
|
||||
# endif
|
||||
|
||||
uniform vec4 baseColorFactor;
|
||||
|
||||
vec4 effect(vec4 color, sampler2D texture, vec2 texture_coords, vec2 screen_coords) {
|
||||
// base color
|
||||
vec4 material_color = texture2D(texture, texture_coords) * baseColorFactor * color;
|
||||
if (material_color.a < 0.1) discard;
|
||||
|
||||
# if LIGHT_COUNT > 0
|
||||
// shading
|
||||
vec4 final_color = vec4(0.0, 0.0, 0.0, material_color.a);
|
||||
for (int i = 0; i < LIGHT_COUNT; i++) {
|
||||
Light light = lights[i];
|
||||
|
||||
// ambient
|
||||
if (light.type == 1) {
|
||||
final_color.rgb += material_color.rgb * light.color.rgb * light.color.a;
|
||||
|
||||
// directional
|
||||
} else if (light.type == 2) {
|
||||
// diffuse lighting
|
||||
vec3 n = normalize(normal_world);
|
||||
vec3 l = normalize(-light.position);
|
||||
final_color.rgb += material_color.rgb * light.color.rgb * light.color.a * max(dot(n, l), 0.0);
|
||||
|
||||
// point
|
||||
} else if (light.type == 3) {
|
||||
// diffuse lighting
|
||||
vec3 n = normalize(normal_world);
|
||||
vec3 l = normalize(light.position - position_world.xyz);
|
||||
float distance = length(light.position - position_world.xyz);
|
||||
final_color.rgb += material_color.rgb * light.color.rgb * light.color.a * max(dot(n, l), 0.0) / (distance * distance);
|
||||
}
|
||||
|
||||
// no specular because i'm lazy (TODO)
|
||||
}
|
||||
|
||||
// done
|
||||
return final_color;
|
||||
# else
|
||||
return material_color;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
]]
|
||||
118
test/AnimatedTriangle.gltf
Normal file
118
test/AnimatedTriangle.gltf
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"nodes" : [ 0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0,
|
||||
"rotation" : [ 0.0, 0.0, 0.0, 1.0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"meshes" : [
|
||||
{
|
||||
"primitives" : [ {
|
||||
"attributes" : {
|
||||
"POSITION" : 1
|
||||
},
|
||||
"indices" : 0
|
||||
} ]
|
||||
}
|
||||
],
|
||||
|
||||
"animations": [
|
||||
{
|
||||
"samplers" : [
|
||||
{
|
||||
"input" : 2,
|
||||
"interpolation" : "LINEAR",
|
||||
"output" : 3
|
||||
}
|
||||
],
|
||||
"channels" : [ {
|
||||
"sampler" : 0,
|
||||
"target" : {
|
||||
"node" : 0,
|
||||
"path" : "rotation"
|
||||
}
|
||||
} ]
|
||||
}
|
||||
],
|
||||
|
||||
"buffers" : [
|
||||
{
|
||||
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
|
||||
"byteLength" : 44
|
||||
},
|
||||
{
|
||||
"uri" : "data:application/octet-stream;base64,AAAAAAAAgD4AAAA/AABAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAPT9ND/0/TS/AAAAAAAAAAAAAAAAAACAPw==",
|
||||
"byteLength" : 100
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 0,
|
||||
"byteLength" : 6,
|
||||
"target" : 34963
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 8,
|
||||
"byteLength" : 36,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 1,
|
||||
"byteOffset" : 0,
|
||||
"byteLength" : 100
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5123,
|
||||
"count" : 3,
|
||||
"type" : "SCALAR",
|
||||
"max" : [ 2 ],
|
||||
"min" : [ 0 ]
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 3,
|
||||
"type" : "VEC3",
|
||||
"max" : [ 1.0, 1.0, 0.0 ],
|
||||
"min" : [ 0.0, 0.0, 0.0 ]
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 5,
|
||||
"type" : "SCALAR",
|
||||
"max" : [ 1.0 ],
|
||||
"min" : [ 0.0 ]
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"byteOffset" : 20,
|
||||
"componentType" : 5126,
|
||||
"count" : 5,
|
||||
"type" : "VEC4",
|
||||
"max" : [ 0.0, 0.0, 1.0, 1.0 ],
|
||||
"min" : [ 0.0, 0.0, 0.0, -0.707 ]
|
||||
}
|
||||
],
|
||||
|
||||
"asset" : {
|
||||
"version" : "2.0"
|
||||
}
|
||||
|
||||
}
|
||||
142
test/Box.gltf
Normal file
142
test/Box.gltf
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
{
|
||||
"asset": {
|
||||
"generator": "COLLADA2GLTF",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh": 0
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 2
|
||||
},
|
||||
"indices": 0,
|
||||
"mode": 4,
|
||||
"material": 0
|
||||
}
|
||||
],
|
||||
"name": "Mesh"
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"max": [
|
||||
23
|
||||
],
|
||||
"min": [
|
||||
0
|
||||
],
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 288,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"min": [
|
||||
-0.5,
|
||||
-0.5,
|
||||
-0.5
|
||||
],
|
||||
"type": "VEC3"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.800000011920929,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0
|
||||
},
|
||||
"name": "Red"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 576,
|
||||
"byteLength": 72,
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteOffset": 0,
|
||||
"byteLength": 576,
|
||||
"byteStride": 12,
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 648,
|
||||
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
|
||||
}
|
||||
]
|
||||
}
|
||||
219
test/Duck.gltf
Normal file
219
test/Duck.gltf
Normal file
File diff suppressed because one or more lines are too long
85
test/SimpleMeshes.gltf
Normal file
85
test/SimpleMeshes.gltf
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"nodes" : [ 0, 1]
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0
|
||||
},
|
||||
{
|
||||
"mesh" : 0,
|
||||
"translation" : [ 1.0, 0.0, 0.0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"meshes" : [
|
||||
{
|
||||
"primitives" : [ {
|
||||
"attributes" : {
|
||||
"POSITION" : 1,
|
||||
"NORMAL" : 2
|
||||
},
|
||||
"indices" : 0
|
||||
} ]
|
||||
}
|
||||
],
|
||||
|
||||
"buffers" : [
|
||||
{
|
||||
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8=",
|
||||
"byteLength" : 80
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 0,
|
||||
"byteLength" : 6,
|
||||
"target" : 34963
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 8,
|
||||
"byteLength" : 72,
|
||||
"byteStride" : 12,
|
||||
"target" : 34962
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5123,
|
||||
"count" : 3,
|
||||
"type" : "SCALAR",
|
||||
"max" : [ 2 ],
|
||||
"min" : [ 0 ]
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 3,
|
||||
"type" : "VEC3",
|
||||
"max" : [ 1.0, 1.0, 0.0 ],
|
||||
"min" : [ 0.0, 0.0, 0.0 ]
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"byteOffset" : 36,
|
||||
"componentType" : 5126,
|
||||
"count" : 3,
|
||||
"type" : "VEC3",
|
||||
"max" : [ 0.0, 0.0, 1.0 ],
|
||||
"min" : [ 0.0, 0.0, 1.0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"asset" : {
|
||||
"version" : "2.0"
|
||||
}
|
||||
}
|
||||
|
||||
70
test/Triangle.gltf
Normal file
70
test/Triangle.gltf
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"nodes" : [ 0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0
|
||||
}
|
||||
],
|
||||
|
||||
"meshes" : [
|
||||
{
|
||||
"primitives" : [ {
|
||||
"attributes" : {
|
||||
"POSITION" : 1
|
||||
},
|
||||
"indices" : 0
|
||||
} ]
|
||||
}
|
||||
],
|
||||
|
||||
"buffers" : [
|
||||
{
|
||||
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
|
||||
"byteLength" : 44
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 0,
|
||||
"byteLength" : 6,
|
||||
"target" : 34963
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 8,
|
||||
"byteLength" : 36,
|
||||
"target" : 34962
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5123,
|
||||
"count" : 3,
|
||||
"type" : "SCALAR",
|
||||
"max" : [ 2 ],
|
||||
"min" : [ 0 ]
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 3,
|
||||
"type" : "VEC3",
|
||||
"max" : [ 1.0, 1.0, 0.0 ],
|
||||
"min" : [ 0.0, 0.0, 0.0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"asset" : {
|
||||
"version" : "2.0"
|
||||
}
|
||||
}
|
||||
54
test/TriangleWithoutIndices.gltf
Normal file
54
test/TriangleWithoutIndices.gltf
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"nodes" : [ 0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0
|
||||
}
|
||||
],
|
||||
|
||||
"meshes" : [
|
||||
{
|
||||
"primitives" : [ {
|
||||
"attributes" : {
|
||||
"POSITION" : 0
|
||||
}
|
||||
} ]
|
||||
}
|
||||
],
|
||||
|
||||
"buffers" : [
|
||||
{
|
||||
"uri" : "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA",
|
||||
"byteLength" : 36
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteOffset" : 0,
|
||||
"byteLength" : 36,
|
||||
"target" : 34962
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 3,
|
||||
"type" : "VEC3",
|
||||
"max" : [ 1.0, 1.0, 0.0 ],
|
||||
"min" : [ 0.0, 0.0, 0.0 ]
|
||||
}
|
||||
],
|
||||
|
||||
"asset" : {
|
||||
"version" : "2.0"
|
||||
}
|
||||
}
|
||||
BIN
test/hyke.png
Normal file
BIN
test/hyke.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
|
|
@ -1,3 +1,4 @@
|
|||
require("candran").setup()
|
||||
local wirefame = require("wirefame")
|
||||
|
||||
love.graphics.setLineStyle("rough")
|
||||
|
|
@ -6,23 +7,22 @@ love.window.setMode(1000, 1000, { vsync = false })
|
|||
|
||||
wirefame.defaultColor = { 1, 0, 0, 1 }
|
||||
|
||||
local camera = { 0, 1, 3 }
|
||||
|
||||
local frame = wirefame.model("frame.obj")
|
||||
local model = wirefame.model("test/Duck.gltf")
|
||||
:setColor(1, 1, 1)
|
||||
|
||||
local model = wirefame.model("metaknight.obj")
|
||||
:scale(1/15, 1/15, 1/15)
|
||||
:translate(0, -0.7, 0)
|
||||
local img = wirefame.model(love.graphics.newImage("hyke.png")):scale{1/100, 1/100, 1/100}
|
||||
|
||||
-- local model = wirefame.model("test/metaknight.obj")
|
||||
-- :scale{1/15, 1/15, 1/15}
|
||||
-- :translate{0, -0.7, 0}
|
||||
|
||||
local scene = wirefame.scene()
|
||||
:setViewport(0, 0, 1000, 1000)
|
||||
:lookAt(camera, {0,0,0}, {0,1,0})
|
||||
:setViewDistance(5)
|
||||
:add(frame, model)
|
||||
:setPerspective(math.rad(60), 1, .1, 100)
|
||||
:lookAt({0,0,3}, {0,0,0}, {0,1,0})
|
||||
:add(model, img)
|
||||
|
||||
function love.update(dt)
|
||||
model:rotate(dt/2, {0, 1, 0})
|
||||
scene:rotate(dt/2, {0, 1, 0})
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
|
|
|
|||
222
test/untitled.gltf
Normal file
222
test/untitled.gltf
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
{
|
||||
"asset" : {
|
||||
"generator" : "Khronos glTF Blender I/O v1.0.5",
|
||||
"version" : "2.0"
|
||||
},
|
||||
"extensionsUsed" : [
|
||||
"KHR_lights_punctual"
|
||||
],
|
||||
"extensionsRequired" : [
|
||||
"KHR_lights_punctual"
|
||||
],
|
||||
"extensions" : {
|
||||
"KHR_lights_punctual" : {
|
||||
"lights" : [
|
||||
{
|
||||
"color" : [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"intensity" : 1000,
|
||||
"type" : "point",
|
||||
"name" : "Light"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"name" : "Scene",
|
||||
"nodes" : [
|
||||
0,
|
||||
2,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0,
|
||||
"name" : "Cube"
|
||||
},
|
||||
{
|
||||
"extensions" : {
|
||||
"KHR_lights_punctual" : {
|
||||
"light" : 0
|
||||
}
|
||||
},
|
||||
"name" : "Light_Orientation",
|
||||
"rotation" : [
|
||||
-0.7071067690849304,
|
||||
0,
|
||||
0,
|
||||
0.7071067690849304
|
||||
]
|
||||
},
|
||||
{
|
||||
"children" : [
|
||||
1
|
||||
],
|
||||
"name" : "Light",
|
||||
"rotation" : [
|
||||
0.16907575726509094,
|
||||
0.7558803558349609,
|
||||
-0.27217137813568115,
|
||||
0.570947527885437
|
||||
],
|
||||
"translation" : [
|
||||
4.076245307922363,
|
||||
5.903861999511719,
|
||||
-1.0054539442062378
|
||||
]
|
||||
},
|
||||
{
|
||||
"camera" : 0,
|
||||
"name" : "Camera_Orientation",
|
||||
"rotation" : [
|
||||
-0.7071067690849304,
|
||||
0,
|
||||
0,
|
||||
0.7071067690849304
|
||||
]
|
||||
},
|
||||
{
|
||||
"children" : [
|
||||
3
|
||||
],
|
||||
"name" : "Camera",
|
||||
"rotation" : [
|
||||
0.483536034822464,
|
||||
0.33687159419059753,
|
||||
-0.20870360732078552,
|
||||
0.7804827094078064
|
||||
],
|
||||
"translation" : [
|
||||
7.358891487121582,
|
||||
4.958309173583984,
|
||||
6.925790786743164
|
||||
]
|
||||
},
|
||||
{
|
||||
"extras" : {
|
||||
"prop" : 1.7699999809265137
|
||||
},
|
||||
"name" : "Empty",
|
||||
"translation" : [
|
||||
0,
|
||||
0,
|
||||
-4.324187755584717
|
||||
]
|
||||
}
|
||||
],
|
||||
"cameras" : [
|
||||
{
|
||||
"name" : "Camera",
|
||||
"perspective" : {
|
||||
"yfov" : 0.39959652046304894,
|
||||
"zfar" : 100,
|
||||
"znear" : 0.10000000149011612
|
||||
},
|
||||
"type" : "perspective"
|
||||
}
|
||||
],
|
||||
"materials" : [
|
||||
{
|
||||
"doubleSided" : true,
|
||||
"name" : "Material",
|
||||
"pbrMetallicRoughness" : {
|
||||
"baseColorFactor" : [
|
||||
0.800000011920929,
|
||||
0.800000011920929,
|
||||
0.800000011920929,
|
||||
1
|
||||
],
|
||||
"metallicFactor" : 0,
|
||||
"roughnessFactor" : 0.4000000059604645
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes" : [
|
||||
{
|
||||
"name" : "Cube",
|
||||
"primitives" : [
|
||||
{
|
||||
"attributes" : {
|
||||
"POSITION" : 0,
|
||||
"NORMAL" : 1,
|
||||
"TEXCOORD_0" : 2
|
||||
},
|
||||
"indices" : 3,
|
||||
"material" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"max" : [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min" : [
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"type" : "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView" : 3,
|
||||
"componentType" : 5123,
|
||||
"count" : 36,
|
||||
"type" : "SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 288,
|
||||
"byteOffset" : 0
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 288,
|
||||
"byteOffset" : 288
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 192,
|
||||
"byteOffset" : 576
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 72,
|
||||
"byteOffset" : 768
|
||||
}
|
||||
],
|
||||
"buffers" : [
|
||||
{
|
||||
"byteLength" : 840,
|
||||
"uri" : "data:application/octet-stream;base64,AACAPwAAgD8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAgPwAAgD4AACA/AAAAPwAAYD8AAAA/AABgPwAAgD4AACA/AAAAAAAAwD4AAAAAAADAPgAAgD4AACA/AACAPgAAID8AAEA/AADAPgAAQD8AAMA+AACAPwAAID8AAIA/AAAAPgAAgD4AAAA+AAAAPwAAwD4AAAA/AADAPgAAgD4AACA/AACAPgAAwD4AAIA+AADAPgAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AABAPwAAID8AAEA/AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"
|
||||
}
|
||||
]
|
||||
}
|
||||
1533
wirefame.lua
1533
wirefame.lua
File diff suppressed because it is too large
Load diff
170
wirefame/group.lua
Normal file
170
wirefame/group.lua
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
local wirefame = require((...):match("^(.*)%.wirefame%.group$"))
|
||||
local model_mt = require((...):match("^(.*)group$").."model")
|
||||
local m4, v3, bb3, bs3 = wirefame.m4, wirefame.v3, wirefame.bb3, wirefame.bs3
|
||||
local unpack = table.unpack or unpack
|
||||
|
||||
--- Group methods.
|
||||
local group_mt = {
|
||||
--- Add a model to the node.
|
||||
add = function(self, ...)
|
||||
assert(not self.filtered, "can't add to a filtered group")
|
||||
for _, m in ipairs({...}) do
|
||||
self.n = self.n + 1
|
||||
table.insert(self, m)
|
||||
if m.parent == nil then
|
||||
m.parent = self
|
||||
if self.scene then
|
||||
m:onAddToScene(self.scene)
|
||||
end
|
||||
m:setTransform(0, self:updateFinalTransform())
|
||||
end
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
--- Remove a model from the node.
|
||||
remove = function(self, ...)
|
||||
assert(not self.filtered, "can't remove from a filtered group")
|
||||
for _, m in ipairs({...}) do
|
||||
for i=1, self.n do
|
||||
if self[i] == m then
|
||||
self.n = self.n - 1
|
||||
table.remove(self, i)
|
||||
m.parent = nil
|
||||
if m.scene then
|
||||
m:onRemoveFromScene(m.scene)
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
end
|
||||
error("can't find model to remove")
|
||||
end,
|
||||
|
||||
--- All these methods and properties of model are present, but operate on the whole group.
|
||||
|
||||
-- Overwrite some model methods:
|
||||
onAddToScene = function(self, scene)
|
||||
self.scene = scene
|
||||
for i=1, self.n do
|
||||
self[i]:onAddToScene(scene)
|
||||
end
|
||||
end,
|
||||
onRemoveFromScene = function(self, scene)
|
||||
self.scene = nil
|
||||
for i=1, self.n do
|
||||
self[i]:onRemoveFromScene(scene)
|
||||
end
|
||||
end,
|
||||
get = function(self, ...)
|
||||
local l = {}
|
||||
for i=1, self.n do
|
||||
local il = self[i]:get(...)
|
||||
if #il > 0 then
|
||||
table.insert(l, il)
|
||||
end
|
||||
end
|
||||
l.n = #l
|
||||
l.filtered = true
|
||||
return setmetatable(l, {__index = self, __tostring = self.__tostring})
|
||||
end,
|
||||
boundingBox = function(self)
|
||||
if #self > 0 then
|
||||
self:updateFinalTransform()
|
||||
local r = self[1]:boundingBox()
|
||||
for i=2, self.n do
|
||||
r:include(self[i]:boundingBox())
|
||||
end
|
||||
return r
|
||||
else
|
||||
return bb3(v3(0,0,0), v3(0,0,0))
|
||||
end
|
||||
end,
|
||||
boundingSphere = function(self)
|
||||
if #self > 0 then
|
||||
self:updateFinalTransform()
|
||||
local r = self[1]:boundingSphere()
|
||||
for i=2, self.n do
|
||||
r:include(self[i]:boundingSphere())
|
||||
end
|
||||
return r
|
||||
else
|
||||
return bs3(v3(0,0,0), 0)
|
||||
end
|
||||
end,
|
||||
initShader = function(self, shader)
|
||||
for i=1, self.n do
|
||||
self[i]:initShader(shader)
|
||||
end
|
||||
end,
|
||||
sendFinalTransform = function(self, tr)
|
||||
if self.filtered then self = getmetatable(self).__index end
|
||||
for i=1, self.n do
|
||||
self[i]:setTransform(0, tr)
|
||||
end
|
||||
end,
|
||||
setColor = function(self, r, g, b, a) -- apply color to all children
|
||||
self.color[1], self.color[2], self.color[3], self.color[4] = r, g, b, a or 1
|
||||
for i=1, self.n do
|
||||
self[i]:setColor(r, g, b, a)
|
||||
end
|
||||
return self
|
||||
end,
|
||||
draw = function(self, shader, transp)
|
||||
-- Transparency sort
|
||||
if transp then
|
||||
if self.color[4] >= 1 then return end
|
||||
else
|
||||
if self.color[4] < 1 then return end
|
||||
end
|
||||
|
||||
-- Transformation matrix
|
||||
self:updateFinalTransform()
|
||||
|
||||
-- Draw
|
||||
for i=1, self.n do
|
||||
self[i]:draw(shader, transp)
|
||||
end
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
local mt = getmetatable(self)
|
||||
setmetatable(self, nil)
|
||||
local str = "group: "..(tostring(self):gsub("^table: ", ""))
|
||||
setmetatable(self, mt)
|
||||
return str
|
||||
end
|
||||
}
|
||||
for k, v in pairs(model_mt) do
|
||||
if group_mt[k] == nil then
|
||||
group_mt[k] = v
|
||||
end
|
||||
end
|
||||
group_mt.__index = group_mt
|
||||
|
||||
--- Create a group of models.
|
||||
function wirefame.group(t)
|
||||
local group = {
|
||||
-- Size
|
||||
n = 0,
|
||||
|
||||
-- Tags
|
||||
tags = {},
|
||||
|
||||
-- Group transformation matrices
|
||||
transformStack = { n = 1, changed = true, names = {}, [0] = m4.identity(), m4.identity() },
|
||||
finalTransform = nil,
|
||||
|
||||
-- Group color
|
||||
color = { wirefame.defaultColor[1], wirefame.defaultColor[2], wirefame.defaultColor[3], wirefame.defaultColor[4] },
|
||||
|
||||
-- Hierarchy
|
||||
parent = nil,
|
||||
scene = nil
|
||||
}
|
||||
|
||||
-- Create & return object
|
||||
return setmetatable(group, group_mt):add(unpack(t))
|
||||
end
|
||||
|
||||
return group_mt
|
||||
117
wirefame/light.lua
Normal file
117
wirefame/light.lua
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
local wirefame = require((...):match("^(.*)%.wirefame%.light$"))
|
||||
local model_mt = require((...):match("^(.*)light$").."model")
|
||||
local m4, v3 = wirefame.m4, wirefame.v3
|
||||
|
||||
--- Light methods.
|
||||
local lightType = {
|
||||
none = 0,
|
||||
ambient = 1,
|
||||
directional = 2,
|
||||
point = 3
|
||||
}
|
||||
local light_mt = {
|
||||
-- Rewrite model methods
|
||||
initShader = function(self, shader)
|
||||
shader:send("lights["..(self.index-1).."].type", self.type)
|
||||
shader:send("lights["..(self.index-1).."].position", self.position)
|
||||
shader:send("lights["..(self.index-1).."].color", self.color)
|
||||
end,
|
||||
onAddToScene = function(self, scene)
|
||||
self.scene = scene
|
||||
-- create array if needed
|
||||
if not scene.shader.define.LIGHT_COUNT then
|
||||
scene.shader.define.LIGHT_COUNT = 0
|
||||
scene.shader.lights = {}
|
||||
end
|
||||
-- find free spot
|
||||
self.index = nil
|
||||
for i=1, scene.shader.define.LIGHT_COUNT, 1 do
|
||||
if not scene.shader.lights[i] then
|
||||
self.index = i
|
||||
self:initShader() -- update already existing array in shader
|
||||
break
|
||||
end
|
||||
end
|
||||
if not self.index then -- create new spot
|
||||
scene.shader.define.LIGHT_COUNT = scene.shader.define.LIGHT_COUNT + 1
|
||||
self.index = scene.shader.define.LIGHT_COUNT
|
||||
scene.shader.changed = true
|
||||
end
|
||||
-- register oneself
|
||||
scene.shader.lights[self.index] = true
|
||||
end,
|
||||
onRemoveFromScene = function(self, scene)
|
||||
self.scene = nil
|
||||
-- unregister oneself
|
||||
scene.shader.lights[self.index] = nil
|
||||
-- recalculate minimal array size
|
||||
for i=scene.shader.define.LIGHT_COUNT, 1, -1 do
|
||||
if scene.shader.lights[i] then
|
||||
break
|
||||
else
|
||||
scene.shader.define.LIGHT_COUNT = scene.shader.define.LIGHT_COUNT - 1 -- will update on next recompile, nothing is urgent
|
||||
end
|
||||
end
|
||||
-- update oneself in shader
|
||||
scene.shader.shader:send("lights["..(self.index-1).."].type", lightType.none)
|
||||
end,
|
||||
sendFinalTransform = function(self, tr)
|
||||
self.position = v3(0,0,0):transform(tr)
|
||||
end,
|
||||
draw = function(self, shader, transp)
|
||||
-- Draw
|
||||
if shader:hasUniform("lights["..(self.index-1).."].position") then
|
||||
self:updateFinalTransform()
|
||||
shader:send("lights["..(self.index-1).."].position", self.position)
|
||||
shader:send("lights["..(self.index-1).."].color", self.color)
|
||||
end
|
||||
end,
|
||||
__tostring = function(self)
|
||||
local mt = getmetatable(self)
|
||||
setmetatable(self, nil)
|
||||
local str = "light: "..(tostring(self):gsub("^table: ", ""))
|
||||
setmetatable(self, mt)
|
||||
return str
|
||||
end
|
||||
}
|
||||
for k, v in pairs(model_mt) do
|
||||
if light_mt[k] == nil then
|
||||
light_mt[k] = v
|
||||
end
|
||||
end
|
||||
light_mt.__index = light_mt
|
||||
|
||||
--- Create a light object
|
||||
function wirefame.light(type)
|
||||
local light = {
|
||||
-- Light
|
||||
type = lightType[type],
|
||||
position = v3(0, 0, 0),
|
||||
|
||||
-- Color
|
||||
color = { wirefame.defaultColor[1], wirefame.defaultColor[2], wirefame.defaultColor[3], wirefame.defaultColor[4] },
|
||||
|
||||
-- Size
|
||||
n = 1,
|
||||
true,
|
||||
|
||||
-- Tags
|
||||
tags = {},
|
||||
|
||||
-- Group transformation matrices
|
||||
transformStack = { n = 1, changed = true, names = {}, [0] = m4.identity(), m4.identity() },
|
||||
finalTransform = nil,
|
||||
|
||||
-- Empty model object
|
||||
object = {},
|
||||
|
||||
-- Hierarchy
|
||||
parent = nil,
|
||||
scene = nil
|
||||
}
|
||||
|
||||
-- Create & return object
|
||||
return setmetatable(light, light_mt)
|
||||
end
|
||||
|
||||
return light_mt
|
||||
375
wirefame/model.lua
Normal file
375
wirefame/model.lua
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
local wirefame = require((...):match("^(.*)%.wirefame%.model$"))
|
||||
local m4, v3, bb3, bs3 = wirefame.m4, wirefame.v3, wirefame.bb3, wirefame.bs3
|
||||
local unpack = table.unpack or unpack
|
||||
local lg = love.graphics
|
||||
|
||||
-- Model methods.
|
||||
local model_mt = {
|
||||
--- Called when the model is added to a scene.
|
||||
onAddToScene = function(self, scene)
|
||||
self.scene = scene
|
||||
end,
|
||||
|
||||
--- Called when the model is removed from a scene.
|
||||
onRemoveFromScene = function(self, scene)
|
||||
self.scene = nil
|
||||
end,
|
||||
|
||||
--- Create a new group containing this entity
|
||||
group = function(self)
|
||||
return wirefame.group{self}
|
||||
end,
|
||||
|
||||
-- Add tags on this model only.
|
||||
tag = function(self, ...)
|
||||
for _, t in ipairs({...}) do
|
||||
self.tags[t] = true
|
||||
end
|
||||
return self
|
||||
end,
|
||||
-- Check tags on this model only.
|
||||
is = function(self, ...)
|
||||
local r = true
|
||||
for _, t in ipairs({...}) do
|
||||
r = r and self.tags[t]
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- List tags on this model only.
|
||||
listTags = function(self)
|
||||
local r = {}
|
||||
for t, _ in pairs(self.tags) do
|
||||
table.insert(r, t)
|
||||
end
|
||||
return r
|
||||
end,
|
||||
|
||||
--- Get a new group of all models recursively contained in this object with these tags.
|
||||
get = function(self, ...)
|
||||
if self:is(...) then
|
||||
return self
|
||||
else
|
||||
return wirefame.group{}
|
||||
end
|
||||
end,
|
||||
|
||||
--- Apply a transformation matrix to the model vertices coordinates.
|
||||
-- If no i is given, will transform the first matrix.
|
||||
transform = function(self, i, tr)
|
||||
if not tr then
|
||||
i, tr = 1, i
|
||||
elseif type(i) == "string" then
|
||||
i = self.transformStack.names[i]
|
||||
end
|
||||
self.transformStack[i]:transform(tr)
|
||||
self.transformStack.changed = true
|
||||
return self
|
||||
end,
|
||||
--- Retrieve the ith transformation matrix.
|
||||
-- If no i is given, will retrieve the first matrix.
|
||||
getTransform = function(self, i)
|
||||
if not i then
|
||||
i = 1
|
||||
elseif type(i) == "string" then
|
||||
i = self.transformStack.names[i]
|
||||
end
|
||||
return self.transformStack[i]
|
||||
end,
|
||||
--- Set the ith transformation matrix.
|
||||
-- If no i is given, will set the first matrix.
|
||||
setTransform = function(self, i, tr)
|
||||
if not tr then
|
||||
i, tr = 1, i
|
||||
elseif type(i) == "string" then
|
||||
i = self.transformStack.names[i]
|
||||
end
|
||||
self.transformStack[i] = tr
|
||||
self.transformStack.changed = true
|
||||
return self
|
||||
end,
|
||||
--- Reset the ith transformation matrix.
|
||||
-- If no i is given, will reset the first matrix.
|
||||
resetTransform = function(self, i)
|
||||
if not i then
|
||||
i = 1
|
||||
end
|
||||
self.transformStack[i] = m4.identity()
|
||||
self.transformStack.changed = true
|
||||
return self
|
||||
end,
|
||||
|
||||
--- Set the whole transformation stack.
|
||||
setTransformStack = function(self, stack)
|
||||
self.transformStack = { names = self.transformStack.names, [0] = self.transformStack[0] }
|
||||
for i, tr in ipairs(stack) do
|
||||
self.transformStack[i] = tr
|
||||
end
|
||||
self.transformStack.n = #self.transformStack
|
||||
self.transformStack.changed = true
|
||||
return self
|
||||
end,
|
||||
--- Retrieve the whole transformation stack.
|
||||
getTransformStack = function(self)
|
||||
return self.transformStack
|
||||
end,
|
||||
--- Set the size of the transformation stack, initiatializing new transformations matrices if needed.
|
||||
setTransformStackSize = function(self, n)
|
||||
self.transformStack.n = n
|
||||
for i=1, self.transformStack.n, 1 do
|
||||
if self.transformStack[i] == nil then
|
||||
self.transformStack[i] = m4.identity()
|
||||
end
|
||||
end
|
||||
self.transformStack.changed = true
|
||||
return self
|
||||
end,
|
||||
--- Assign to each transform in the transformation stack a name and resize the transformation stack to match.
|
||||
setTransformNames = function(self, list)
|
||||
self:setTransformStackSize(#list)
|
||||
local names = {}
|
||||
for i, name in ipairs(list) do
|
||||
names[name] = i
|
||||
end
|
||||
self.transformStack.names = names
|
||||
return self
|
||||
end,
|
||||
--- Retrieve the transformation names.
|
||||
getTransformNames = function(self, list)
|
||||
local names = {}
|
||||
for name, i in pairs(self.transformStack.names) do
|
||||
names[i] = name
|
||||
end
|
||||
return names
|
||||
end,
|
||||
--- Assign a name to the transform i.
|
||||
setTransformName = function(self, i, name)
|
||||
self.transformStack.names[name] = i
|
||||
return self
|
||||
end,
|
||||
|
||||
--- Calculate the final transformation matrix if needed, and send it to eventual children (using :sendFinalTransform).
|
||||
-- Returns the transform.
|
||||
updateFinalTransform = function(self)
|
||||
if self.transformStack.changed then
|
||||
self.finalTransform = m4.identity()
|
||||
for i=1, self.transformStack.n, 1 do
|
||||
self.finalTransform:transform(self.transformStack[i])
|
||||
end
|
||||
self.finalTransform:transform(self.transformStack[0])
|
||||
self.transformStack.changed = false
|
||||
self:sendFinalTransform(self.finalTransform)
|
||||
end
|
||||
return self.finalTransform
|
||||
end,
|
||||
|
||||
--- Send the final transformation matrix to the eventual children objects.
|
||||
sendFinalTransform = function(self, tr)
|
||||
if self.object.setTransform then
|
||||
self.object:setTransform(tr)
|
||||
end
|
||||
end,
|
||||
|
||||
--- Common tranforms
|
||||
translate = function(self, i, v)
|
||||
if v then
|
||||
return self:transform(i, m4.translate(v))
|
||||
else
|
||||
return self:transform(m4.translate(i))
|
||||
end
|
||||
end,
|
||||
scale = function(self, i, v)
|
||||
if v then
|
||||
return self:transform(i, m4.scale(v))
|
||||
else
|
||||
return self:transform(m4.scale(i))
|
||||
end
|
||||
end,
|
||||
rotate = function(self, i, a, v)
|
||||
if v then
|
||||
return self:transform(i, m4.rotate(a, v))
|
||||
else
|
||||
return self:transform(m4.rotate(i, a))
|
||||
end
|
||||
end,
|
||||
shear = function(self, i, vxy, vyz)
|
||||
if vyz then
|
||||
return self:transform(i, m4.shear(vxy, vyz))
|
||||
else
|
||||
return self:transform(m4.shear(i, vxy))
|
||||
end
|
||||
end,
|
||||
|
||||
--- Returns the minimum bounding box.
|
||||
boundingBox = function(self)
|
||||
if self.object.boundingBox then
|
||||
return self.object:boundingBox(self:updateFinalTransform())
|
||||
else
|
||||
return bb3(v3(0,0,0), v3(0,0,0))
|
||||
end
|
||||
end,
|
||||
--- Returns the minimum bounding sphere.
|
||||
boundingSphere = function(self)
|
||||
if self.object.boundingSphere then
|
||||
return self.object:boundingSphere(self:updateFinalTransform())
|
||||
else
|
||||
return bs3(v3(0,0,0), 0)
|
||||
end
|
||||
end,
|
||||
|
||||
--- Sets the color used to draw the model.
|
||||
setColor = function(self, r, g, b, a)
|
||||
self.color[1], self.color[2], self.color[3], self.color[4] = r, g, b, a or 1
|
||||
return self
|
||||
end,
|
||||
getColor = function(self)
|
||||
return self.color[1], self.color[2], self.color[3], self.color[4]
|
||||
end,
|
||||
|
||||
--- Initialize all relevant uniforms in the shader (called after shader rebuild)
|
||||
initShader = function(self, shader)
|
||||
end,
|
||||
|
||||
-- Draw the model. Shader may be either render or pick.
|
||||
draw = function(self, shader, transp)
|
||||
if self.object.draw then
|
||||
-- Transparency sort
|
||||
if transp then
|
||||
if self.color[4] >= 1 then return end
|
||||
else
|
||||
if self.color[4] < 1 then return end
|
||||
end
|
||||
|
||||
-- Transformation matrix (will automatically update object that need to know the matrix)
|
||||
shader:send("model_transform", self:updateFinalTransform())
|
||||
|
||||
-- Draw
|
||||
lg.setColor(self.color)
|
||||
self.object:draw(shader)
|
||||
end
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
local mt = getmetatable(self)
|
||||
setmetatable(self, nil)
|
||||
local str = "model: "..(tostring(self):gsub("^table: ", ""))
|
||||
setmetatable(self, mt)
|
||||
return str
|
||||
end
|
||||
}
|
||||
model_mt.__index = model_mt
|
||||
|
||||
--- Loads an .obj/.iqe file, or import a LÖVE drawable, or import a drawing function, or create a empty model.
|
||||
function wirefame.model(object)
|
||||
local model = {
|
||||
-- Size
|
||||
n = 1,
|
||||
true,
|
||||
|
||||
-- Tags
|
||||
tags = {},
|
||||
|
||||
-- Model transformation matrices. Matrix 0 is the transform inherited from parent chain.
|
||||
transformStack = { n = 1, changed = true, names = {}, [0] = m4.identity(), m4.identity() },
|
||||
finalTransform = nil,
|
||||
|
||||
-- Model color
|
||||
color = { wirefame.defaultColor[1], wirefame.defaultColor[2], wirefame.defaultColor[3], wirefame.defaultColor[4] },
|
||||
|
||||
-- Model object
|
||||
-- object:draw(shader)
|
||||
-- object:setTransform(tr)
|
||||
-- object:boundingBox(tr)
|
||||
-- object:boundingSphere(tr)
|
||||
object = nil,
|
||||
|
||||
-- Hierarchy
|
||||
parent = nil,
|
||||
scene = nil
|
||||
}
|
||||
|
||||
-- Empty model
|
||||
if object == nil then
|
||||
model.object = {}
|
||||
-- Load a model file
|
||||
elseif type(object) == "string" then
|
||||
local ext = object:match("%.(.-)$") or ""
|
||||
if wirefame.loader[ext] then
|
||||
model.object = wirefame.loader[ext](object)
|
||||
else
|
||||
error(("unknown model type %s"):format(ext))
|
||||
end
|
||||
-- Function
|
||||
elseif type(object) == "function" then
|
||||
model.object = { draw = object }
|
||||
-- Convertible to LÖVE meshes.
|
||||
elseif type(object) == "userdata" then
|
||||
if object:type() == "Image" or object:type() == "Canvas" then
|
||||
local t = lg.newMesh({
|
||||
{ "VertexPosition", "float", 3 },
|
||||
{ "VertexTexCoord", "float", 2 },
|
||||
{ "VertexColor", "byte", 4 },
|
||||
{ "VertexNormal", "float", 3 }
|
||||
},
|
||||
{
|
||||
{
|
||||
0, 0, 0,
|
||||
0, 1,
|
||||
1, 1, 1, 1,
|
||||
0, 0, 1
|
||||
},
|
||||
{
|
||||
object:getWidth(), 0, 0,
|
||||
1, 1,
|
||||
1, 1, 1, 1,
|
||||
0, 0, 1
|
||||
},
|
||||
{
|
||||
object:getWidth(), object:getHeight(), 0,
|
||||
1, 0,
|
||||
1, 1, 1, 1,
|
||||
0, 0, 1
|
||||
},
|
||||
{
|
||||
0, object:getHeight(), 0,
|
||||
0, 0,
|
||||
1, 1, 1, 1,
|
||||
0, 0, 1
|
||||
}
|
||||
}, "fan")
|
||||
t:setVertexMap(1, 2, 3, 4)
|
||||
t:setTexture(object)
|
||||
model.object = {
|
||||
draw = function()
|
||||
lg.draw(t)
|
||||
end,
|
||||
boundingBox = function(self, tr)
|
||||
return bb3.fromMesh(t, tr)
|
||||
end,
|
||||
boundingSphere = function(self, tr)
|
||||
return bs3.fromMesh(t, tr)
|
||||
end
|
||||
}
|
||||
elseif object:type() == "Mesh" then
|
||||
model.object = {
|
||||
draw = function()
|
||||
lg.draw(object)
|
||||
end,
|
||||
boundingBox = function(self, tr)
|
||||
return bb3.fromMesh(object, tr)
|
||||
end,
|
||||
boundingSphere = function(self, tr)
|
||||
return bs3.fromMesh(t, tr)
|
||||
end
|
||||
}
|
||||
else
|
||||
error("unknown userdata")
|
||||
end
|
||||
else
|
||||
model.object = object
|
||||
end
|
||||
|
||||
-- Create & return object
|
||||
return setmetatable(model, model_mt)
|
||||
end
|
||||
|
||||
return model_mt
|
||||
189
wirefame/scene.lua
Normal file
189
wirefame/scene.lua
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
local wirefame = require((...):match("^(.*)%.wirefame%.scene$"))
|
||||
local group_mt = require((...):match("^(.*)scene$").."group")
|
||||
local m4, v3 = wirefame.m4, wirefame.v3
|
||||
local lg = love.graphics
|
||||
|
||||
-- Global shaders
|
||||
local pickShader = lg.newShader(wirefame.shader.pick)
|
||||
|
||||
--- Scene methods
|
||||
local scene_mt = {
|
||||
--- Set the projection matrix.
|
||||
setPerspective = function(self, fovy, ratio, near, far)
|
||||
return self:setTransform(3, m4.perspective(fovy, ratio, near, far))
|
||||
end,
|
||||
|
||||
--- Sets the view and projection matrix.
|
||||
lookAt = function(self, eye, center, up)
|
||||
self.camera = v3(eye)
|
||||
return self:setTransform(2, m4.lookAt(eye, center, up))
|
||||
end,
|
||||
|
||||
--- Rebuild the shader
|
||||
rebuildShader = function(self)
|
||||
if self.shader.changed then
|
||||
if self.shader.shader then self.shader.shader:release() end
|
||||
local s = ""
|
||||
for var, val in pairs(self.shader.define) do
|
||||
s = s .. ("# define %s %s"):format(var, val) .. "\n"
|
||||
end
|
||||
s = s .. wirefame.shader.render
|
||||
self.shader.shader = lg.newShader(s)
|
||||
self.shader.shader:send("scene_transform", m4.identity())
|
||||
self.shader.shader:send("model_transform", m4.identity())
|
||||
for _, model in ipairs(self) do model:initShader(self.shader.shader) end
|
||||
self.shader.changed = false
|
||||
end
|
||||
return self.shader.shader
|
||||
end,
|
||||
|
||||
--- Draw the scene.
|
||||
draw = function(self)
|
||||
-- Init shader
|
||||
local shader = self:rebuildShader()
|
||||
|
||||
-- Calculate transformation matrix
|
||||
shader:send("scene_transform", self:updateFinalTransform())
|
||||
|
||||
-- Draw models
|
||||
lg.setShader(shader)
|
||||
for _, model in ipairs(self) do
|
||||
model:draw(shader, false) -- draw opaque models
|
||||
end
|
||||
for _, model in ipairs(self) do
|
||||
model:draw(shader, true) -- draw transparent models
|
||||
end
|
||||
lg.setShader()
|
||||
end,
|
||||
|
||||
--- Returns the object visible at x, y on the screen.
|
||||
pick = function(self, x, y)
|
||||
local canvas = lg.newCanvas()
|
||||
|
||||
-- Calculate transformation matrix
|
||||
pickShader:send("scene_transform", m4.scale{1, -1, 1} * self:updateFinalTransform())
|
||||
|
||||
-- Draw models
|
||||
lg.setCanvas{canvas, depth = true}
|
||||
lg.setShader(pickShader)
|
||||
local r, g, b = 0, 0, 0
|
||||
for _, model in ipairs(self) do
|
||||
if r < 255 then
|
||||
r = r+1
|
||||
elseif g < 255 then
|
||||
g = g+1
|
||||
elseif b < 255 then
|
||||
b = b+1
|
||||
else
|
||||
error("too many object to pick from")
|
||||
end
|
||||
pickShader:send("pick_color", {r/255, g/255, b/255})
|
||||
model:draw(pickShader, false)
|
||||
end
|
||||
r, g, b = 0, 0, 0
|
||||
for _, model in ipairs(self) do
|
||||
if r < 255 then
|
||||
r = r+1
|
||||
elseif g < 255 then
|
||||
g = g+1
|
||||
elseif b < 255 then
|
||||
b = b+1
|
||||
end
|
||||
pickShader:send("pick_color", {r/255, g/255, b/255})
|
||||
model:draw(pickShader, true)
|
||||
end
|
||||
lg.setShader()
|
||||
love.graphics.setCanvas()
|
||||
|
||||
-- Pick object
|
||||
r, g, b = canvas:newImageData():getPixel(x, y)
|
||||
local i = math.floor(r*255) + math.floor(g*255)*255 + math.floor(b*255)*255*255
|
||||
return self[i]
|
||||
end,
|
||||
|
||||
-- Redefine some group methods (main change is not passing scene transform to children)
|
||||
add = function(self, ...)
|
||||
for _, m in ipairs({...}) do
|
||||
self.n = self.n + 1
|
||||
table.insert(self, m)
|
||||
if m.parent == nil then
|
||||
m.parent = self
|
||||
if self.scene then
|
||||
m:onAddToScene(self.scene)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end,
|
||||
sendFinalTransform = function(self, tr)
|
||||
-- scene final transform is applied in shader
|
||||
end,
|
||||
__tostring = function(self)
|
||||
local mt = getmetatable(self)
|
||||
setmetatable(self, nil)
|
||||
local str = "scene: "..(tostring(self):gsub("^table: ", ""))
|
||||
setmetatable(self, mt)
|
||||
return str
|
||||
end
|
||||
|
||||
-- And every group method
|
||||
}
|
||||
for k, v in pairs(group_mt) do
|
||||
if scene_mt[k] == nil then
|
||||
scene_mt[k] = v
|
||||
end
|
||||
end
|
||||
scene_mt.__index = scene_mt
|
||||
|
||||
--- Create a scene.
|
||||
wirefame.scene = function()
|
||||
local scene = {
|
||||
-- Other scene variables
|
||||
camera = v3(0, 0, 0), -- camera position
|
||||
shader = {
|
||||
changed = true, -- rebuild shader
|
||||
define = {}, -- map of variables to define in the shader
|
||||
shader = nil, -- the shader
|
||||
lights = {} -- list of used lights slots, see light_mt
|
||||
},
|
||||
|
||||
-- Size
|
||||
n = 0,
|
||||
|
||||
-- Tags
|
||||
tags = {},
|
||||
|
||||
-- Scene transformation matrices
|
||||
transformStack = {
|
||||
n = 4,
|
||||
changed = true,
|
||||
names = {},
|
||||
[0] = m4.identity(),
|
||||
m4.identity(), -- Custom: user-defined transformation to the models world coordinates in the scene (excluding camera)
|
||||
m4.identity(), -- View: world coordinates -> camera coordinates
|
||||
m4.identity(), -- Projection: camera coordinates -> perspective camera coordinates
|
||||
m4.identity(), -- Viewport: perspective camera coordinates -> screen coordinates
|
||||
},
|
||||
finalTransform = nil,
|
||||
|
||||
-- Group color
|
||||
color = { wirefame.defaultColor[1], wirefame.defaultColor[2], wirefame.defaultColor[3], wirefame.defaultColor[4] },
|
||||
|
||||
-- Hierarchy
|
||||
parent = nil,
|
||||
scene = nil
|
||||
}
|
||||
scene.scene = scene
|
||||
|
||||
-- enable depth buffer (lequal instead of less in order to allow regular 2D drawing (constant depth value))
|
||||
lg.setDepthMode("lequal", true)
|
||||
|
||||
-- enable back face culling
|
||||
lg.setFrontFaceWinding("ccw")
|
||||
lg.setMeshCullMode("back")
|
||||
|
||||
-- Create & return object
|
||||
return setmetatable(scene, scene_mt)
|
||||
end
|
||||
|
||||
return scene_mt
|
||||
Loading…
Add table
Add a link
Reference in a new issue