From 237bcd21373a0f587582c3dfeffe89e03a23f13a Mon Sep 17 00:00:00 2001 From: Hipstercat Date: Thu, 28 Jan 2021 17:41:20 +0100 Subject: [PATCH] ok --- lib/__init__.py | 0 lib/fileutils.py | 63 ++++++ lib/game.py | 71 +++++++ lib/packet.py | 70 +++++++ obj2gbs.py | 496 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 700 insertions(+) create mode 100644 lib/__init__.py create mode 100644 lib/fileutils.py create mode 100644 lib/game.py create mode 100644 lib/packet.py create mode 100644 obj2gbs.py diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/fileutils.py b/lib/fileutils.py new file mode 100644 index 0000000..37dba58 --- /dev/null +++ b/lib/fileutils.py @@ -0,0 +1,63 @@ +import struct + + +def read_int(fp): + return struct.unpack("> dec_bits & 1) == 0: + dec_pos = ((compressed_bytes[i] + ( + (compressed_bytes[i + 1] & 0xF0) << 4) - buff_start - j) & 0xFFF) - 0x1000 + j + dec_len = (compressed_bytes[i + 1] & 0xF) + 3 + i += 2 + while dec_len > 0: + if dec_pos >= 0: + res[j] = res[dec_pos] + else: + res[j] = 32 + j += 1 + dec_pos += 1 + dec_len -= 1 + else: + res[j] = compressed_bytes[i] + i += 1 + j += 1 + dec_bits += 1 + return res diff --git a/lib/game.py b/lib/game.py new file mode 100644 index 0000000..f13487b --- /dev/null +++ b/lib/game.py @@ -0,0 +1,71 @@ +import math + + +class Vec2: + def __init__(self, x=0.0, y=0.0): + self.x = x + self.y = y + + def __str__(self): + return "[%s,%s]" % (self.x, self.y) + + +class Vec3: + def __init__(self, x=0.0, y=0.0, z=0.0): + self.x = x + self.y = y + self.z = z + + def __add__(self, other): + return Vec3(self.x + other.x, self.y + other.y, self.z + other.z) + + def __sub__(self, other): + return self + -other + + def __radd__(self, other): + return Vec3(self.x + other.x, self.y + other.y, self.z + other.z) + + def __mul__(self, scalar): + return Vec3(self.x * scalar, self.y * scalar, self.z * scalar) + + def __rmul__(self, scalar): + return Vec3(self.x * scalar, self.y * scalar, self.z * scalar) + + def __neg__(self): + return Vec3(-self.x, -self.y, -self.z) + + def __pos__(self): + return Vec3(self.x, self.y, self.z) + + def __xor__(self, other): + cx = self.y * other.z - self.z * other.y + cy = self.z * other.x - self.x * other.z + cz = self.x * other.y - self.y * other.x + return Vec3(cx, cy, cz) + + def cross(self, other): + return self ^ other + + def normalize(self): + w = math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) + if w == 0: + return Vec3() + else: + return Vec3(self.x / w, self.y / w, self.z / w) + + def __str__(self): + return "[%s,%s,%s]" % (self.x, self.y, self.z) + + +class VecRGB: + def __init__(self, r=0, g=0, b=0): + self.r = r + self.g = g + self.b = b + + def __str__(self): + return "[%s,%s,%s]" % (self.r, self.g, self.b) + + +def cross(v1: Vec3, v2: Vec3): + return v1.cross(v2) diff --git a/lib/packet.py b/lib/packet.py new file mode 100644 index 0000000..06334da --- /dev/null +++ b/lib/packet.py @@ -0,0 +1,70 @@ +import io +import struct + + +class Packet(io.BytesIO): + def put_byte(self, val) -> None: + self.write(struct.pack(' int: + return struct.unpack(' None: + for byte in b: + self.write(struct.pack(' bytes: + return bytes(struct.unpack('<'+str(num)+'B', self.read(num))) + + def put_short(self, val) -> None: + self.write(struct.pack(' int: + return struct.unpack(' None: + self.write(struct.pack(' None: + self.write(struct.pack(' int: + return struct.unpack(' int: + return struct.unpack(' None: + self.write(struct.pack(' int: + return struct.unpack(' None: + self.write(struct.pack(' float: + return struct.unpack(' None: + self.write(val + b'\x00') + + def put_string_size(self, val, size) -> None: + self.write(val.encode("utf8") + b"\x00" * (size - len(val))) + + def get_string_until_none(self) -> str: + s = "" + c = self.get_byte() + while c != 0x00: + s += chr(c) + c = self.get_byte() + return s + + def get_string(self, size) -> str: + return self.get_bytes(size).decode("utf8").replace("\x00", "") + + def get_3bytes_int(self) -> int: + return struct.unpack(" None: + self.write(struct.pack(" List[OBJMaterial]: + materials = [] + curr_mat = None + with open(matlib_file, "r") as fp: + while line := fp.readline(): + line = line.strip() + arr = line.split(" ") + if arr[0] == "newmtl": + mat = OBJMaterial() + materials.append(mat) + mat.name = arr[1].rstrip() + print("NEW MAT: %s" % mat.name) + curr_mat = mat + if arr[0] == "map_Ka" or arr[0] == "map_Kd": + matname_without_ext = "".join(arr[1:]).split("/")[-1] + matname_without_ext = "".join(matname_without_ext.split(".")[0:-1]) + curr_mat.texture = matname_without_ext + print("MAT %s HAS TEXTURE %s" % (curr_mat.name, curr_mat.texture)) + return materials + + +class GbsData: + def __init__(self): + self.name = "" + self.optionsflags: int = 0 + self.nndefs: int = 0 + self.num_normals: int = 0 + self.normals: List[int] = [] # word + self.indexed_normals: List[int] = [] # unsigned short + self.num_vertices: int = 0 + self.vertices: List[Vec3] = [] + self.nsobjs: int = 0 + self.nmobjs: int = 0 + self.indexed_vertices: List[int] = [] # unsigned short + self.vertrgb: List[VecRGB] = [] + self.nverts: int = 0 + self.vertuv: List[UV] = [] + self.MaxObjs: List[MaxObj] = [] + self.SubObjs: List[SubObject] = [] + + def read(self, file): + debug = True + self.name = os.path.basename(file) + with open(file, "rb") as fp: + version_header = read_int(fp) + if version_header != GBS_VERSION: + raise Exception("File does not appear to be a GBS file.") + + self.optionsflags = read_int(fp) + self.num_vertices = read_int(fp) + resize(self.vertices, Vec3, self.num_vertices) + for i in range(self.num_vertices): + self.vertices[i].x = read_float(fp) + self.vertices[i].y = read_float(fp) + self.vertices[i].z = read_float(fp) + + if self.optionsflags & GBSFlagNormals: + self.nndefs = read_int(fp) + self.num_normals = read_int(fp) + resize(self.normals, int, self.num_normals) + for i in range(self.num_normals): + self.normals[i] = read_short(fp) + + self.nverts = read_int(fp) + if debug: print("NVERTS=%s, NUMVERTICES=%s, options=%s" % (self.nverts, self.num_vertices, self.optionsflags)) + resize(self.indexed_vertices, int, self.nverts) + for i in range(self.nverts): + self.indexed_vertices[i] = read_short(fp) + if debug: print(self.indexed_vertices) + + if self.optionsflags & GBSFlagNormals: + resize(self.indexed_normals, int, self.nverts) + for i in range(self.nverts): + self.indexed_normals[i] = read_short(fp) + + if self.optionsflags & GBSFlagUVs: + resize(self.vertuv, UV, self.nverts) + if debug: print("Read %s UV" % self.nverts) + for i in range(self.nverts): + self.vertuv[i].u = read_float(fp) + self.vertuv[i].v = read_float(fp) * -1 + + if self.optionsflags & GBSFlagRGBs: + resize(self.vertrgb, VecRGB, self.nverts) + if debug: print("Read %s RGB" % self.nverts) + for i in range(self.nverts): + self.vertrgb[i].r = read_byte(fp) + self.vertrgb[i].g = read_byte(fp) + self.vertrgb[i].b = read_byte(fp) + + # Get number of objects + self.nmobjs = read_int(fp) + if debug: print("NMOBJS = %s" % self.nmobjs) + resize(self.MaxObjs, MaxObj, self.nmobjs) + for maxobj in self.MaxObjs: + print("Processing new maxobj") + fileMaxObj = FileMaxObj() + fileMaxObj.vstart = read_int(fp) + fileMaxObj.vcount = read_int(fp) + fileMaxObj.nstart = read_int(fp) + fileMaxObj.ncount = read_int(fp) + fileMaxObj.noffset = read_int(fp) + print("vstart=%s vcount=%s nstart=%s ncount=%s noffset=%s" % (fileMaxObj.vstart, fileMaxObj.vcount, fileMaxObj.nstart, fileMaxObj.ncount, fileMaxObj.noffset)) + maxobj.vstart = fileMaxObj.vstart + maxobj.vcount = fileMaxObj.vcount + maxobj.nstart = fileMaxObj.nstart + maxobj.ncount = fileMaxObj.ncount + maxobj.noffset = fileMaxObj.noffset + maxobj.fstart = 0 + maxobj.fcount = 0 + maxobj.sostart = 0 + maxobj.socount = 0 + + # verify max obj + nmcount = 0 + for maxobj in self.MaxObjs: + nmcount += maxobj.vcount + assert nmcount == self.num_vertices + + self.nsobjs = read_int(fp) + print("Subobject count: %s" % self.nsobjs) + resize(self.SubObjs, SubObject, self.nsobjs) + num_faces = 0 + len_tridata = 0 + for ns in range(self.nsobjs): + if debug: print("Processing subobj %s" % ns) + object = self.SubObjs[ns] + object.objname = read_string(fp, 32) + object.maxobjindex = read_int(fp) + object.totaltris = read_int(fp) + if debug: print("read %s totaltris" % object.totaltris) + num_faces += object.totaltris + object.ntris = read_int(fp) + + assert ((object.ntris - 1) / 3 == object.totaltris) + + resize(object.tridata, int, object.ntris + 1) + if debug: print("read gbs: totaltris: %s, tridata is %s long, ntris: %s" % (object.totaltris, len(object.tridata), object.ntris)) + len_tridata += object.ntris + 1 + for i in range(object.ntris): + object.tridata[i] = read_short(fp) + + # if debug: print("tridata: %s" % object.tridata) + + object.verticeref_start = read_int(fp) + object.verticeref_count = read_int(fp) + if self.optionsflags & GBSFlagUVs: + object.texname = read_string(fp, 32) + object.bumptexture = read_string(fp, 32) + + object.falloff = read_float(fp) + if self.optionsflags & GBSFlagRGBs: + object.blend = read_float(fp) + + object.flags = read_int(fp) + object.emissive = read_int(fp) + object.ambient = read_int(fp) + object.diffuse = read_int(fp) + object.specular = read_int(fp) + object.power = read_float(fp) + if debug: print("%s (%s) falloff: %s, blend: %s, flags:%s, emissive: %s, ambiant: %s, diffuse: %s, specular: %s, power: %s" % (object.objname, object.texname, object.falloff, object.blend, object.flags, object.emissive, object.ambient, object.diffuse, object.specular, object.power)) + + maxobj = self.MaxObjs[object.maxobjindex] + maxobj.fcount += object.totaltris + if not maxobj.socount: + maxobj.socount = ns + + maxobj.socount += 1 + if debug: print("read num_faces: %s, len_tridata: %s" % (num_faces, len_tridata)) + + + @staticmethod + def evaluate_tridata(tridata, tri_idx, count): + if count == 0: + count = tridata[0] + if count == 0: + return False + tri_idx = 0 + v1 = tridata[tri_idx + 1] + v2 = tridata[tri_idx + 2] + v3 = tridata[tri_idx + 3] + tri_idx += 3 + + count -= 1 + if count < 0: + count = 0xffff # max unsigned short + if count == 0: + tridata = tridata[tridata[0] * 3 + 1:] + return tridata, tri_idx, count, v1, v2, v3 + + def generate_normals(self): + normals: List[Vec3] = [] + resize(normals, Vec3, self.num_vertices) + for subobj_i in range(len(self.SubObjs)): + subobj = self.SubObjs[subobj_i] + tridata = subobj.tridata + values = self.evaluate_tridata(tridata, -1, 0) + while values: + tridata, tri_idx, count, v1, v2, v3 = values + try: + p = cross(self.vertices[self.indexed_vertices[v2]] - self.vertices[self.indexed_vertices[v1]], self.vertices[self.indexed_vertices[v3]] - self.vertices[self.indexed_vertices[v1]]) + except Exception as e: + raise e + normals[self.indexed_vertices[v1]] += p + normals[self.indexed_vertices[v2]] += p + normals[self.indexed_vertices[v3]] += p + values = self.evaluate_tridata(tridata, tri_idx, count) + + for i in range(len(normals)): + normals[i] = normals[i].normalize() + return normals + + @staticmethod + def from_obj(obj_file): + materials = [] + vertices: List[Vec3] = [] + uvs: List[UV] = [] + normals: List[Vec3] = [] + objects: List[OBJObject] = [] + root_obj = OBJObject() + objects.append(root_obj) + + last_material = None + current_object = root_obj + + uv_ind = {} + + with open(obj_file, "r") as obj_fp: + while line := obj_fp.readline(): + arr = line.split(" ") + if arr[0] == "v": + v = Vec3(float(arr[1]), float(arr[2]), float(arr[3])) + vertices.append(v) + if arr[0] == "vt": + v = UV(float(arr[1]), float(arr[2])) + uvs.append(v) + if arr[0] == "vn": + v = Vec3(float(arr[1]), float(arr[2]), float(arr[3])) + normals.append(v) + if arr[0] == "f": + f = OBJFace() + f1_s = arr[1].split("/") + f2_s = arr[2].split("/") + f3_s = arr[3].split("/") + if len(f1_s) == len(f2_s) == len(f3_s) >= 1: + # face only has vertex index data + v1_index = int(f1_s[0]) + v2_index = int(f2_s[0]) + v3_index = int(f3_s[0]) + f.index_vertices.append(v1_index) + f.index_vertices.append(v2_index) + f.index_vertices.append(v3_index) + if len(f1_s) == len(f2_s) == len(f3_s) >= 2: + # face has vertex index and uv + v1_uv_index = int(f1_s[1]) + v2_uv_index = int(f2_s[1]) + v3_uv_index = int(f3_s[1]) + uv_ind[v1_uv_index] = v1_index + uv_ind[v2_uv_index] = v2_index + uv_ind[v3_uv_index] = v3_index + f.index_uvs.append(v1_uv_index) + f.index_uvs.append(v2_uv_index) + f.index_uvs.append(v3_uv_index) + if len(f1_s) == len(f2_s) == len(f3_s) >= 3: + # face has vertex index and uv and normal + v1_normal_index = int(f1_s[2]) + v2_normal_index = int(f2_s[2]) + v3_normal_index = int(f3_s[2]) + f.index_normals.append(v1_normal_index) + f.index_normals.append(v2_normal_index) + f.index_normals.append(v3_normal_index) + current_object.faces.append(f) + if arr[0] == "o": + obj_name = arr[1].rstrip() + o = OBJObject() + o.name = obj_name + o.material = last_material + objects.append(o) + if len(current_object.faces) == 0: + objects.remove(current_object) + current_object = o + if arr[0] == "usemtl": + mtl_name = arr[1].rstrip() + print("mtl_name: %s" % mtl_name) + mtl = [mat for mat in materials if mat.name == mtl_name][0] + print("FOUND MTL: %s for name %s" % (mtl.texture, mtl_name)) + current_object.material = mtl + last_material = mtl + if arr[0] == "mtllib": + matlib_file = arr[1].rstrip() + print("Reading mtllib %s" % matlib_file) + materials = obj_read_materials("%s/%s" % (os.path.dirname(obj_file), matlib_file)) + + num_faces = sum([len(o.faces) for o in objects]) + print("%s vertices, %s uvs, %s normals, %s objects, %s materials, %s faces" % (len(vertices), len(uvs), len(normals), len(objects), len(materials), num_faces)) + # assert len(objects) >= len(materials) + assert len(vertices) < 0xffff + + len_vertices = len(vertices) + len_normals = len(normals) + + data = Packet() + data.put_ulong(GBS_VERSION) + options = 0 + if len(uvs) > 0: + options |= GBSFlagUVs + options |= GBSFlagCalcNormals + data.put_long(options) + data.put_long(len_vertices) + for v in vertices: + data.put_float(v.x) + data.put_float(v.y) + data.put_float(v.z) + + if options & GBSFlagNormals: + for v in normals: + data.put_float(v.x) + data.put_float(v.y) + data.put_float(v.z) + + data.put_ulong(len_normals) + data.put_ulong(len_normals) + for i in range(len_normals): + data.put_short(len_vertices + i) + + assert len_vertices <= len(uvs) + + indices = [] + for uv_in in sorted(uv_ind.keys()): + indices.append(uv_ind[uv_in]-1) + + nverts = max(len_vertices, len(uvs)) + assert nverts == len(indices) + + data.put_ulong(nverts) + for i in indices: + # indexed vert/UV + data.put_short(i) + + if options & GBSFlagUVs: + for v in uvs: + data.put_float(v.u) + data.put_float(v.v * -1) + + # max objects + data.put_long(1) # 1 big object + data.put_long(0) # vstart + data.put_long(len_vertices) # vcount + data.put_long(0) # nstart + data.put_long(0) # ncount + data.put_long(0) # noffset ??? + + # start write subobjects + data.put_long(len(objects)) + for obj in objects: + print("Writing object %s" % obj.name) + data.put_string_size(obj.name, 32) + data.put_long(0) # max obj index + data.put_long(len(obj.faces)) # totaltris + print("wrote %s totaltris" % len(obj.faces)) + data.put_long(3 * len(obj.faces) + 1) # ntris + print("wrote %s ntris" % (3 * len(obj.faces) + 1)) + + data.put_short(len(obj.faces)) + for face in obj.faces: # obj.faces.length == ntris + data.put_short(face.index_uvs[0] - 1) + data.put_short(face.index_uvs[1] - 1) + data.put_short(face.index_uvs[2] - 1) + + data.put_long(0) # verticeref_start + data.put_long(nverts) # verticeref_count + if options & GBSFlagUVs: + print("Writing texture to GBS: %s" % obj.material.texture) + data.put_string_size(obj.material.texture, 32) # texture + data.put_string_size(obj.material.texture, 32) # bump + + data.put_float(0) # falloff + if options & GBSFlagRGBs: + data.put_float(0) # blend + + data.put_long(0x40000000) # flags + data.put_long(0) # emissive + data.put_long(0) # ambient + data.put_long(0) # diffuse + data.put_long(0) # specular + data.put_float(0) # power + + # write gbs file + output = "%s/%s.gbs" % (os.path.dirname(os.path.abspath(obj_file)), os.path.basename(obj_file)) + with open(output, "wb") as gbs: + gbs.write(data.getvalue()) + + def __str__(self): + return "[name: %s, nverts:%s, num_vertices: %s, vertuv: %s, normals: %s, maxobjs: %s, subobjs:%s]" % (self.name, self.nverts, self.num_vertices, len(self.vertuv), self.num_normals, self.nmobjs, self.nsobjs) + + +def convert_obj(path): + GbsData.from_obj(path) + output = "%s/%s.gbs" % (os.path.dirname(os.path.abspath(path)), os.path.basename(path)) + print("Done! Output: %s" % output) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("path") + args = parser.parse_args() + if os.path.exists(args.path): + convert_obj(args.path) + else: + print("ERROR: file %s does not exist" % args.path)