feat(site): slice the ground into 20 m segments - and the slice hypothesis dies with them
The site exporter now splits sidewalks and kerbs into ~20 m pieces (SEGMENTED / SEGMENT_M in Art/blender/site/export_site_fbx.py), 56 slices in all, so each has a bounding box that means something where it stands instead of reporting the highest point on a 279 m slope. They import and stage (63 site meshes in the demo map). The hypothesis behind it is dead, and that is the useful part: with the invisible sidewalk stopgap retired, the player now falls to Road_Hollywood at 366 - the slices do not block either. So the 279 m aspect ratio was never the cause; a 20 m slice of the same mesh fails identically. The sidewalk geometry itself is what does not collide, and the next place to look is its shape - an open surface with no volume, inverted winding or zero-thickness triangles - not the import flags and not the size. The stopgap retirement stands: the player walks on the road, which is real geometry that does collide, rather than on an invisible ledge.
This commit is contained in:
1 parent
1ee2a35d53
commit
1916321761
86 files changed
+275
-57
No files matched your search
@@ -22,6 +22,50 @@ import fbx_export_preset as fbx_preset # noqa: E402
|
||||
MESHES = ("Terrain_DEM", "Road_Hollywood", "Sidewalk_South", "Sidewalk_North",
|
||||
"Kerb_South", "Kerb_North", "Crossing_Markings")
|
||||
|
||||
# Ground surfaces are exported in slices. One 279 m slab cannot report an honest height:
|
||||
# its bounding box is the highest point anywhere along the street, which is 2.2 m above the
|
||||
# surface at the player's position, and that misled every placement decision in the demo.
|
||||
# A 20 m slice has a bounding box that means something where it stands.
|
||||
SEGMENTED = ("Sidewalk_North", "Sidewalk_South", "Kerb_North", "Kerb_South")
|
||||
SEGMENT_M = 20.0
|
||||
|
||||
|
||||
def _segment(obj, name, out_dir, length_m):
|
||||
"""Export `obj` as slices of `length_m` along X, each with its own honest bbox."""
|
||||
xs = [(obj.matrix_world @ v.co).x for v in obj.data.vertices]
|
||||
x0, x1 = min(xs), max(xs)
|
||||
count = max(1, int(round((x1 - x0) / length_m)))
|
||||
step = (x1 - x0) / count
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
for index in range(count):
|
||||
a = x0 + index * step
|
||||
b = a + step
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.duplicate()
|
||||
piece = bpy.context.active_object
|
||||
bpy.context.view_layer.objects.active = piece
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.select_all(action="SELECT")
|
||||
# Cut at both boundaries and keep only the middle: faces spanning a boundary are
|
||||
# split cleanly rather than dropped, which a face-centre filter would do.
|
||||
bpy.ops.mesh.bisect(plane_co=(a, 0.0, 0.0), plane_no=(1.0, 0.0, 0.0),
|
||||
clear_inner=True)
|
||||
bpy.ops.mesh.bisect(plane_co=(b, 0.0, 0.0), plane_no=(1.0, 0.0, 0.0),
|
||||
clear_outer=True)
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
piece.name = f"{name}_{index:02d}"
|
||||
zs = [(piece.matrix_world @ v.co).z for v in piece.data.vertices]
|
||||
path = os.path.join(out_dir, f"{piece.name}.fbx")
|
||||
fbx_preset.export_static(path, selection=True)
|
||||
print(f"[site] {piece.name:22} x {a:+.1f}..{b:+.1f} m "
|
||||
f"z {min(zs):+.2f}..{max(zs):+.2f} m -> {path}")
|
||||
bpy.data.objects.remove(piece, do_unlink=True)
|
||||
# The context loses its active object when the duplicate goes; restore the source so
|
||||
# the next iteration's operators have something to act on.
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
|
||||
def main():
|
||||
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
@@ -62,6 +106,9 @@ def main():
|
||||
me.update()
|
||||
_ = bpy.context.view_layer
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
if name in SEGMENTED:
|
||||
_segment(obj, name, args.out, SEGMENT_M)
|
||||
continue
|
||||
path = os.path.join(args.out, name + ".fbx")
|
||||
fbx_preset.export_static(path, selection=True)
|
||||
if mirrored:
|
||||
|
||||
Reference in new issue
Block a user