mirror of
https://github.com/appgurueu/modlib.git
synced 2024-08-19 21:04:13 +02:00
Clean up B3D reader, add B3D writer
This commit is contained in:
parent
c0b2957c67
commit
c088057d56
247
b3d.lua
247
b3d.lua
@ -1,15 +1,17 @@
|
|||||||
-- Localize globals
|
-- Localize globals
|
||||||
local assert, error, math, modlib, next, pairs, setmetatable, table = assert, error, math, modlib, next, pairs, setmetatable, table
|
local assert, error, math, modlib, next, ipairs, pairs, setmetatable, string_char, table
|
||||||
|
= assert, error, math, modlib, next, ipairs, pairs, setmetatable, string.char, table
|
||||||
|
|
||||||
local read_int, read_single = modlib.binary.read_int, modlib.binary.read_single
|
local read_int, read_single = modlib.binary.read_int, modlib.binary.read_single
|
||||||
|
|
||||||
|
local write_int, write_single = modlib.binary.write_int, modlib.binary.write_single
|
||||||
|
|
||||||
-- Set environment
|
-- Set environment
|
||||||
local _ENV = {}
|
local _ENV = {}
|
||||||
setfenv(1, _ENV)
|
setfenv(1, _ENV)
|
||||||
|
|
||||||
local metatable = {__index = _ENV}
|
local metatable = {__index = _ENV}
|
||||||
|
|
||||||
--! experimental
|
|
||||||
--+ Reads a single BB3D chunk from a stream
|
--+ Reads a single BB3D chunk from a stream
|
||||||
--+ Doing `assert(stream:read(1) == nil)` afterwards is recommended
|
--+ Doing `assert(stream:read(1) == nil)` afterwards is recommended
|
||||||
--+ See `b3d_specification.txt` as well as https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp
|
--+ See `b3d_specification.txt` as well as https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp
|
||||||
@ -84,7 +86,9 @@ function read(stream)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function content()
|
local function content()
|
||||||
assert(left >= 0, stream:seek())
|
if left < 0 then
|
||||||
|
error(("unexpected EOF at position %d"):format(stream:seek()))
|
||||||
|
end
|
||||||
return left ~= 0
|
return left ~= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -106,8 +110,8 @@ function read(stream)
|
|||||||
end,
|
end,
|
||||||
BRUS = function()
|
BRUS = function()
|
||||||
local brushes = {}
|
local brushes = {}
|
||||||
brushes.n_texs = int()
|
local n_texs = int()
|
||||||
assert(brushes.n_texs <= 8)
|
assert(n_texs <= 8)
|
||||||
while content() do
|
while content() do
|
||||||
local brush = {}
|
local brush = {}
|
||||||
brush.name = string()
|
brush.name = string()
|
||||||
@ -116,7 +120,7 @@ function read(stream)
|
|||||||
brush.blend = int()
|
brush.blend = int()
|
||||||
brush.fx = int()
|
brush.fx = int()
|
||||||
brush.texture_id = {}
|
brush.texture_id = {}
|
||||||
for index = 1, brushes.n_texs do
|
for index = 1, n_texs do
|
||||||
brush.texture_id[index] = optional_id()
|
brush.texture_id[index] = optional_id()
|
||||||
end
|
end
|
||||||
table.insert(brushes, brush)
|
table.insert(brushes, brush)
|
||||||
@ -214,8 +218,6 @@ function read(stream)
|
|||||||
end
|
end
|
||||||
table.insert(bone, frame)
|
table.insert(bone, frame)
|
||||||
end
|
end
|
||||||
-- Ensure frames are sorted ascending
|
|
||||||
table.sort(bone, function(a, b) return a.frame < b.frame end)
|
|
||||||
return bone
|
return bone
|
||||||
end,
|
end,
|
||||||
ANIM = function()
|
ANIM = function()
|
||||||
@ -235,7 +237,7 @@ function read(stream)
|
|||||||
node.children = {}
|
node.children = {}
|
||||||
local node_type
|
local node_type
|
||||||
-- See https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp#L263
|
-- See https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp#L263
|
||||||
-- Order is not validated; double occurences of mutually exclusive node def are
|
-- Order is not validated; double occurrences of mutually exclusive node def are
|
||||||
while content() do
|
while content() do
|
||||||
local elem, type = chunk()
|
local elem, type = chunk()
|
||||||
if type == "MESH" then
|
if type == "MESH" then
|
||||||
@ -247,7 +249,6 @@ function read(stream)
|
|||||||
node_type = "bone"
|
node_type = "bone"
|
||||||
node.bone = elem
|
node.bone = elem
|
||||||
elseif type == "KEYS" then
|
elseif type == "KEYS" then
|
||||||
assert((node.keys[#node.keys] or {}).frame ~= (elem[1] or {}).frame, "duplicate frame")
|
|
||||||
modlib.table.append(node.keys, elem)
|
modlib.table.append(node.keys, elem)
|
||||||
elseif type == "NODE" then
|
elseif type == "NODE" then
|
||||||
table.insert(node.children, elem)
|
table.insert(node.children, elem)
|
||||||
@ -258,7 +259,11 @@ function read(stream)
|
|||||||
node_type = "pivot"
|
node_type = "pivot"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- TODO somehow merge keys
|
-- Ensure frames are sorted ascendingly
|
||||||
|
table.sort(node.keys, function(a, b)
|
||||||
|
assert(a.frame ~= b.frame, "duplicate frame")
|
||||||
|
return a.frame < b.frame
|
||||||
|
end)
|
||||||
return node
|
return node
|
||||||
end,
|
end,
|
||||||
BB3D = function()
|
BB3D = function()
|
||||||
@ -267,7 +272,6 @@ function read(stream)
|
|||||||
version = {
|
version = {
|
||||||
major = math.floor(version / 100),
|
major = math.floor(version / 100),
|
||||||
minor = version % 100,
|
minor = version % 100,
|
||||||
raw = version
|
|
||||||
},
|
},
|
||||||
textures = {},
|
textures = {},
|
||||||
brushes = {}
|
brushes = {}
|
||||||
@ -309,7 +313,224 @@ function read(stream)
|
|||||||
return setmetatable(self, metatable)
|
return setmetatable(self, metatable)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO function write(self, stream)
|
-- Writer
|
||||||
|
|
||||||
|
local function write_rope(self)
|
||||||
|
local rope = {}
|
||||||
|
|
||||||
|
local written_len = 0
|
||||||
|
local function write(str)
|
||||||
|
written_len = written_len + #str
|
||||||
|
table.insert(rope, str)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function byte(val)
|
||||||
|
write(string_char(val))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function int(val)
|
||||||
|
write_int(byte, val, 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function id(val)
|
||||||
|
int(val - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function optional_id(val)
|
||||||
|
int(val and (val - 1) or -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function string(val)
|
||||||
|
write(val)
|
||||||
|
write"\0"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function float(val)
|
||||||
|
write_single(byte, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function float_array(arr, len)
|
||||||
|
assert(#arr == len)
|
||||||
|
for i = 1, len do
|
||||||
|
float(arr[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function color(val)
|
||||||
|
float(val.r)
|
||||||
|
float(val.g)
|
||||||
|
float(val.b)
|
||||||
|
float(val.a)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function vector3(val)
|
||||||
|
float_array(val, 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function quaternion(quat)
|
||||||
|
float(quat[4])
|
||||||
|
float(quat[1])
|
||||||
|
float(quat[2])
|
||||||
|
float(quat[3])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function chunk(name, write_func)
|
||||||
|
write(name)
|
||||||
|
|
||||||
|
-- Insert placeholder for the 4-bit len
|
||||||
|
table.insert(rope, false)
|
||||||
|
written_len = written_len + 4
|
||||||
|
local len_idx = #rope -- save index of placeholder
|
||||||
|
|
||||||
|
local prev_written_len = written_len
|
||||||
|
write_func()
|
||||||
|
|
||||||
|
-- Write the length of this chunk
|
||||||
|
local chunk_len = written_len - prev_written_len
|
||||||
|
local len_binary = {}
|
||||||
|
write_int(function(byte)
|
||||||
|
table.insert(len_binary, string_char(byte))
|
||||||
|
end, chunk_len, 4)
|
||||||
|
rope[len_idx] = table.concat(len_binary)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function NODE(node)
|
||||||
|
chunk("NODE", function()
|
||||||
|
string(node.name)
|
||||||
|
vector3(node.position)
|
||||||
|
vector3(node.scale)
|
||||||
|
quaternion(node.rotation)
|
||||||
|
local mesh = node.mesh
|
||||||
|
if mesh then
|
||||||
|
chunk("MESH", function()
|
||||||
|
optional_id(mesh.brush_id)
|
||||||
|
local vertices = mesh.vertices
|
||||||
|
chunk("VRTS", function()
|
||||||
|
int(vertices.flags)
|
||||||
|
int(vertices.tex_coord_sets)
|
||||||
|
int(vertices.tex_coord_set_size)
|
||||||
|
for _, vertex in ipairs(vertices) do
|
||||||
|
if vertex.pos then vector3(vertex.pos) end
|
||||||
|
if vertex.normal then vector3(vertex.normal) end
|
||||||
|
if vertex.color then color(vertex.color) end
|
||||||
|
for tex_coord_set = 1, vertices.tex_coord_sets do
|
||||||
|
local tex_coords = vertex.tex_coords[tex_coord_set]
|
||||||
|
for tex_coord = 1, vertices.tex_coord_set_size do
|
||||||
|
float(tex_coords[tex_coord])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
for _, triangle_set in ipairs(mesh.triangle_sets) do
|
||||||
|
chunk("TRIS", function()
|
||||||
|
id(triangle_set.brush_id)
|
||||||
|
for _, tri in ipairs(triangle_set.vertex_ids) do
|
||||||
|
id(tri[1])
|
||||||
|
id(tri[2])
|
||||||
|
id(tri[3])
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if node.bone then
|
||||||
|
chunk("BONE", function()
|
||||||
|
for vertex_id, weight in pairs(node.bone) do
|
||||||
|
id(vertex_id)
|
||||||
|
float(weight)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if node.keys then
|
||||||
|
local keys_by_flags = {}
|
||||||
|
for _, key in ipairs(node.keys) do
|
||||||
|
local flags = 0
|
||||||
|
flags = flags
|
||||||
|
+ (key.position and 1 or 0)
|
||||||
|
+ (key.scale and 2 or 0)
|
||||||
|
+ (key.rotation and 4 or 0)
|
||||||
|
keys_by_flags[flags] = keys_by_flags[flags] or {}
|
||||||
|
table.insert(keys_by_flags[flags], key)
|
||||||
|
end
|
||||||
|
for flags, keys in pairs(keys_by_flags) do
|
||||||
|
chunk("KEYS", function()
|
||||||
|
int(flags)
|
||||||
|
for _, frame in ipairs(keys) do
|
||||||
|
int(frame.frame)
|
||||||
|
if frame.position then vector3(frame.position) end
|
||||||
|
if frame.scale then vector3(frame.scale) end
|
||||||
|
if frame.rotation then quaternion(frame.rotation) end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local anim = node.animation
|
||||||
|
if anim then
|
||||||
|
chunk("ANIM", function()
|
||||||
|
int(anim.flags)
|
||||||
|
int(anim.frames)
|
||||||
|
float(anim.fps)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
for _, child in ipairs(node.children) do
|
||||||
|
NODE(child)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
chunk("BB3D", function()
|
||||||
|
int(self.version.major * 100 + self.version.minor)
|
||||||
|
if self.textures[1] then
|
||||||
|
chunk("TEXS", function()
|
||||||
|
for _, tex in ipairs(self.textures) do
|
||||||
|
string(tex.file)
|
||||||
|
int(tex.flags)
|
||||||
|
int(tex.blend)
|
||||||
|
float_array(tex.pos, 2)
|
||||||
|
float_array(tex.scale, 2)
|
||||||
|
float(tex.rotation)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if self.brushes[1] then
|
||||||
|
local max_n_texs = 0
|
||||||
|
for _, brush in ipairs(self.brushes) do
|
||||||
|
for n in pairs(brush.texture_id) do
|
||||||
|
if n > max_n_texs then
|
||||||
|
max_n_texs = n
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
chunk("BRUS", function()
|
||||||
|
int(max_n_texs)
|
||||||
|
for _, brush in ipairs(self.brushes) do
|
||||||
|
string(brush.name)
|
||||||
|
color(brush.color)
|
||||||
|
float(brush.shininess)
|
||||||
|
int(brush.blend)
|
||||||
|
int(brush.fx)
|
||||||
|
for index = 1, max_n_texs do
|
||||||
|
optional_id(brush.texture_id[index])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if self.node then
|
||||||
|
NODE(self.node)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return rope
|
||||||
|
end
|
||||||
|
|
||||||
|
function write_string(self)
|
||||||
|
return table.concat(write_rope(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
function write(self, stream)
|
||||||
|
for _, str in ipairs(write_rope(self)) do
|
||||||
|
stream:write(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local binary_search_frame = modlib.table.binary_search_comparator(function(a, b)
|
local binary_search_frame = modlib.table.binary_search_comparator(function(a, b)
|
||||||
return modlib.table.default_comparator(a, b.frame)
|
return modlib.table.default_comparator(a, b.frame)
|
||||||
|
9
test.lua
9
test.lua
@ -480,9 +480,9 @@ local tests = {
|
|||||||
}
|
}
|
||||||
if tests.b3d then
|
if tests.b3d then
|
||||||
local stream = assert(io.open(mod.get_resource("player_api", "models", "character.b3d"), "r"))
|
local stream = assert(io.open(mod.get_resource("player_api", "models", "character.b3d"), "r"))
|
||||||
local b3d = b3d.read(stream)
|
local character = assert(b3d.read(stream))
|
||||||
stream:close()
|
stream:close()
|
||||||
--! dirty helper method to create truncate tables with 10+ number keys
|
--! dirty helper method to truncate tables with 10+ number keys
|
||||||
local function _b3d_truncate(table)
|
local function _b3d_truncate(table)
|
||||||
local count = 1
|
local count = 1
|
||||||
for key, value in pairs(table) do
|
for key, value in pairs(table) do
|
||||||
@ -503,7 +503,10 @@ if tests.b3d then
|
|||||||
end
|
end
|
||||||
return table
|
return table
|
||||||
end
|
end
|
||||||
file.write(mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(table.copy(b3d))))
|
local str = character:write_string()
|
||||||
|
local read = b3d.read(text.inputstream(str))
|
||||||
|
assert(modlib.table.equals_noncircular(character, read))
|
||||||
|
file.write(mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(table.copy(character))))
|
||||||
end
|
end
|
||||||
local vector, minetest, ml_mt = _G.vector, _G.minetest, minetest
|
local vector, minetest, ml_mt = _G.vector, _G.minetest, minetest
|
||||||
if tests.liquid_dir then
|
if tests.liquid_dir then
|
||||||
|
Loading…
Reference in New Issue
Block a user