Last active
April 26, 2021 19:14
-
-
Save hhrhhr/ee63fbcd280f6110cc2f8184da857964 to your computer and use it in GitHub Desktop.
Subnautica Below Zero map maker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@echo off | |
rem ==== start of user settings ==== | |
rem minimum level (0...24) | |
set LEVEL=16 | |
rem input raw files from AssetStudio (https://github.com/Perfare/AssetStudio) | |
set MESH_DIR=h:\raw | |
rem output directories | |
set OBJ_DIR=h:\obj | |
set LEVEL_DIR=h:\level | |
set LUA=lua54_static.exe | |
rem ==== end of user settings ==== | |
set LUA_DIR=%~dp0% | |
rem goto :merge | |
:convert | |
if not exist "%OBJ_DIR%" mkdir "%OBJ_DIR%" | |
for /r "%MESH_DIR%" %%i in (*.dat) do ( | |
"%LUA%" "%LUA_DIR%\mesh2obj_bz.lua" "%%i" "%OBJ_DIR%" %LEVEL% | |
if ERRORLEVEL 1 goto eof | |
) | |
:merge | |
if not exist "%LEVEL_DIR%" mkdir "%LEVEL_DIR%" | |
pushd "%LEVEL_DIR%" | |
for /l %%i in (%LEVEL% 1 24) do ( | |
echo merge level %%i | |
copy /y "%OBJ_DIR%\*-%%i-*.obj" level-%%i.obj >NUL 2>&1 | |
) | |
popd | |
:eof | |
pause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local FN = arg[1] or [[h:\sub_raw\unnamed asset-resources.assets-10072.dat]] | |
local DIR = arg[2] or [[h:\sub_obj]] | |
local SKIP = tonumber(arg[3]) or 0 | |
print(FN) | |
if "\\" ~= DIR:sub(-1) then | |
DIR = DIR .. "\\" | |
end | |
local function writeObj(obj) | |
local withNormal = obj.normal[1] and true or false | |
local n = withNormal and "_wn" or "" | |
local fn = DIR .. obj.name:sub(7) .. n .. ".obj" | |
local w = assert(io.open(fn, "w+b")) | |
local fmt = { | |
head = "#o n_%s\n# %d points, %d faces\n", | |
point = "v %f %f %f\n", | |
normal = "vn %f %f %f\n", | |
group = "g g_%s\ns 1\n", | |
face_n = "f -%d//-%d -%d//-%d -%d//-%d\n", | |
face = "f -%d -%d -%d\n" | |
} | |
w:write(fmt.head:format(obj.name, #obj.point, #obj.face)) | |
for i = 1, #obj.point do | |
local p = obj.point[i] | |
w:write(fmt.point:format(p[1], p[2], p[3])) | |
end | |
if withNormal then | |
for i = 1, #obj.normal do | |
local n = obj.normal[i] | |
w:write(fmt.normal:format(n[1], n[2], n[3])) | |
end | |
w:write(fmt.group:format(obj.name)) | |
for i = 1, #obj.face do | |
local f = obj.face[i] | |
w:write(fmt.face_n:format(f[1], f[1], f[2], f[2], f[3], f[3])) | |
end | |
else | |
w:write(fmt.group:format(obj.name)) | |
for i = 1, #obj.face do | |
local f = obj.face[i] | |
w:write(fmt.face:format(f[1], f[2], f[3])) | |
end | |
end | |
w:close() | |
end | |
local function convert() | |
local m = require("mesh_utils_bz") | |
local obj | |
if SKIP >= 0 then | |
local level = m.open(FN) -- get fileSize, name, offset | |
if level >= SKIP then | |
obj = m.processMesh() | |
writeObj(obj) | |
end | |
else | |
m.just_open(FN) | |
obj = m.processMesh() | |
writeObj(obj) | |
end | |
end | |
convert() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local m = {} | |
local obj = {} | |
local r | |
local su = string.unpack | |
local function uint8() | |
local res, _ = su("B", r:read(1)) | |
return res | |
end | |
local function uint16() | |
local res, _ = su("H", r:read(2)) | |
return res | |
end | |
local function uint32() | |
local res, _ = su("I4", r:read(4)) | |
return res | |
end | |
local function str(len) | |
return r:read(len) | |
end | |
local function float() | |
local res, _ = su("f", r:read(4)) | |
if res ~= res then res = 0.0 end | |
return res | |
end | |
local function seek(n) r:seek("cur", n) end | |
local function padding() | |
local pos = r:seek() | |
pos = pos % 4 | |
if pos > 0 then | |
seek(4 - pos) | |
end | |
return r:seek() | |
end | |
local function pos() print("!!! pos:" .. r:seek()) end | |
local function string() | |
local sz = uint32() | |
local name = str(sz) | |
padding() | |
return name | |
end | |
local function AABB() | |
local t = {} | |
t.center = {float(), float(), float()} | |
t.extent = {float()*2, float()*2, float()*2} | |
-- print(("center: %s\nextent: %s"):format( | |
-- table.concat(t.center, ", "), table.concat(t.extent, ", "))) | |
return t | |
end | |
local function SubMeshes() -- vector | |
local sz = uint32() | |
assert(1 == sz, sz) | |
seek(4) -- firstByte | |
obj.indexCount = uint32() | |
seek(4*3) -- topology, baseVertex, firstVertex | |
obj.points = uint32() -- vertexCount | |
obj.AABB = AABB() | |
print(("%s:\t%d points, %d faces"):format(obj.name, obj.points, obj.indexCount//3)) | |
-- print(("center: %s\nextent: %s"):format( | |
-- table.concat(obj.AABB.center, ", "), table.concat(obj.AABB.extent, ", "))) | |
end | |
local function BlendShapeData() | |
-- // BlendShapeData Shapes | |
assert(0 == uint32()) -- vector vertices | |
assert(0 == uint32()) -- vector shape | |
assert(0 == uint32()) -- vector channels | |
assert(0 == uint32()) -- vector fullWeights | |
-- \\ BlendShapeData Shapes | |
end | |
local function vars() | |
assert(0 == uint32()) -- vector BindPose | |
assert(0 == uint32()) -- vector BoneNameHashes | |
seek(4*1) -- unsigned int RootBoneNameHash | |
assert(0 == uint32()) -- vector BonesAABB | |
assert(0 == uint32()) -- VariableBoneCountWeights VariableBoneCountWeights | |
obj.MeshCompression = uint8() -- UInt8 MeshCompression | |
seek(3*1 + 4) -- bool IsReadable, KeepVertices, KeepIndices, int IndexFormat | |
end | |
local function other() | |
local localAABB = AABB() | |
seek(4) -- MeshUsageFlags | |
assert(0 == uint32()) -- vector BakedConvexCollisionMesh | |
assert(0 == uint32()) -- vector BakedTriangleCollisionMesh | |
seek(4) -- float MeshMetrics[0] | |
seek(4) -- float MeshMetrics[1] | |
-- StreamingInfo StreamData | |
seek(4) -- unsigned int offset | |
seek(4) -- unsigned int size | |
local sz = uint32() | |
seek(sz) -- string path | |
end | |
local function bitUnpacker(t) | |
local result = {} | |
local idx = 1 | |
local sz = 0 | |
local buf = 0 | |
local mask = (1 << t.bitSize) - 1 | |
-- local num = 0 | |
-- while num < t.count do | |
for _ = 1, t.count do | |
local buf_add | |
while sz < t.bitSize do | |
buf_add, idx = su("B", t.data, idx) | |
buf = (buf_add << sz) + buf | |
sz = sz + 8 | |
end | |
local res = buf & mask | |
table.insert(result, res) | |
-- num = num + 1 | |
buf = buf >> t.bitSize | |
sz = sz - t.bitSize | |
--print("num", num, "res", res, "buf", buf, "sz", sz) | |
end | |
return result | |
end | |
local function packedBitVector(str, withOpt) | |
local t = {} | |
local p1 = r:seek() | |
t.count = uint32() | |
if withOpt then | |
t.range = float() | |
t.start = float() | |
end | |
t.sz = uint32() | |
t.data = r:read(t.sz) | |
padding() | |
t.bitSize = uint8() | |
local p2 = padding() | |
-- print(("%12s count:%6d, %10f %10f, sz:%6d, b:%2d, %d-%d"):format( | |
-- str, t.count, t.range or 0, t.start or 0, t.sz, t.bitSize, p1, p2)) | |
return t | |
end | |
local function processVertices(v) | |
local point = {} | |
local scale = v.range -- t.start -- CHECK | |
local o = obj.offset | |
if v.bitSize == 16 then | |
local h, pos = 0, 1 | |
for i = 1, v.count, 3 do | |
local pp = {} | |
for j = 1, 3 do | |
h, pos = su("H", v.data, pos) | |
h = (h * scale / 65535.0) + v.start | |
table.insert(pp, h) | |
end | |
-- fix coord | |
pp[1] = o.x + pp[1] | |
pp[2] = o.y + pp[2] | |
pp[3] = o.z - pp[3] | |
table.insert(point, pp) | |
end | |
else | |
assert(false, "not implemented") | |
end | |
return point | |
end | |
local function processNormals(n, s) | |
local sign = {} | |
local tmp = bitUnpacker(s) | |
for i = 1, #tmp do | |
sign[i] = tmp[i] == 1 and 1 or -1 | |
end | |
local norm = {} | |
tmp = bitUnpacker(n) | |
local scale = n.range -- t.start -- CHECK | |
for i = 1, #tmp, 2 do | |
local n1 = tmp[i] * scale / 255.0 + n.start | |
local n2 = tmp[i+1] * scale / 255.0 + n.start | |
local n3s = sign[(i+1)/2] | |
local n3 = (1.0 - (n1^2 + n2^2)) | |
n3 = math.abs(n3)^0.5 * n3s | |
table.insert(norm, {n1, n2, -n3}) | |
end | |
return norm | |
end | |
local function processFaces(t) | |
-- for i = 1, faces do | |
-- local f1, f2, f3 = points-uint16(), points-uint16(), points-uint16() | |
-- table.insert(face, { f1, f3, f2 }) | |
-- end | |
local face = {} | |
local tmp = bitUnpacker(t) | |
--assert(t.count == #tmp, t.count .. " ~= " .. #tmp) | |
local p = obj.points | |
for i = 1, t.count, 3 do | |
local ff = {p - tmp[i], p - tmp[i+2], p - tmp[i+1]} | |
table.insert(face, ff) | |
end | |
--assert(t.count // 3 == #f) | |
return face | |
end | |
local function compressedMesh() | |
local t = {} | |
assert(0 == uint32()) -- vector IndexBuffer | |
-- // VertexData VertexData | |
assert(0 == uint32()) -- unsigned int VertexCount | |
sz = uint32() -- vector m_Channels | |
seek(sz * 4) -- ChannelInfo data, 4 * uint8 | |
assert(0 == uint32()) -- TypelessData DataSize | |
-- \\ VertexData VertexData | |
-- CompressedMesh CompressedMesh | |
local v = packedBitVector("Vertices", true) | |
packedBitVector("UV", true) | |
local n = packedBitVector("Normals", true, true) | |
packedBitVector("Tangents", true) | |
packedBitVector("Weights") | |
local s = packedBitVector("NormalSigns") | |
packedBitVector("TangentSigns") | |
packedBitVector("FloatColors") | |
packedBitVector("BoneIndices") | |
seek(8) -- ??? | |
local f = packedBitVector("Triangles") | |
seek(4*1) -- unsigned int UVInfo | |
obj.point = processVertices(v) | |
obj.normal = {} --processNormals(n, s) | |
obj.face = processFaces(f) | |
end | |
local function uncompressedMesh() | |
obj.face = {} | |
local p = obj.points | |
-- // vector IndexBuffer | |
local sz = uint32() | |
for i = 1, sz // 6 do | |
local f1, f2, f3 = p-uint16(), p-uint16(), p-uint16() | |
table.insert(obj.face, { f1, f3, f2 }) | |
end | |
-- \\ vector IndexBuffer | |
-- // VertexData VertexData | |
sz = uint32() | |
-- assert(points == sz) | |
sz = uint32() -- vector m_Channels | |
seek(sz * 4) -- ChannelInfo data, 4 * uint8 | |
sz = uint32() | |
-- assert(points == sz // 24) | |
obj.point = {} | |
obj.normal = {} | |
local o = obj.offset | |
for _ = 1, obj.points do | |
local x, y, z = float() + o.x, float() + o.y, o.z - float() | |
table.insert(obj.point, { x, y, z }) | |
-- table.insert(obj.normal, { float(), float(), -float() }) | |
seek(4*3) | |
end | |
-- \\ VertexData VertexData | |
-- CompressedMesh CompressedMesh | |
packedBitVector("Vertices", true) | |
packedBitVector("UV", true) | |
packedBitVector("Normals", true) | |
packedBitVector("Tangents", true) | |
packedBitVector("Weights") | |
packedBitVector("NormalSigns") | |
packedBitVector("TangentSigns") | |
packedBitVector("FloatColors") | |
packedBitVector("BoneIndices") | |
seek(8) -- ??? | |
packedBitVector("Triangles") | |
seek(4*1) -- unsigned int UVInfo | |
end | |
function m.open(fn) | |
r = assert(io.open(fn, "rb")) | |
obj.fileSize = r:seek("end") | |
r:seek("set") | |
obj.name = string() | |
for x, y, z in obj.name:gmatch("-(.+)-(.+)-(.+)") do | |
obj.offset = {x = x * 32.0, y = y * 32.0, z = z * -32.0} | |
end | |
return obj.offset.y // 32 | |
end | |
function m.just_open(fn) | |
r = assert(io.open(fn, "rb")) | |
obj.fileSize = r:seek("end") | |
r:seek("set") | |
obj.name = string() | |
obj.offset = {x = 0.0, y = 0.0, z = 0.0} | |
end | |
function m.processMesh() | |
SubMeshes() | |
BlendShapeData() | |
vars() | |
if 0 == obj.MeshCompression then uncompressedMesh() | |
elseif 2 == obj.MeshCompression then compressedMesh() | |
else assert(false, obj.MeshCompression) | |
end | |
other() | |
local pos = r:seek() | |
assert(obj.fileSize == pos, obj.fileSize .. " ~= " .. pos) | |
return obj | |
end | |
return m |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
static Lua executable: https://mega.nz/file/39hykALT#boTEEu_UhGl51HSZcGK-P66XdaadKGmUuQ78TZ-KWx4