[Closed] [MAXSCRIPT] Binary 3D model format recursive searching
I’d like to start out by saying that I’m very interesting in programming and using languages such as MAXScript to do cool things. I’m starting out small and working with an older game, Midtown Madness 2. It’s well documented and the vertex format is simple enough to understand. I capture the vertices and faces into an array and put those into BuildTVFaces to get a basic mesh. Now here’s where my problem comes in: The PKG file format is what contains 3D models and shaders (information about textures to apply to the object). The models, unfortunately, are split into “Sections” or “Strips”, meaning the faces were detached to give them a crisp look in game (since the game doesn’t support anything like smoothing groups).
Since this format is well-documented, I was able to find pseudo-C format information about each FILE section:
struct PKGFileData { long nSections; // Number of sections making up this LOD long nVerticesTot; // Total number of vertices in this LOD long nIndiciesTot; // Total number of indicies in this LOD long nSections2; // Repetition of the number of sections? // Highly unlikely, but I have no better suggestion at // this time long fvf; // Flags defining what components are provided with // each vertex, see below PKGSection[nSections] sections; }
struct PKGSection { ushort nStrips; // Number of geometry strips in this section ushort flags; // Unknown flags (Always 0 in MM2 PKGs) long shaderOffset; // Offset into the shader list of the requested // paintjob PKGStrip[nStrips] strips; }
struct PKGStrip { long primType; // Determines the primitive type long nVertices; // Number of vertices in this strip PKGVertex[nVertices] vertices; long nIndices; // Number of indices making up the geometry strip ushort[nIndices] indices; }
These 3 are the most important. Basically the file starts out with an identifier, PKG3 (sometimes PKG2, seems to be deprecated for the most part). Without a terminator, it jumps straight to the first FILE and then reads everything from there. I was able to get my basic loading code finished, but now I’ve run into a serious roadblock: Recursively searching. Basically, there’s a bunch of different “FILE” sections which is basically a container for each object for the vehicle (BODY_(H,M,L,VL, WHL(0,1,2,3), etc.). I can’t seem to get the recursive searching down, since there’s no terminator values and it just keeps reading until it hits the end of the file (which causes errors and results in no mesh). If I remove my crappy recursive searching functions, it loads the mesh just fine. But it’s only one part of it!
Here’s my MAXScript that I need help with (currently it has some bad looping functions, but there are some notes in there to give you an idea of what I need help with):
f = fopen "C:\\Program Files (x86)\\Microsoft Games\\Midtown Madness 2\\geometry\\vpbug.pkg" "rb"
clearlistener()
fn readFixedStr bstream fixedLen =
(
local str = ""
for i = 1 to fixedLen do
(
str+= bit.intAsChar (ReadByte bstream #unsigned)
)
str
)
pkgTyp=readFixedStr f 4
-- Must be PKG3 file, otherwise terminate
if pkgTyp == "PKG3" then (
-- PKG3 file format
print "Angel Studios PacKaGe File Format V3"
messageBox "PKG3 file detected" title:"File type"
fSeek f 4 #seek_set -- Make sure we're on address 0x04
Print ("I'm at @ 0x"+((bit.intAsHex(ftell f))as string))
-- Here's what needs to happen:
-- For each recursive search, determine what the header is, what the name is, then add the sum...
-- of those to offset to fNstrips for next section. Oleg's source makes it really difficult to understand..
-- Verbal logic:
-- For each instance of idFILE "FILE" beginning from 0x04, check the length of the objName, retrieve the objName (with 0x00 terminator)
-- Read the section data and take note of how many sections are present.
-- For each new section, read PKG section data and recurse.
-- Recurse until an instance of "FILE.shaders" or "FILE.offset" is found.
do (
--This is a 3D model, not shaders or an offset.
isGeometry=true
-- PKG3 File
-- We need to recurse all of the FILES present in the package
-- But we need to recurse the sections most importantly
idFILE=readFixedStr f 4 -- "FILE"
fLenName=ReadByte f #unsigned -- Length of object name
fStrName=readFixedStr f fLenName -- get objName
fFileLen=ReadLong f -- Length of FILE in bytes
-- This part will look like "FILE.BODY_H." in a hex editor (the first period being the fLenName and the last period being a 00 terminator which is included in fLenName)
-- If an instance of "FILE.shaders" or "FILE.offset" is found, DO NOT READ AS GEOMETRY
-- PKG File Data
fNSections=ReadLong f -- This will help us split them into sections, gotta figure out how
fNVerticesTot=ReadLong f -- Total Vertices
fNIndiciesTot=ReadLong f -- Total Indicies (faces)
fNSections2=ReadLong f -- Total Sections 2 (unknown)
fFVF=ReadLong f -- Flags for D3DFVF stuff
-- PKG Section
--This is where we recurse
fNStrips=ReadShort f #unsigned -- Number of strips in this section
fUnkFlags=ReadShort f #unsigned -- Flags (unused in MM2)
fToShaders=ReadLong f -- Offset to assigned shader in list
-- PKG Strip
fPrimType=ReadLong f -- Primitive type for this strip (triangle)
fNVertices=ReadLong f
curVerts=#()
curFaces=#()
curUVs=#()
-- PKG Vertex
for v = 1 to fNVertices do (
vx=ReadFloat f
vy=ReadFloat f
vz=ReadFloat f
vnx=ReadFloat f
vny=ReadFloat f
vnz=ReadFloat f
tu=ReadFloat f
tv=ReadFloat f
append curVerts[vx,vz,vy]
append curUVs[tu,tv,0]
)
fNIndicies=ReadLong f
for x = 1 to fNIndicies/3 do(
fa=ReadShort f #unsigned+1
fb=ReadShort f #unsigned+1
fc=ReadShort f #unsigned+1
append curFaces[fc,fb,fa]
)
pkgObj = mesh vertices:curVerts faces:curFaces
pkgObj.numTVerts = curUVs.count
buildTVFaces pkgObj
pkgObj.name=fStrName
--convertTo pkgObj PolyMeshObject
for j = 1 to curUVs.count do setTVert pkgObj j curUVs[j]
for j = 1 to curFaces.count do setTVFace pkgObj j curFaces[j]
print "Why am I here?"
) while (isGeometry)
) else (
--If PKG2 file
if pkgTyp == "PKG2" then (
print "Angel Studios PacKaGe File Format V2"
messageBox "PKG2 files are not supported!"
) else (
-- Catch unsupported files if not PKG2
messageBox "Unsupported file type, terminating!"
)
)
Print ("Last Read @ 0x"+((bit.intAsHex(ftell f))as string))
gc()
fclose f
Hopefully this is enough information. More PKG file format information can be found here:
http://mm2kiwi.apan.is-a-geek.com/index.php?title=PKG
I greatly appreciate anyone who can help me figure this out. I’m eager to learn!
So, the file is organised into structures. If you recognize first structure you should know it’s size, and I gusess in PKG structure start pos + structure size will lead to start of next structure. No searching algorithm required.
You got structures so use structures in script as well (object oriented programming). Recently I uses objects really often in my scripts.
Example how your structure may look:
struct node_mesh
(
name = "mesh",
position = point3 0 0 0,
vertArray = #(),
faceArray= #(),
-- etc
function load file = -- loads mesh structure from file and stores it in memory
(
if file != undefined then
(
name = file.load name -- for example
-- load vertices, faces etc
)
)
function create = -- creates mesh object in scene
(
object= mesh name:name vertices:vertArray faces:#()
convertTo object (Editable_Poly) --convert to Editable_Poly
-- add faces, mapping all the stuff
move object position
object -- return created object as function return value
)
)
If you have defined all structures you can now go throught the file and load them all (pseudo-code):
loop until end of file
(
header = file.read the struct header
case header of
(
"mesh": -- mesh header recogonized
(
value = node_mesh()
value.load file
if value.loadedWell() then
append meshes value -- add loaded structure to array
)
"material":
(
-- similar here
)
default: -- unknown structure
(
file skip structureSize
)
)
)
After that you should have all structures loaded into arrays of easy to handle objects which you can combine together to build the object in scene.
Thank you for the response, sorry I’m a bit late coming back to check up on this. I did get the recursive searching down, my only problem now is that the “strips” are basically separate objects and the hardest thing for me to do is combine them into one mesh as they should be. I was able to get them loaded as separate objects and UVs and everything worked just fine:
I tried everything possible to get the mesh to parse as one object, but it just turned into a bunch of garbage and it was obvious the indicies is where I am failing. I’ve tried seeking help from another site as well but maybe I’m just not experienced enough to understand what people are trying to tell me, haha. Here’s an example of the vertice/indicie loading code:
-- -- PKG Strip
-- fPrimType=ReadLong f -- Primitive type for this strip (triangle)
-- fNVertices=ReadLong f
--
-- fNVerts = 0
--
-- -- PKG Vertex
-- for v = 1 to fNVertices do (
--
-- fNVerts += 1
--
-- vert = pVertex x: (ReadFloat f) y: (ReadFloat f) z: (ReadFloat f) nx: (ReadFloat f) ny: (ReadFloat f) nz: (ReadFloat f) u: (ReadFloat f) v: (ReadFloat f)
-- -- append curVerts[-vert.x,vert.z,vert.y]
-- -- append curUVs[vert.u,-vert.v,0]
--
-- )
--
-- fNIndicies=ReadLong f
--
-- -- for x = 1 to fNIndicies/3 do (
-- -- i2=ReadShort f #unsigned*3
-- -- i1=ReadShort f #unsigned*3+1
-- -- i3=ReadShort f #unsigned*3+2
-- -- append curFaces[i1,i2,i3]
-- -- )
It’s commented out because I’m in the process of rewriting it, taking advantage of structural functions. More progress includes being able to read the shaders section of a PKG file, I have all the necessary information, just have to parse it correctly. ’tis a fun experience.
Thanks again!