Skip to content

added two files. #2

Open
AlleyKatPr0 wants to merge 5 commits into
rfsheffer:masterfrom
AlleyKatPr0:master
Open

added two files. #2
AlleyKatPr0 wants to merge 5 commits into
rfsheffer:masterfrom
AlleyKatPr0:master

Conversation

@AlleyKatPr0
Copy link
Copy Markdown

a Quake2 MAP converter for parsing a MAP file to a T3D format, and, an HTML t3D viewer to check results

Copilot AI review requested due to automatic review settings January 29, 2026 09:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a Quake 2 MAP to Unreal Engine 5 T3D converter script. The converter parses Quake 2 .MAP files and transforms them into .T3D format for import into Unreal Engine 5, including geometry brushes, lights, monsters, triggers, and other game entities.

Changes:

  • Added QUAKE2_MAP_2_T3D.py: A comprehensive Python script (1,235 lines) that converts Quake 2 MAP files to UE5 T3D format
  • The script includes vector mathematics, brush/face parsing, polygon triangulation, and T3D file generation
  • Supports conversion of world geometry, lights with color properties, entities (monsters, items, player starts), and triggers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +64 to +68
def normalize(self):
length = self.length()
if length > 0.0001:
return self / length
return Vector3(0, 0, 1)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalize method returns a default vector (0, 0, 1) when the length is too small, which is reasonable for degenerate cases. However, this behavior should be documented in a docstring since it's a non-obvious design choice that callers need to be aware of.

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
f.write(f" Begin Object Name=\"Sprite\"\n")
f.write(f" AttachParent=\"SceneComp\"\n")
f.write(f" End Object\n")
f.write(f" Text=\"{properties_text}\"\n")
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The properties_text value (which comes from entity.properties_to_string()) is directly embedded in the Text attribute without proper escaping. If entity properties contain special characters like quotes or backslashes, this could break the T3D file format or lead to injection issues. Consider escaping the text appropriately for the T3D format.

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +729 to +741
'player_starts': 0
}

def write(self, entities: List[Entity]):
with open(self.output_path, 'w', encoding='utf-8') as f:
self._write_header(f)

for entity in entities:
try:
self._write_entity(f, entity)
except Exception as e:
logger.error(f"Error writing entity {entity.get_classname()}: {e}")

Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an exception occurs during _write_entity, the error is logged but the conversion continues. This could result in an incomplete or invalid T3D file being written. Consider whether critical entity write failures should abort the conversion, or at minimum, track failed entities and report them in the final statistics.

Suggested change
'player_starts': 0
}
def write(self, entities: List[Entity]):
with open(self.output_path, 'w', encoding='utf-8') as f:
self._write_header(f)
for entity in entities:
try:
self._write_entity(f, entity)
except Exception as e:
logger.error(f"Error writing entity {entity.get_classname()}: {e}")
'player_starts': 0,
'failed_entities': 0,
}
def write(self, entities: List[Entity]):
with open(self.output_path, 'w', encoding='utf-8') as f:
self._write_header(f)
failed_entities = []
for entity in entities:
try:
self._write_entity(f, entity)
except Exception as e:
logger.error(f"Error writing entity {entity.get_classname()}: {e}")
self.stats['failed_entities'] += 1
try:
failed_entities.append(entity.get_classname())
except Exception:
failed_entities.append('<unknown>')
if failed_entities:
logger.warning(
"Completed T3D conversion with %d failed entities: %s",
self.stats['failed_entities'],
", ".join(failed_entities)
)

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
lines.append("")
lines.append(f"Brushes: {len(self.brushes)}")

return "\\n".join(lines)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The escape sequence "\n" in the join creates a literal backslash followed by 'n' instead of a newline character. This should be "\n" (with a single backslash) to create actual newlines.

Suggested change
return "\\n".join(lines)
return "\n".join(lines)

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +764 to +770
def _write_entity(self, f, entity: Entity):
classname = entity.get_classname()

if entity.is_light():
self._write_light_actor(f, entity)
self.stats['lights'] += 1
if '_color' in entity.properties:
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stats dictionary tracks 'lights_with_color' by checking for '_color' in entity.properties, but the get_light_properties method also accepts color from the 'light' value itself (with hex or RGB values). This means the statistic may not accurately reflect all lights that have color information.

Suggested change
def _write_entity(self, f, entity: Entity):
classname = entity.get_classname()
if entity.is_light():
self._write_light_actor(f, entity)
self.stats['lights'] += 1
if '_color' in entity.properties:
def _entity_has_color(self, entity: Entity) -> bool:
"""
Determine whether the given entity has color information.
A light can specify color either via a dedicated '_color' property
or encoded in the 'light' value itself (hex or RGB formats).
This method mirrors that behavior for statistics purposes.
"""
# Explicit color property
if '_color' in entity.properties:
return True
# Color encoded in the 'light' property
light_value = entity.properties.get('light')
if not light_value:
return False
value = str(light_value).strip()
if not value:
return False
# Hex color: RRGGBB or #RRGGBB
if re.fullmatch(r'#?[0-9A-Fa-f]{6}', value):
return True
# RGB triple: three numeric components separated by whitespace or commas
parts = re.split(r'[\s,]+', value)
if len(parts) != 3:
return False
try:
components = [float(p) for p in parts]
except ValueError:
return False
# Accept either normalized [0,1] or 0–255 ranges
if all(0.0 <= c <= 1.0 for c in components):
return True
if all(0.0 <= c <= 255.0 for c in components):
return True
return False
def _write_entity(self, f, entity: Entity):
classname = entity.get_classname()
if entity.is_light():
self._write_light_actor(f, entity)
self.stats['lights'] += 1
if self._entity_has_color(entity):

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
texture = match.group(10)

return Face(p1, p2, p3, texture)
except:
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare except clause catches all exceptions including KeyboardInterrupt and SystemExit, which should not be caught. Use a more specific exception type like ValueError or Exception.

Suggested change
except:
except Exception:

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +76 to +87
def to_unreal(self, grid_snap: float = 2.54):
scale = 2.54
x = self.x * scale
y = -self.y * scale
z = self.z * scale

if grid_snap > 0:
x = self.snap_to_grid(x, grid_snap)
y = self.snap_to_grid(y, grid_snap)
z = self.snap_to_grid(z, grid_snap)

return Vector3(x, y, z)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grid_snap parameter is used in the to_unreal method but the variable 'scale' is hardcoded to 2.54 on line 77, making the grid_snap parameter value meaningless for the scaling calculation. Consider whether scale should use the grid_snap parameter value or if these should be separate concerns.

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +1093 to +1095
if (self.triangulator._vec_equal(tri[0], tri[1]) or
self.triangulator._vec_equal(tri[1], tri[2]) or
self.triangulator._vec_equal(tri[0], tri[2])):
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing internal/private method _vec_equal from outside the class violates encapsulation. Consider making this method public by removing the underscore prefix, or providing a public method to check vertex equality.

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +46 to +49
def __truediv__(self, scalar):
if abs(scalar) < 0.0001:
return Vector3(0, 0, 0)
return Vector3(self.x / scalar, self.y / scalar, self.z / scalar)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Division by zero protection checks for values less than 0.0001, but this threshold might be too small for floating-point precision issues. Consider using a named constant (e.g., EPSILON) consistently throughout the code for such comparisons, or use a more robust comparison approach.

Copilot uses AI. Check for mistakes.
Comment thread QUAKE2_MAP_2_T3D.py
Comment on lines +161 to +162
except (ValueError, IndexError):
pass
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except (ValueError, IndexError):
pass
except (ValueError, IndexError) as e:
logger.warning(f"Failed to parse RGB light components from '{light_value}': {e}")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants