Add A3D version 3 support

This commit is contained in:
Pyogenics
2024-11-24 18:34:10 +00:00
parent e8fd653c80
commit 696a65e5a2
3 changed files with 174 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
''' '''
from .IOTools import unpackStream, readNullTerminatedString from .IOTools import unpackStream, readNullTerminatedString, calculatePadding
from . import A3DObjects from . import A3DObjects
''' '''
@@ -83,7 +83,20 @@ class A3D:
self.readObjectBlock2(stream) self.readObjectBlock2(stream)
def readRootBlock3(self, stream): def readRootBlock3(self, stream):
raise RuntimeError("Version 3 files are not supported yet") # Verify signature
signature, length = unpackStream("<2I", stream)
if signature != A3D_ROOTBLOCK_SIGNATURE:
raise RuntimeError(f"Invalid root data block signature: {signature}")
# Read data
self.readMaterialBlock3(stream)
self.readMeshBlock3(stream)
self.readTransformBlock3(stream)
self.readObjectBlock3(stream)
# Padding
padding = calculatePadding(length)
stream.read(padding)
''' '''
Material data blocks Material data blocks
@@ -101,6 +114,23 @@ class A3D:
material.read2(stream) material.read2(stream)
self.materials.append(material) self.materials.append(material)
def readMaterialBlock3(self, stream):
# Verify signature
signature, length, materialCount = unpackStream("<3I", stream)
if signature != A3D_MATERIALBLOCK_SIGNATURE:
raise RuntimeError(f"Invalid material data block signature: {signature}")
# Read data
print(f"Reading material block with {materialCount} materials and length {length}")
for _ in range(materialCount):
material = A3DObjects.A3DMaterial()
material.read3(stream)
self.materials.append(material)
# Padding
padding = calculatePadding(length)
stream.read(padding)
''' '''
Mesh data blocks Mesh data blocks
''' '''
@@ -117,6 +147,23 @@ class A3D:
mesh.read2(stream) mesh.read2(stream)
self.meshes.append(mesh) self.meshes.append(mesh)
def readMeshBlock3(self, stream):
# Verify signature
signature, length, meshCount = unpackStream("<3I", stream)
if signature != A3D_MESHBLOCK_SIGNATURE:
raise RuntimeError(f"Invalid mesh data block signature: {signature}")
# Read data
print(f"Reading mesh block with {meshCount} meshes and length {length}")
for _ in range(meshCount):
mesh = A3DObjects.A3DMesh()
mesh.read3(stream)
self.meshes.append(mesh)
# Padding
padding = calculatePadding(length)
stream.read(padding)
''' '''
Transform data blocks Transform data blocks
''' '''
@@ -139,6 +186,29 @@ class A3D:
transformID, = unpackStream("<I", stream) transformID, = unpackStream("<I", stream)
self.transforms[transformID] = transforms[transformI] self.transforms[transformID] = transforms[transformI]
def readTransformBlock3(self, stream):
# Verify signature
signature, length, transformCount = unpackStream("<3I", stream)
if signature != A3D_TRANSFORMBLOCK_SIGNATURE:
print(f"{stream.tell()}")
raise RuntimeError(f"Invalid transform data block signature: {signature}")
# Read data
print(f"Reading transform block with {transformCount} transforms and length {length}")
transforms = []
for _ in range(transformCount):
transform = A3DObjects.A3DTransform()
transform.read3(stream)
transforms.append(transform)
# Read and assign transform ids
for transformI in range(transformCount):
transformID, = unpackStream("<I", stream)
self.transforms[transformID] = transforms[transformI]
# Padding
padding = calculatePadding(length)
stream.read(padding)
''' '''
Object data blocks Object data blocks
''' '''
@@ -155,3 +225,21 @@ class A3D:
objec = A3DObjects.A3DObject() objec = A3DObjects.A3DObject()
objec.read2(stream) objec.read2(stream)
self.objects.append(objec) self.objects.append(objec)
def readObjectBlock3(self, stream):
# Verify signature
signature, length, objectCount = unpackStream("<3I", stream)
if signature != A3D_OBJECTBLOCK_SIGNATURE:
print(f"{stream.tell()}")
raise RuntimeError(f"Invalid object data block signature: {signature}")
# Read data
print(f"Reading object block with {objectCount} objects and length {length}")
for _ in range(objectCount):
objec = A3DObjects.A3DObject()
objec.read3(stream)
self.objects.append(objec)
# Padding
padding = calculatePadding(length)
stream.read(padding)

View File

@@ -20,7 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
''' '''
from .IOTools import unpackStream, readNullTerminatedString from .IOTools import unpackStream, readNullTerminatedString, readLengthPrefixedString, calculatePadding
class A3DMaterial: class A3DMaterial:
def __init__(self): def __init__(self):
@@ -36,10 +36,17 @@ class A3DMaterial:
print(f"[A3DMaterial name: {self.name} color: {self.color} diffuse map: {self.diffuseMap}]") print(f"[A3DMaterial name: {self.name} color: {self.color} diffuse map: {self.diffuseMap}]")
def read3(self, stream): def read3(self, stream):
pass self.name = readLengthPrefixedString(stream)
self.color = unpackStream("<3f", stream)
self.diffuseMap = readLengthPrefixedString(stream)
print(f"[A3DMaterial name: {self.name} color: {self.color} diffuse map: {self.diffuseMap}]")
class A3DMesh: class A3DMesh:
def __init__(self): def __init__(self):
self.name = ""
self.bboxMax = None
self.bboxMin = None
self.vertexBuffers = [] self.vertexBuffers = []
self.submeshes = [] self.submeshes = []
@@ -58,7 +65,31 @@ class A3DMesh:
submesh.read2(stream) submesh.read2(stream)
self.submeshes.append(submesh) self.submeshes.append(submesh)
print(f"[A3DMesh vertex buffers: {len(self.vertexBuffers)} submeshes: {len(self.submeshes)}]") print(f"[A3DMesh name: {self.name} bbox max: {self.bboxMax} bbox min: {self.bboxMin} vertex buffers: {len(self.vertexBuffers)} submeshes: {len(self.submeshes)}]")
def read3(self, stream):
# Read mesh info
self.name = readLengthPrefixedString(stream)
# XXX: bbox order maybe incorrect, check this (might be min then max and not max then min)
self.bboxMax = unpackStream("<3f", stream)
self.bboxMin = unpackStream("<3f", stream)
stream.read(4) # XXX: Unknown float value
# Read vertex buffers
vertexCount, bufferCount = unpackStream("<2I", stream)
for _ in range(bufferCount):
vertexBuffer = A3DVertexBuffer()
vertexBuffer.read2(vertexCount, stream)
self.vertexBuffers.append(vertexBuffer)
# Read submeshes
submeshCount, = unpackStream("<I", stream)
for _ in range(submeshCount):
submesh = A3DSubmesh()
submesh.read3(stream)
self.submeshes.append(submesh)
print(f"[A3DMesh name: {self.name} bbox max: {self.bboxMax} bbox min: {self.bboxMin} vertex buffers: {len(self.vertexBuffers)} submeshes: {len(self.submeshes)}]")
A3D_VERTEXTYPE_COORDINATE = 1 A3D_VERTEXTYPE_COORDINATE = 1
A3D_VERTEXTYPE_UV1 = 2 A3D_VERTEXTYPE_UV1 = 2
@@ -93,6 +124,7 @@ class A3DVertexBuffer:
class A3DSubmesh: class A3DSubmesh:
def __init__(self): def __init__(self):
self.indices = []
self.faces = [] self.faces = []
self.smoothingGroups = [] self.smoothingGroups = []
self.materialID = None self.materialID = None
@@ -107,10 +139,24 @@ class A3DSubmesh:
self.smoothingGroups.append(smoothingGroup) self.smoothingGroups.append(smoothingGroup)
self.materialID, = unpackStream("<H", stream) self.materialID, = unpackStream("<H", stream)
print(f"[A3DSubmesh faces: {len(self.faces)} smoothing groups: {len(self.smoothingGroups)} materialID: {self.materialID}]") print(f"[A3DSubmesh indices: {len(self.indices)} faces: {len(self.faces)} smoothing groups: {len(self.smoothingGroups)} materialID: {self.materialID}]")
def read3(self, stream):
# Read indices
indexCount, = unpackStream("<I", stream)
for _ in range(indexCount):
index, = unpackStream("<H", stream)
self.indices.append(index)
# Padding
padding = calculatePadding(indexCount*2) # Each index is 2 bytes
stream.read(padding)
print(f"[A3DSubmesh indices: {len(self.indices)} faces: {len(self.faces)} smoothing groups: {len(self.smoothingGroups)} materialID: {self.materialID}]")
class A3DTransform: class A3DTransform:
def __init__(self): def __init__(self):
self.name = ""
self.position = (0.0, 0.0, 0.0) self.position = (0.0, 0.0, 0.0)
self.rotation = (0.0, 0.0, 0.0, 0.0) self.rotation = (0.0, 0.0, 0.0, 0.0)
self.scale = (0.0, 0.0, 0.0) self.scale = (0.0, 0.0, 0.0)
@@ -122,14 +168,33 @@ class A3DTransform:
print(f"[A3DTransform position: {self.position} rotation: {self.rotation} scale: {self.scale}]") print(f"[A3DTransform position: {self.position} rotation: {self.rotation} scale: {self.scale}]")
def read3(self, stream):
self.name = readLengthPrefixedString(stream)
self.position = unpackStream("<3f", stream)
self.rotation = unpackStream("<4f", stream)
self.scale = unpackStream("<3f", stream)
print(f"[A3DTransform name: {self.name} position: {self.position} rotation: {self.rotation} scale: {self.scale}]")
class A3DObject: class A3DObject:
def __init__(self): def __init__(self):
self.name = "" self.name = ""
self.meshID = None self.meshID = None
self.transformID = None self.transformID = None
self.materialIDs = []
def read2(self, stream): def read2(self, stream):
self.name = readNullTerminatedString(stream) self.name = readNullTerminatedString(stream)
self.meshID, self.transformID = unpackStream("<2I", stream) self.meshID, self.transformID = unpackStream("<2I", stream)
print(f"[A3DObject name: {self.name} meshID: {self.meshID} transformID: {self.transformID}]") print(f"[A3DObject name: {self.name} meshID: {self.meshID} transformID: {self.transformID} materialIDs: {len(self.materialIDs)}]")
def read3(self, stream):
self.meshID, self.transformID, materialCount = unpackStream("<3I", stream)
# Read material IDs
for _ in range(materialCount):
materialID, = unpackStream("<I", stream)
self.materialIDs.append(materialID)
print(f"[A3DObject name: {self.name} meshID: {self.meshID} transformID: {self.transformID} materialIDs: {len(self.materialIDs)}]")

View File

@@ -34,3 +34,17 @@ def readNullTerminatedString(stream):
string += char string += char
char = stream.read(1) char = stream.read(1)
return string.decode("utf8") return string.decode("utf8")
def calculatePadding(length):
# (it basically works with rounding)
paddingSize = (((length + 3) // 4) * 4) - length
return paddingSize
def readLengthPrefixedString(stream):
length, = unpackStream("<I", stream)
string = stream.read(length)
paddingSize = calculatePadding(length)
stream.read(paddingSize)
return string.decode("utf8")