Refresh generated terrain assets and projection
This commit is contained in:
parent
3608be41ab
commit
05838406d0
53 changed files with 319 additions and 51 deletions
|
|
@ -10,10 +10,11 @@ Run through Blender rather than a standalone Python interpreter:
|
|||
Collections use ``chunk_####_description`` and contain one primary terrain
|
||||
mesh named ``chunk_####``. Additional production objects may live in the same
|
||||
collection. Reusable procedural props use individual ``prop_description``
|
||||
mesh objects. They may be arranged anywhere in the source file because each
|
||||
object's authored origin becomes the exported runtime anchor. A same-named
|
||||
``prop_description`` collection remains supported for multi-object props.
|
||||
Unrelated objects and collections are ignored.
|
||||
mesh objects. Their child empties are exported with them so authored sockets
|
||||
remain attached to the prop. Props may be arranged anywhere in the source file
|
||||
because each root object's authored origin becomes the exported runtime anchor.
|
||||
A same-named ``prop_description`` collection remains supported for multi-object
|
||||
props. Unrelated objects and collections are ignored.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -43,6 +44,12 @@ PROP_COLLECTION_PATTERN = re.compile(
|
|||
r"^prop_(?P<label>[a-z0-9]+(?:_[a-z0-9]+)*)$"
|
||||
)
|
||||
ALLOWED_OBJECT_TYPES = {"EMPTY", "MESH"}
|
||||
UPWARD_SURFACE_MATERIALS = {
|
||||
"dirt",
|
||||
"grass_lite",
|
||||
"sand",
|
||||
"water_reference",
|
||||
}
|
||||
|
||||
|
||||
class ExportValidationError(RuntimeError):
|
||||
|
|
@ -167,6 +174,33 @@ def _validate_transform(
|
|||
return problems
|
||||
|
||||
|
||||
def _validate_upward_surface_normals(
|
||||
mesh_object: bpy.types.Object,
|
||||
) -> list[str]:
|
||||
"""Reject exposed terrain materials whose faces point into the ground."""
|
||||
downward_counts: dict[str, int] = {}
|
||||
materials = mesh_object.data.materials
|
||||
for polygon in mesh_object.data.polygons:
|
||||
if polygon.normal.z >= -0.25 or polygon.material_index >= len(materials):
|
||||
continue
|
||||
material = materials[polygon.material_index]
|
||||
if material is None:
|
||||
continue
|
||||
material_name = re.sub(r"\.\d{3}$", "", material.name)
|
||||
if material_name not in UPWARD_SURFACE_MATERIALS:
|
||||
continue
|
||||
downward_counts[material_name] = (
|
||||
downward_counts.get(material_name, 0) + 1
|
||||
)
|
||||
return [
|
||||
(
|
||||
f"{count} {material_name} face(s) point downward; "
|
||||
"terrain surface normals must face up"
|
||||
)
|
||||
for material_name, count in sorted(downward_counts.items())
|
||||
]
|
||||
|
||||
|
||||
def _validate_bounds(
|
||||
bounds: Bounds,
|
||||
tolerance: float,
|
||||
|
|
@ -318,6 +352,10 @@ def _discover_chunks(tolerance: float) -> list[ChunkSource]:
|
|||
errors.append(
|
||||
f"{collection.name}/{primary_mesh.name}: mesh has no material"
|
||||
)
|
||||
errors.extend(
|
||||
f"{collection.name}/{primary_mesh.name}: {problem}"
|
||||
for problem in _validate_upward_surface_normals(primary_mesh)
|
||||
)
|
||||
|
||||
chunks.append(
|
||||
ChunkSource(
|
||||
|
|
@ -522,20 +560,38 @@ def _discover_props(tolerance: float) -> list[PropSource]:
|
|||
if not collections:
|
||||
errors.append(f"{item.name}: prop object belongs to no collection")
|
||||
continue
|
||||
objects = tuple(
|
||||
sorted(
|
||||
(item, *item.children_recursive),
|
||||
key=lambda value: value.name,
|
||||
)
|
||||
)
|
||||
overlapping_sources = {
|
||||
claimed_objects[descendant.as_pointer()]
|
||||
for descendant in objects
|
||||
if descendant.as_pointer() in claimed_objects
|
||||
}
|
||||
if overlapping_sources:
|
||||
errors.append(
|
||||
f"{item.name}: child object is already exported by "
|
||||
f"{', '.join(sorted(overlapping_sources))}"
|
||||
)
|
||||
continue
|
||||
prop, source_errors = _make_prop_source(
|
||||
stable_id,
|
||||
match.group("label"),
|
||||
"object",
|
||||
item.name,
|
||||
collections[0],
|
||||
(item,),
|
||||
objects,
|
||||
tolerance,
|
||||
)
|
||||
errors.extend(source_errors)
|
||||
if prop is not None:
|
||||
props.append(prop)
|
||||
claimed_ids[stable_id] = item.name
|
||||
claimed_objects[item.as_pointer()] = item.name
|
||||
for descendant in objects:
|
||||
claimed_objects[descendant.as_pointer()] = item.name
|
||||
|
||||
if errors:
|
||||
raise ExportValidationError("\n".join(errors))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue