Skip to content

Commit

Permalink
(#158) more Genesis3D structs
Browse files Browse the repository at this point in the history
  • Loading branch information
snake-biscuits committed Oct 29, 2023
1 parent 3a4fc41 commit 6bae004
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 10 deletions.
1 change: 1 addition & 0 deletions bsp_tool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def load_bsp(filename: str, branch_script: ModuleType = None) -> base.Bsp:
assert int.from_bytes(bsp_file.read(4), "little") == 0x01
file_magic = bsp_file.read(4)
bsp_file.seek(4, 1) # skip 4 trailing null bytes
version = int.from_bytes(bsp_file.read(4), "little")
except AssertionError:
raise RuntimeError("bsp file begins with null bytes")
# endianness
Expand Down
24 changes: 24 additions & 0 deletions bsp_tool/branches/colour.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import List

from . import base


class Rgb24(base.MappedArray):
_mapping = [*"rgb"]
_format = "3B"

def as_floats(self) -> List[float]:
return [getattr(self, x) / 255 for x in self._mapping]


class Rgb32(Rgb24):
_mapping = [*"rgba"]
_format = "4B"


class RgbExponent(Rgb32):
_mapping = [*"rgb", "exponent"]

def as_floats(self) -> List[float]:
"""HDR scaled values"""
return [getattr(self, x) / 255 * self.exponent for x in (*"rgb")]
8 changes: 7 additions & 1 deletion bsp_tool/branches/id_software/quake.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ def __repr__(self):
return str(int(self))


class PlaneSide(enum.Enum):
FRONT = 0
BACK = 1


class PlaneType(enum.Enum):
# Axial, perfectly aligned
X = 0
Expand Down Expand Up @@ -157,7 +162,7 @@ def from_tuple(cls, _tuple):

class Face(base.Struct): # LUMP 7
plane: int # signed for quake, unsigned for quake 2
side: int # 0 or 1 for side of plane
side: PlaneSide
first_edge: int
num_edges: int
texture_info: int # index of this face's TextureInfo
Expand All @@ -170,6 +175,7 @@ class Face(base.Struct): # LUMP 7
"lighting_type", "base_light", "light", "lighting_offset"]
_format = "2HI2H4Bi"
_arrays = {"light": 2}
_classes = {"side": PlaneSide}
# TODO: FaceLightingType(enum.IntFlag)


Expand Down
21 changes: 20 additions & 1 deletion bsp_tool/branches/id_software/quake2.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class LUMP(enum.Enum):
# \--> TextureInfo
# \-> Lightmap

# Area -> AreaPortal -> Area
# \-> Portal

# POP appears to only be used in Deathmatch maps & is always 256 bytes, cannot find use in source code
# POP seems to be the last lump written and is always null bytes in every map which has this lump

Expand Down Expand Up @@ -148,6 +151,20 @@ class Surface(enum.IntFlag): # qcommon/qfiles.h


# classes for lumps, in alphabetical order:
class Area(base.Struct): # LUMP 17
num_area_portals: int
first_area_portal: int
__slots__ = ["num_area_portals", "first_area_portal"]
_format = "2i"


class AreaPortal(base.Struct): # LUMP 18
portal: int
area: int # Area this AreaPortal connects to
__slots__ = ["portal", "area"]
_format = "2i"


class Brush(base.MappedArray): # LUMP 14
first_brush_side: int # first BrushSide of this Brush
num_brush_sides: int # number of BrushSides after first_brush_side on this Brush
Expand Down Expand Up @@ -305,7 +322,9 @@ def as_bytes(self) -> bytes:
BASIC_LUMP_CLASSES = {"LEAF_FACES": shared.Shorts,
"SURFEDGES": shared.Ints}

LUMP_CLASSES = {"BRUSHES": Brush,
LUMP_CLASSES = {"AREAS": Area,
"AREA_PORTALS": AreaPortal,
"BRUSHES": Brush,
"BRUSH_SIDES": BrushSide,
"EDGES": quake.Edge,
"FACES": quake.Face,
Expand Down
172 changes: 164 additions & 8 deletions bsp_tool/branches/wild_tangent/genesis3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
from typing import List

from .. import base
from .. import shared
from .. import time
from .. import vector
from ..id_software import quake
from ..id_software import quake2
from ..id_software import remake_quake
from ..id_software import remake_quake_old


FILE_MAGIC = b"GBSP"
Expand All @@ -22,7 +26,7 @@ class LUMP(enum.Enum):
HEADER = 0
MODELS = 1
NODES = 2
BNODES = 3
CLIP_NODES = 3 # BNODES
LEAVES = 4
CLUSTERS = 5
AREAS = 6
Expand All @@ -38,7 +42,7 @@ class LUMP(enum.Enum):
ENTITIES = 16
TEXTURE_INFO = 17
TEXTURES = 18
TEXTURE_DATA = 19
TEXELS = 19 # TEXTURE_DATA
LIGHTING = 20
VISIBILITY = 21
SKY = 22
Expand All @@ -61,19 +65,123 @@ class LumpHeader(base.MappedArray):
# TODO: a rough map of the relationships between lumps:


# flag enums:
class Contents(enum.IntFlag):
# NOTE: BSP_CONTENTS, separate from GE_CONTENTS
SOLID = -1
EMPTY = -2

def __repr__(self):
if self < 0:
return super().__repr__()
else:
return str(int(self))


# classes for lumps, in alphabetical order:
class AreaPortal(base.Struct): # LUMP 7
model: int
area: int
__slots__ = ["model", "area"]
_format = "2i"


class ClipNode(remake_quake_old.ClipNode): # LUMP 3
__slots__ = ["children", "plane"]


class Face(base.Struct): # LUMP 11
first_vertex: int
num_vertices: int
plane: int
side: quake.PlaneSide
texture_info: int
lighting: List[int]
# lighting.offset: int # -1 for unlit
# lighting.size: vector.vec2(width, height)
# lighting.type: List[int] # styles?
__slots__ = ["first_vertex", "num_vertices", "plane", "side", "texture_info", "lighting"]
_format = "8i4B"
_arrays = {"lighting": {"offset": None, "size": ["width", "height"], "types": 4}}
_classes = {"side": quake.PlaneSide}


class Header(base.Struct): # LUMP 0
magic: bytes
padding: bytes # 4 NULL bytes
version: int
timestamp: List[int]
__slots__ = ["magic", "padding", "version", "time"]
_format = "4s4sI8H"
_format = "4s4si8H"
_arrays = {"time": ["year", "month", "day_of_week", "day",
"hour", "minute", "second", "millisecond"]}
_classes = {"time": time.SystemTime}


class Leaf(base.Struct): # LUMP 4
contents: int
bounds: List[vector.vec3]
# bounds.mins: vector.vec3
# bounds.maxs: vector.vec3
first_leaf_face: int
num_leaf_faces: int
first_portal: int
num_portals: int
cluster: int # index into Cluster lump
area: int # -1: Area, 0: Solid, *: Area Index
first_leaf_side: int
num_leaf_sides: int
__slots__ = ["contents", "bounds", "first_leaf_face", "num_leaf_faces", "first_portal",
"num_portals", "cluster", "area", "first_leaf_side", "num_leaf_sides"]
_format = "i6f8i"
_arrays = {"bounds": {"mins": [*"xyz"], "maxs": [*"xyz"]}}
_classes = {"contents": Contents, "bounds.mins": vector.vec3, "bounds.maxs": vector.vec3}


class LeafSide(base.Struct): # LUMP 8
"""bevelled sides for BBox collisions"""
plane: int # index into Planes
side: int # 0/1 front/back?
__slots__ = ["plane", "side"]
_format = "2i"


class Model(base.Struct): # LUMP 1
node: int # top level Node
clip_node: int # top level ClipNode
bounds: List[vector.vec3]
origin: vector.vec3
first_face: int
num_faces: int
first_leaf: int
num_leaves: int
first_cluster: int
num_clusters: int
areas: List[int]
motion: int
__slots__ = ["node", "clip_node", "bounds", "origin", "first_face", "num_faces",
"first_leaf", "num_leaves", "first_cluster", "num_clusters", "areas", "motion"]
_format = "2i9f9i"
_arrays = {"bounds": {"mins": [*"xyz"], "maxs": [*"xyz"]}, "origin": [*"xyz"], "areas": 2}
_classes = {"bounds.mins": vector.vec3, "bounds.maxs": vector.vec3, "origin": vector.vec3}


class Node(remake_quake.Node): # LUMP 2
__slots__ = ["children", "num_faces", "first_face", "plane", "bounds"]
_format = "5I6f"


# TODO: Palette (256x colour.Colour24)


class Portal(base.Struct): # LUMP 9
origin: vector.vec3
leaf: int # Leaf this Portal "looks into"
__slots__ = ["origin", "leaf"]
_arrays = {"origin": [*"xyz"]}
_classes = {"origin": vector.vec3}


class Sky(base.Struct): # LUMP 22
axis: vector.vec3 # axis of rotation
degrees_per_minute: float
Expand All @@ -85,13 +193,61 @@ class Sky(base.Struct): # LUMP 22
_classes = {"axis": vector.vec3}


# {"LUMP_NAME": LumpClass}
BASIC_LUMP_CLASSES = dict()
class Texture(base.Struct): # LUMP 18
name: str
flags: int
size: List[int]
first_texel: int
# num_texels = size.width * size.height
palette: int
_format = "32sI4i"
__slots__ = ["name", "flags", "size", "first_texel", "palette"]
_arrays = {"size": ["width", "height"]}
# TODO: _classes = {"flags": TextureFlags, "size": ivec2}


class TextureInfo(base.Struct): # LUMP 17
projection: List[List[vector.vec3 | float]]
# projections.vectors: List[vector.vec3]
# projections.offsets: List[float]
# projections.sizes: List[float]
flags: int
face_light: float # for radiosity calculations; temp compile variable?
reflectiveness: float # "scale"
alpha: float
mip_map_bias: float # ???
texture: int
__slots__ = ["projection", "flags", "face_light", "reflectiveness",
"alpha", "mip_map_bias", "texture"]
_format = "10fi4fi"
_arrays = {"projection": {"vectors": {"s": [*"xyz"], "t": [*"xyz"]},
"offsets": [*"st"], "sizes": [*"st"]}}
# TODO: _classes = {"flags": TextureInfoFlags}

LUMP_CLASSES = {"PLANES": quake.Plane,
"SKY": Sky}

SPECIAL_LUMP_CLASSES = {"HEADER": Header}
# {"LUMP_NAME": LumpClass}
BASIC_LUMP_CLASSES = {"CLUSTERS": shared.Ints,
"INDICES": shared.Ints,
"LEAF_FACES": shared.Ints,
"TEXELS": shared.Bytes}

LUMP_CLASSES = {"AREAS": quake2.Area,
"AREA_PORTALS": AreaPortal,
"CLIP_NODES": ClipNode,
"FACES": Face,
"LEAVES": Leaf,
"LEAF_SIDES": LeafSide,
"MODELS": Model,
"NODES": Node,
"PLANES": quake.Plane,
"PORTALS": Portal,
"RGB_VERTICES": quake.Vertex, # colour values parallel w/ indices?
"TEXTURES": Texture,
"TEXTURE_INFO": TextureInfo,
"VERTICES": quake.Vertex}

SPECIAL_LUMP_CLASSES = {"HEADER": Header,
"SKY": Sky}


methods = dict()

0 comments on commit 6bae004

Please sign in to comment.