Compare commits
5 Commits
546de3d6ca
...
342357e9de
Author | SHA1 | Date |
---|---|---|
Starbeamrainbowlabs | 342357e9de | |
Starbeamrainbowlabs | 9dd92dbe70 | |
Starbeamrainbowlabs | f02b1d0b33 | |
Starbeamrainbowlabs | 621ca53d28 | |
Starbeamrainbowlabs | 985901de94 |
|
@ -113,6 +113,7 @@ WEASCHEM 1
|
|||
{"name":"Test schematic","description": "Some description","size":{"x":5,"y":3,"z":4},"offset":{"x":1,"y":0,"z":2},"type":"full","generator": "WorldEditAdditions v1.14"}
|
||||
{"0":"default:air","5":"default:stone","14":"default:dirt"}
|
||||
10x5,40x14,0,5,14,5,14,5x0
|
||||
10x0,40x1,0,1,0,2x1,5x0
|
||||
```
|
||||
|
||||
## Magic bytes
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
local modpath = minetest.get_modpath("worldeditadditions_core")
|
||||
|
||||
worldeditadditions_core = {
|
||||
version = "1.15-dev",
|
||||
modpath = modpath,
|
||||
registered_commands = {},
|
||||
-- Storage for per-player node limits before safe_region kicks in.
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
local weac = worldeditadditions_core
|
||||
|
||||
local weaschem = weac.parse.file.weaschem
|
||||
local voxeltools = dofile(weac.modpath.."utils/io/voxeltools.lua")
|
||||
|
||||
--- A region of the world that is to be or has been saved to/from disk.
|
||||
-- This class exists to make moving things to/from disk easier and less complicated.
|
||||
--
|
||||
|
@ -9,14 +14,14 @@ StagedVoxelRegion.__index = StagedVoxelRegion
|
|||
StagedVoxelRegion.__name = "StagedVoxelRegion" -- A hack to allow identification in wea.inspect
|
||||
|
||||
|
||||
local function make_instance(tbl) {
|
||||
local function make_instance(tbl)
|
||||
local result = tbl
|
||||
if result == nil then
|
||||
result = {}
|
||||
end
|
||||
setmetatable(result, StagedVoxelRegion)
|
||||
return result
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
@ -33,13 +38,15 @@ local function make_instance(tbl) {
|
|||
-- To save data, you probably want to call the save() method.
|
||||
-- @param pos1 Vector3 The position in WORLD SPACE of pos1 of the defined region to stage for saving.
|
||||
-- @param pos2 Vector3 The position in WORLD SPACE of pos2 of the defined region to stage for saving.
|
||||
-- @param offset Vector3 Apply this offset before placing in the world. e.g. if you have a schematic of a tree, and want it to place centred on the base thereof.
|
||||
-- @param voxelmanip VoxelManipulator The voxel manipulator to take data from and save to disk.
|
||||
-- @returns bool,StagedVoxelRegion A success boolean, followed by the new StagedVoxelRegion instance.
|
||||
function StagedVoxelRegion.NewFromVoxelManip(pos1, pos2, voxelmanip)
|
||||
|
||||
function StagedVoxelRegion.NewFromVoxelManip(pos1, pos2, offset, voxelmanip)
|
||||
local data, param2 = voxeltools.voxelmanip2raw(voxelmanip, pos1, pos2)
|
||||
return StagedVoxelRegion.NewFromRaw(pos1, pos2, offset, data, param2)
|
||||
end
|
||||
|
||||
--- Creates a new StagedVoxelRegion from the given VoxelManipulator data.
|
||||
-- Creates a new StagedVoxelRegion from the given VoxelManipulator data.
|
||||
-- To save data, you probably want to call the save() method.
|
||||
-- @param pos1 Vector3 The position in WORLD SPACE of pos1 of the defined region to stage for saving.
|
||||
-- @param pos2 Vector3 The position in WORLD SPACE of pos2 of the defined region to stage for saving.
|
||||
|
@ -47,23 +54,25 @@ end
|
|||
-- @param data number[] A table of numbers representing the node ids.
|
||||
-- @param param2 number[] A table of numbers representing the param2 data. Should exactly match the data number[] in size.
|
||||
-- @returns bool,StagedVoxelRegion A success boolean, followed by the new StagedVoxelRegion instance.
|
||||
function StagedVoxelRegion.NewFromTable(pos1, pos2, area, data, param2)
|
||||
-- function StagedVoxelRegion.NewFromTable(pos1, pos2, area, data, param2)
|
||||
|
||||
end
|
||||
-- end
|
||||
|
||||
--- Creates a new StagedVoxelRegion from raw data/param2 tables.
|
||||
-- @static
|
||||
-- @param pos1 Vector3 The position in WORLD SPACE of pos1 of the defined region to stage for saving.
|
||||
-- @param pos2 Vector3 The position in WORLD SPACE of pos2 of the defined region to stage for saving.
|
||||
-- @param data number[] A table of numbers representing the node ids. Must be ALREADY TRIMMED, NOT just taken straight from a VoxelManip!
|
||||
-- @param param2 number[] A table of numbers representing the param2 data. Should exactly match the data number[] in size. Must be ALREADY TRIMMED, NOT just taken straight from a VoxelManip!
|
||||
-- @param pos1 Vector3 The position in WORLD SPACE of pos1 of the defined region to stage for saving.
|
||||
-- @param pos2 Vector3 The position in WORLD SPACE of pos2 of the defined region to stage for saving.
|
||||
-- @param offset Vector3 Apply this offset before placing in the world. e.g. if you have a schematic of a tree, and want it to place centred on the base thereof.
|
||||
-- @param data number[] A table of numbers representing the node ids. Must be ALREADY TRIMMED, NOT just taken straight from a VoxelManip!
|
||||
-- @param param2 number[] A table of numbers representing the param2 data. Should exactly match the data number[] in size. Must be ALREADY TRIMMED, NOT just taken straight from a VoxelManip!
|
||||
-- @returns bool,StagedVoxelRegion A success boolean, followed by the new StagedVoxelRegion instance.
|
||||
function StagedVoxelRegion.NewFromRaw(pos1, pos2, data, param2)
|
||||
function StagedVoxelRegion.NewFromRaw(pos1, pos2, offset, data, param2)
|
||||
return make_instance({
|
||||
name = "untitled",
|
||||
description = "",
|
||||
pos1 = pos1:clone()
|
||||
pos2 = pos2:clone()
|
||||
pos1 = pos1:clone(),
|
||||
pos2 = pos2:clone(),
|
||||
offset = offset,
|
||||
tables = {
|
||||
data = data,
|
||||
param2 = param2
|
||||
|
@ -131,8 +140,59 @@ end
|
|||
-- @param filepath string The filepath to save the StagedVoxelRegion to.
|
||||
-- @param format="auto" string The format to save in. Default: automatic, determine from file extension. See worldeditadditions_core.io.FileFormats for more information.
|
||||
-- @returns bool Whether the operation was successful or not.
|
||||
function StagedVoxelRegion.save(filepath, format)
|
||||
function StagedVoxelRegion.save(svr, filepath, format)
|
||||
local handle = io.open(filepath, "w")
|
||||
if handle == nil then return false, "Failed to open handle to filepath '"..filepath.."'" end
|
||||
|
||||
local parts = {}
|
||||
|
||||
---
|
||||
-- Magic bytes
|
||||
---
|
||||
table.insert(parts, "WEASCHEM 1\n")
|
||||
|
||||
---
|
||||
-- Header
|
||||
---
|
||||
local header = {
|
||||
name = svr.name,
|
||||
size = (svr.pos2 - svr.pos1):abs(),
|
||||
offset = svr.offset,
|
||||
|
||||
type = "full", -- TODO: Add delta support later
|
||||
generator = "WorldEditAdditions/"..weac.version.." "..minetest.get_version().project.."/"..minetest.get_version().string,
|
||||
}
|
||||
if svr.description then header.description = svr.description end
|
||||
table.insert(parts, minetest.write_json(header, false).."\n")
|
||||
|
||||
---
|
||||
-- ID map
|
||||
---
|
||||
local id_map, wid2sid = voxeltools.make_id_maps(svr.tables.data)
|
||||
table.insert(parts, minetest.write_json(id_map, false).."\n")
|
||||
|
||||
---
|
||||
-- Data tables
|
||||
---
|
||||
local data, param2 = weac.table.map(svr.tables.data, function(val)
|
||||
return wid2sid[data]
|
||||
end), svr.tables.param2
|
||||
|
||||
table.insert(parts, table.concat(voxeltools.runlength_encode(data), ","))
|
||||
table.insert(parts, "\n")
|
||||
table.insert(parts, table.concat(voxeltools.runlength_encode(param2), ","))
|
||||
table.insert(parts, "\n")
|
||||
|
||||
|
||||
---
|
||||
-- Writing
|
||||
---
|
||||
|
||||
-- TODO: Implement compression here - maybe via minetest.compress(data, method, ...)
|
||||
local schematic = table.concat(parts, "")
|
||||
|
||||
handle:write(schematic)
|
||||
handle:close()
|
||||
end
|
||||
|
||||
--- Loads a file of the an array.
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
local weac = worldeditadditions_core
|
||||
local Vector3 = weac.Vector3
|
||||
|
||||
--- Converts data in a VoxelManip to a raw data array for serialisation.
|
||||
-- TODO: Figure out the specifics of how this fits into StagedVoxelRegion, and whether or not StagedVoxelRegion has too many overrides or not. Then implement the counterpart raw2voxelmanip & integrate into WEA + StagedVoxelRegion
|
||||
-- @param voxelmanip VoxelManipulator The voxelmanip instance to extract data from.
|
||||
-- @param pos1 Vector3 Position 1 that defines the region to extract data from.
|
||||
-- @param pos2 Vector3 Position 2 that defines the region to extract data from.
|
||||
local function voxelmanip2raw(voxelmanip, pos1, pos2)
|
||||
|
||||
local pos1_sort, pos2_sort = Vector3.sort(pos1, pos2)
|
||||
local size = pos2_sort - pos1_sort
|
||||
|
||||
local data = voxelmanip:get_data()
|
||||
local param2 = voxelmanip:get_param2_data()
|
||||
|
||||
local pos1_manip, pos2_manip = voxelmanip:get_emerged_area()
|
||||
local area = VoxelArea:new({
|
||||
MinEdge = pos1_manip,
|
||||
MaxEdge = pos2_manip
|
||||
})
|
||||
pos1_manip = Vector3.clone(pos1_manip)
|
||||
pos2_manip = Vector3.clone(pos2_manip)
|
||||
|
||||
local pos1_manip_sort, pos2_manip_sort = Vector3.sort(pos1_manip, pos2_manip)
|
||||
|
||||
local result_data = {}
|
||||
local result_param2 = {}
|
||||
for z = pos2_sort.z, pos1_sort.z, -1 do
|
||||
for x = pos2_sort.x, pos1_sort.x, -1 do
|
||||
for y = pos2_sort.y, pos1_sort.y, -1 do
|
||||
local here = Vector3.new(x, y, z)
|
||||
local here_target = here - pos1_manip_sort
|
||||
local here_target_i = here_target.z * size.y * size.x + here_target.y * size.x + here_target.x -- Minetest flat arrays start from 1 (I thought they started from 0?!?!?!), but WEA flat arrays start from 0. Hence, we don't +1 here, though at some point we'll hafta update eeeevvvveerrrryyything...... :-(
|
||||
|
||||
local here_source_i = area:index(x, y, z)
|
||||
result_data[here_target_i] = data[here_source_i]
|
||||
result_param2[here_target_i] = param2[here_source_i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result_data, result_param2
|
||||
end
|
||||
|
||||
--- Makes a node id map for saving to disk based on a given RAW data array of node ids.
|
||||
-- Also sequentially packs node ids to save space and potentially improve compression ratio, though this is unproven.
|
||||
-- @param data table The RAW data table to read ids from to build the map.
|
||||
-- @returns table,table A pair of maps to transform node ids from the world to the schematic file.
|
||||
--
|
||||
-- 1. The sID: number → node_name: string map to be saved in the schematic. sID stands for *schematic* id, which is NOT the same as the node id in the world.
|
||||
-- 2. The wID → sID node id map. wID = the node id in the current Minetest world, and sID = the *schematic* id as in #1. All world node ids MUST be pushed through this map before being saved to the schematic file.
|
||||
local function make_id_maps(data)
|
||||
local map = {}
|
||||
local id2id = {}
|
||||
local next_id = 0
|
||||
for _, wid in pairs(data) do
|
||||
if id2id[wid] == nil then
|
||||
id2id[wid] = next_id
|
||||
next_id = next_id + 1
|
||||
local sid = id2id[wid]
|
||||
map[sid] = minetest.get_name_from_content_id(wid)
|
||||
end
|
||||
end
|
||||
return map, id2id
|
||||
end
|
||||
|
||||
--- Encodes a table of numbers (ZERO INDEXED) with run-length encoding.
|
||||
-- The reason for the existence of this function is avoiding very long strings when serialising / deserialising. Long strings can be a problem in more ways than one.
|
||||
-- @param tbl number[] The table of numbers to runlength encode.
|
||||
local function runlength_encode(tbl)
|
||||
local result = {}
|
||||
local next = 0
|
||||
local prev, count = nil, 0
|
||||
for i, val in pairs(tbl) do
|
||||
if prev ~= val then
|
||||
local msg = tostring(prev)
|
||||
if count > 1 then msg = tostring(count).."x"..msg end
|
||||
result[next] = msg
|
||||
next = next + 1
|
||||
count = 0
|
||||
end
|
||||
prev = val
|
||||
count = count + 1
|
||||
end
|
||||
local msg_last = tostring(prev)
|
||||
if count > 1 then msg_last = tostring(count) .. "x" .. msg_last end
|
||||
result[next] = msg_last
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local function runlength_decode(tbl)
|
||||
local unpacked = {}
|
||||
local next = 0
|
||||
|
||||
for i, val in pairs(tbl) do
|
||||
local count, sid = string.match(val, "(%d+)x(%d+)")
|
||||
if count == nil then
|
||||
unpacked[next] = tonumber(val)
|
||||
next = next + 1
|
||||
else
|
||||
sid = tonumber(sid)
|
||||
for _ = 1, count do
|
||||
unpacked[next] = sid
|
||||
next = next + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return unpacked
|
||||
end
|
||||
|
||||
return {
|
||||
voxelmanip2raw,
|
||||
make_id_maps,
|
||||
|
||||
runlength_encode,
|
||||
runlength_decode
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
local weac = worldeditadditions_core
|
||||
|
||||
|
||||
local localpath = weac.modpath.."/utils/parse/file/"
|
||||
|
||||
--- Parsers specifically for file formats.
|
||||
-- @namespace worldeditadditions_core.parse.file
|
||||
return {
|
||||
weaschem = dofile(localpath.."weaschem.lua")
|
||||
}
|
|
@ -3,7 +3,7 @@ local Vector3
|
|||
local parse_json, split
|
||||
if worldeditadditions_core then
|
||||
Vector3 = weac.Vector3
|
||||
parse_json = weac.parse.json
|
||||
parse_json = dofile(weac.modpath.."/utils/parse/json.lua")
|
||||
split = weac.split
|
||||
else
|
||||
Vector3 = require("worldeditadditions_core.utils.vector3")
|
||||
|
|
|
@ -24,7 +24,9 @@ wea_c.parse = {
|
|||
seed = dofile(wea_c.modpath.."/utils/parse/seed.lua"),
|
||||
chance = dofile(wea_c.modpath.."/utils/parse/chance.lua"),
|
||||
map = dofile(wea_c.modpath.."/utils/parse/map.lua"),
|
||||
weighted_nodes = dofile(wea_c.modpath.."/utils/parse/weighted_nodes.lua")
|
||||
weighted_nodes = dofile(wea_c.modpath.."/utils/parse/weighted_nodes.lua"),
|
||||
|
||||
file = dofile(wea_c.modpath.."/utils/parse/file/init.lua")
|
||||
}
|
||||
|
||||
dofile(wea_c.modpath.."/utils/parse/tokenise_commands.lua")
|
||||
|
|
Loading…
Reference in New Issue