Hello - yes, sure. It looks like I was distracted by other things during the holidays.
Below is the latest version of it, I believe. It takes one command line argument, the name of an xbf file, and produces an obj file with the same name (only extension differs), with the name of the objects, their vertices and faces. All vertices are exported with their coordinates transformed; hence the use of the Numpy library.
#!/usr/bin/env python3
import struct
import numpy as np
import argparse
import os
def readInt(file):
return struct.unpack("<i", file.read(4))[0]
def readUInt(file):
return struct.unpack("<I", file.read(4))[0]
def readInt16(file):
return struct.unpack("<h", file.read(2))[0]
def readUInt16(file):
return struct.unpack("<H", file.read(2))[0]
def readMatrix(file):
return struct.unpack("<16d", file.read(8*16))
def readByte(file):
return struct.unpack("<c", file.read(1))[0]
class Vertex:
def __init__(self):
self.vertices = None
def readFrom(self, file):
self.vertices = struct.unpack("<6f", xbfFile.read(4 * 6))
class Face:
def __init__(self):
self.longs = []
self.floats = []
def readFrom(self, file):
self.longs = struct.unpack("<5i", xbfFile.read(4 * 5))
self.floats = struct.unpack("<6f", xbfFile.read(4 * 6))
VertexTotal = 0 #for writing out to obj
class Object:
def __init__(self):
self.children = []
self.vertices = []
self.faces = []
def readFrom(self, file):
vertexCount = readInt(file)
flags = readInt(file)
hasPrelight = bool(flags & 1)
hasFaceData = bool(flags & 2)
hasVertexAnimation = bool(flags & 4)
hasKeyAnimation = bool(flags & 8)
faceCount = readInt(file)
childCount = readInt(file)
self.transform = readMatrix(file)
nameLength = readInt(file)
self.name = file.read(nameLength)
for i in range(childCount):
child = Object()
child.readFrom(file)
self.children.append(child)
for i in range(vertexCount):
vertex = Vertex()
vertex.readFrom(file)
self.vertices.append(vertex)
for i in range(faceCount):
face = Face()
face.readFrom(file)
self.faces.append(face)
if hasPrelight:
rgb = [readInt(file) for i in range(vertexCount)]
if hasFaceData:
faceData = [readInt(file) for i in range(faceCount)]
if hasVertexAnimation:
frameCount = readInt(file)
count = readInt(file)
actual = readInt(file)
keyList = [readUInt(file) for i in range(actual)]
if count < 0: #compressed
scale = readUInt(file)
compCount = readUInt(file)
compressedData = [readUInt16(file) for i in range(compCount*4)]
if (scale & 0x80000000): #interpolated
interpolationData = [readUInt(file) for i in range(frameCount)]
if hasKeyAnimation:
frameCount = readInt(file)
keynimationflags = readInt(file)
actual = readInt(file)
for i in range(frameCount+1):
readInt16(file)
for i in range(actual):
struct.unpack("<12f", file.read(4 * 12))
def writeToObj(self, file, transform=None, resetTotal=True):
global VertexTotal
if resetTotal:
VertexTotal = 0
np_transform = np.array(self.transform)
np_transform.shape=(4,4)
if transform is not None:
np_transform = np_transform.dot(transform)
name = self.name.decode()
file.write("o "+name+"\n")
for vertex in self.vertices:
np_point = np.array([vertex.vertices[0], vertex.vertices[1], vertex.vertices[2], 1])
np_result = np_point.dot(np_transform)
file.write("v "+str(np_result[0])+" "+str(np_result[1])+" "+str(np_result[2])+" "+str(np_result[3])+"\n")
file.write("\n")
for face in self.faces:
file.write("f "+str(face.longs[0]+1+VertexTotal)+" "+str(face.longs[1]+1+VertexTotal)+" "+str(face.longs[2]+1+VertexTotal)+"\n")
file.write("\n")
VertexTotal += len(self.vertices)
for child in self.children:
child.writeToObj(file, np_transform, False)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("file")
args = parser.parse_args()
with open(args.file, "rb") as xbfFile:
version = readInt(xbfFile)
appDataSize = readInt(xbfFile)
xbfFile.read(appDataSize)
textureNameDataSize = readInt(xbfFile)
xbfFile.read(textureNameDataSize)
xbfObject = Object()
xbfObject.readFrom(xbfFile)
xbfname = os.path.splitext(args.file)[0]
with open(xbfname+".obj", "w") as f:
xbfObject.writeToObj(f)
Some of the types read may not be strictly correct (i.e. int vs uint). I will have to re-immerse myself in the project to ensure I'm not forgetting something already worked out.
What prompts your interest? Have you modded Emperor or other Westwood games before? Is there something you are working on at the moment?