1
0
Fork 0
mirror of https://github.com/Reuh/wirefame.git synced 2026-03-12 23:31:11 +00:00
This commit is contained in:
Étienne Fildadut 2021-02-18 17:22:21 +01:00
parent b4798c8c16
commit 77cfbaad52
24 changed files with 3033 additions and 1545 deletions

170
wirefame/group.lua Normal file
View 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
View 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
View 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
View 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