mirror of
https://github.com/appgurueu/modlib.git
synced 2024-12-22 21:32:27 +01: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
|
||||
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 write_int, write_single = modlib.binary.write_int, modlib.binary.write_single
|
||||
|
||||
-- Set environment
|
||||
local _ENV = {}
|
||||
setfenv(1, _ENV)
|
||||
|
||||
local metatable = {__index = _ENV}
|
||||
|
||||
--! experimental
|
||||
--+ Reads a single BB3D chunk from a stream
|
||||
--+ 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
|
||||
@ -84,7 +86,9 @@ function read(stream)
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
@ -106,8 +110,8 @@ function read(stream)
|
||||
end,
|
||||
BRUS = function()
|
||||
local brushes = {}
|
||||
brushes.n_texs = int()
|
||||
assert(brushes.n_texs <= 8)
|
||||
local n_texs = int()
|
||||
assert(n_texs <= 8)
|
||||
while content() do
|
||||
local brush = {}
|
||||
brush.name = string()
|
||||
@ -116,7 +120,7 @@ function read(stream)
|
||||
brush.blend = int()
|
||||
brush.fx = int()
|
||||
brush.texture_id = {}
|
||||
for index = 1, brushes.n_texs do
|
||||
for index = 1, n_texs do
|
||||
brush.texture_id[index] = optional_id()
|
||||
end
|
||||
table.insert(brushes, brush)
|
||||
@ -214,8 +218,6 @@ function read(stream)
|
||||
end
|
||||
table.insert(bone, frame)
|
||||
end
|
||||
-- Ensure frames are sorted ascending
|
||||
table.sort(bone, function(a, b) return a.frame < b.frame end)
|
||||
return bone
|
||||
end,
|
||||
ANIM = function()
|
||||
@ -235,7 +237,7 @@ function read(stream)
|
||||
node.children = {}
|
||||
local node_type
|
||||
-- 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
|
||||
local elem, type = chunk()
|
||||
if type == "MESH" then
|
||||
@ -247,7 +249,6 @@ function read(stream)
|
||||
node_type = "bone"
|
||||
node.bone = elem
|
||||
elseif type == "KEYS" then
|
||||
assert((node.keys[#node.keys] or {}).frame ~= (elem[1] or {}).frame, "duplicate frame")
|
||||
modlib.table.append(node.keys, elem)
|
||||
elseif type == "NODE" then
|
||||
table.insert(node.children, elem)
|
||||
@ -258,7 +259,11 @@ function read(stream)
|
||||
node_type = "pivot"
|
||||
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
|
||||
end,
|
||||
BB3D = function()
|
||||
@ -267,7 +272,6 @@ function read(stream)
|
||||
version = {
|
||||
major = math.floor(version / 100),
|
||||
minor = version % 100,
|
||||
raw = version
|
||||
},
|
||||
textures = {},
|
||||
brushes = {}
|
||||
@ -309,7 +313,224 @@ function read(stream)
|
||||
return setmetatable(self, metatable)
|
||||
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)
|
||||
return modlib.table.default_comparator(a, b.frame)
|
||||
|
9
test.lua
9
test.lua
@ -480,9 +480,9 @@ local tests = {
|
||||
}
|
||||
if tests.b3d then
|
||||
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()
|
||||
--! 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 count = 1
|
||||
for key, value in pairs(table) do
|
||||
@ -503,7 +503,10 @@ if tests.b3d then
|
||||
end
|
||||
return table
|
||||
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
|
||||
local vector, minetest, ml_mt = _G.vector, _G.minetest, minetest
|
||||
if tests.liquid_dir then
|
||||
|
Loading…
Reference in New Issue
Block a user