fix(hero): export smoothing groups; give the facade real collision
UE export hygiene (unreal-export skill checklist) plus a gameplay defect the new probe caught: - FBX export now uses mesh_smooth_type="FACE" (was OFF, which is why every import logged "No smoothing group information was found"), and the round parts are shaded with shade_smooth_by_angle(35 deg) before export, so columns and medallions read round instead of faceted. - SM_HeroFacade had complex-only collision, so capsule sweeps and line traces passed through the wall: the player could walk through the hero facade. auto_generate_collision was tried and rejected (coarse hulls bulged into the street; the QA wall probe went 0/35 -> 2/35). It now uses UseComplexAsSimple, saved on the asset, like the terrain. - New -qaselftest facade occlusion probe: 40 m traces sideways from the road centre at walking height, required only where a building is staged (explicit X spans mirroring ue_build_demo_map). Result 0/9 -> PASS, alongside transect 0 holes and wall probe 0/35. Docs: release-checks records the probe and both collision attempts; Tools/README lists the new phase; MEMORY records the smoothing/collision lessons.
This commit is contained in:
1 parent
70c6e322e2
commit
160e9144f9
32 files changed
+166
-45
No files matched your search
Binary file not shown.
@@ -95,6 +95,15 @@ Run on the packaged build (`GigLineQA`, Development, Vulkan SM5) with `-qaselfte
|
||||
- **Invisible-wall probe:** horizontal traces 30 m long, grounded at 160 cm above
|
||||
the local ground, along the road and both seam rows. Result **0/35 blocking hits
|
||||
inside 21 m** — no invisible walls or misplaced blockers in the street envelope.
|
||||
- **Facade occlusion probe:** from the road centre, 40 m traces sideways at walking
|
||||
height toward each frontage, required only where the map stages a building (the
|
||||
spans are listed in `AGigLineCharacter` and mirror `Tools/ue_build_demo_map.py`).
|
||||
Result **0/9 directions without a blocking wall** — the player cannot walk
|
||||
through the hero or the frontage. This probe found the hero façade had **no
|
||||
simple collision** at all (complex-only, so traces and capsule sweeps passed
|
||||
through it); it is now `UseComplexAsSimple`. Auto-generated convex hulls were
|
||||
tried first and rejected: on a 95-part, 26 m façade they bulged into the street
|
||||
(the wall probe went 0/35 → 2/35).
|
||||
(First pass used a fixed Z 160 start; the street is at Z ~366, so the probe
|
||||
started inside the earth and reported every row as a dist-0 blocker. Grounding
|
||||
the probe fixed that measurement bug.)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -416,6 +416,81 @@ void AGigLineCharacter::StartQASelfTest()
|
||||
TEXT("GigLine QA: wall probe %d/%d traces with blocking hit inside 21 m -> %s"),
|
||||
Walls, WallSamples,
|
||||
Walls == 0 ? TEXT("PASS") : TEXT("FAIL"));
|
||||
|
||||
// Facade occlusion probe: walkable street is only half the story —
|
||||
// the player must also NOT be able to walk through the buildings.
|
||||
// From the road centre, fire 40 m sideways at walking height toward
|
||||
// each frontage and require a blocking hit. A miss means the
|
||||
// building has no collision (or only non-blocking collision) and the
|
||||
// player would pass through the wall.
|
||||
// Expect a wall only where the map actually stages a building.
|
||||
// Spans below mirror the actors placed by Tools/ue_build_demo_map.py
|
||||
// (frontage + hero) and the two north buildings; update them if
|
||||
// that list changes. Gaps between buildings are legitimate misses.
|
||||
struct FSpan { float Min, Max; };
|
||||
static const FSpan SouthSpans[] = {
|
||||
{-24276.f, -17700.f}, // 6922
|
||||
{-17749.f, -14591.f}, // 6902
|
||||
{-14638.f, -12034.f}, // 6840 hero
|
||||
{-12082.f, -8210.f}, // 6838 El Capitan Theatre
|
||||
{-7854.f, -7252.f}, // 6822 Budget
|
||||
{-6608.f, -3307.f}, // 6806-6820
|
||||
{-3320.f, -1437.f}, // 6800 Lee Drug
|
||||
};
|
||||
static const FSpan NorthSpans[] = {
|
||||
{-27071.f, -22799.f}, // Madame Tussaud's
|
||||
{-22889.f, -18442.f}, // TCL Chinese Theatre
|
||||
};
|
||||
auto InSpans = [](const FSpan* Spans, int32 Count, float X)
|
||||
{
|
||||
for (int32 i = 0; i < Count; ++i)
|
||||
{
|
||||
if (X >= Spans[i].Min && X <= Spans[i].Max) { return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
int32 Facades = 0, FacadeSamples = 0;
|
||||
for (float X0 = -19000.0f; X0 <= -3000.0f; X0 += 2000.0f)
|
||||
{
|
||||
for (float Dir : {1.0f, -1.0f}) // +Y south frontage, -Y north side
|
||||
{
|
||||
const bool bExpectWall = (Dir > 0.0f)
|
||||
? InSpans(SouthSpans, UE_ARRAY_COUNT(SouthSpans), X0)
|
||||
: InSpans(NorthSpans, UE_ARRAY_COUNT(NorthSpans), X0);
|
||||
if (!bExpectWall)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FHitResult GroundHit2;
|
||||
FCollisionQueryParams GQ2(TEXT("GigLineFacadeGround"), false, this);
|
||||
if (!W->LineTraceSingleByChannel(GroundHit2,
|
||||
FVector(X0, 0.0f, 3000.f), FVector(X0, 0.0f, -1000.f),
|
||||
ECC_Visibility, GQ2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float ProbeZ2 = GroundHit2.ImpactPoint.Z + 160.f;
|
||||
FHitResult Hit2;
|
||||
FCollisionQueryParams Q2(TEXT("GigLineFacadeProbe"), false, this);
|
||||
const bool bHit2 = W->LineTraceSingleByChannel(Hit2,
|
||||
FVector(X0, 0.0f, ProbeZ2),
|
||||
FVector(X0, Dir * 4000.f, ProbeZ2),
|
||||
ECC_Visibility, Q2);
|
||||
++FacadeSamples;
|
||||
const bool bBlocking = bHit2 && Hit2.GetActor() != nullptr;
|
||||
if (!bBlocking)
|
||||
{
|
||||
++Facades;
|
||||
UE_LOG(LogTemp, Log,
|
||||
TEXT("GigLine QA: facade probe miss at X %.0f dir %.0f (no blocking hit in 40 m)"),
|
||||
X0, Dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
UE_LOG(LogTemp, Log,
|
||||
TEXT("GigLine QA: facade probe %d/%d directions without a blocking wall -> %s"),
|
||||
Facades, FacadeSamples,
|
||||
Facades == 0 ? TEXT("PASS") : TEXT("FAIL"));
|
||||
}
|
||||
|
||||
// 7.0: verdict, then stop ticking.
|
||||
|
||||
@@ -440,3 +440,23 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
||||
`review-hero-facade-front`.
|
||||
- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails
|
||||
with "Cannot save blend file … not writable" (this bit again here).
|
||||
|
||||
## 2026-11-09 — UE export hygiene: smoothing groups + facade collision
|
||||
|
||||
- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal
|
||||
import logged "No smoothing group information was found for this mesh". The hero
|
||||
now exports `"FACE"` and its round parts are shaded with
|
||||
`shade_smooth_by_angle(35°)` before export: cylinders read as round instead of
|
||||
faceted, and the import warning is gone (6 → 0).
|
||||
- **The hero façade had no simple collision** (complex-only), so line traces and
|
||||
capsule sweeps passed straight through it — the player could walk through the
|
||||
wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m
|
||||
sideways traces from the road centre at walking height).
|
||||
- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it
|
||||
produced coarse hulls that bulged into the street and the wall probe went
|
||||
0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via
|
||||
`UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain
|
||||
uses it), saved on the asset.
|
||||
- Probe expectations must mirror the staged buildings: the facade probe now carries
|
||||
explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so
|
||||
a gap between buildings is not reported as a defect.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -57,7 +57,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
||||
| Command | Purpose |
|
||||
| --- | --- |
|
||||
| `UnrealEditor <uproject> /Game/Hollywood/DemoMap -game -qaselftest` | Headless behaviour check: drives forward input for 2 s, then resets and pauses. Logs `GigLine QA: ... PASS/FAIL` lines and a final `RESULT`. Runs only with `-qaselftest`, so normal play is unaffected. |
|
||||
| `-qaselftest` phases | Walk, reset, pause/unpause, a 16-row seam transect (0 holes) and a ground-referenced wall probe (0/35), plus a frame-time capture writing `Saved/Profiling/perf-walk-frametimes.csv` and logging `GigLine PERF:`. |
|
||||
| `-qaselftest` phases | Walk, reset, pause/unpause, a 16-row seam transect (0 holes), a ground-referenced wall probe (0/35) and a **facade occlusion probe** that fires sideways from the road centre and requires a blocking wall wherever a building is staged (0/9), plus a frame-time capture writing `Saved/Profiling/perf-walk-frametimes.csv` and logging `GigLine PERF:`. |
|
||||
| `gigline.SetupLogs` cvar | Spawn/input setup logging, **off by default** (`DefaultEngine.ini [ConsoleVariables]`); enable at runtime with `gigline.SetupLogs 1`. |
|
||||
|
||||
## Reference and concept generation
|
||||
|
||||
@@ -74,7 +74,7 @@ def cube(name, x, y, z, dx, dy, dz, material=None):
|
||||
return obj
|
||||
|
||||
|
||||
def cylinder(name, x, y, z0, z1, radius, material, vertices=24):
|
||||
def cylinder(name, x, y, z0, z1, radius, material, vertices=24, smooth=True):
|
||||
bpy.ops.mesh.primitive_cylinder_add(
|
||||
vertices=vertices,
|
||||
radius=radius,
|
||||
@@ -85,6 +85,11 @@ def cylinder(name, x, y, z0, z1, radius, material, vertices=24):
|
||||
obj.name = name
|
||||
if material:
|
||||
obj.data.materials.append(material)
|
||||
if smooth:
|
||||
# Round parts must shade smoothly *around* the axis while keeping the
|
||||
# caps creased. Without this the columns read as faceted prisms, and the
|
||||
# FBX carries no smoothing groups at all (Unreal warns about it).
|
||||
bpy.ops.object.shade_smooth_by_angle(angle=math.radians(35.0))
|
||||
return obj
|
||||
|
||||
|
||||
@@ -313,7 +318,9 @@ def export_fbx(path):
|
||||
axis_up="Z",
|
||||
use_space_transform=True,
|
||||
use_mesh_modifiers=True,
|
||||
mesh_smooth_type="OFF",
|
||||
# "FACE" exports smoothing groups; without it every import logs
|
||||
# "No smoothing group information was found for this mesh".
|
||||
mesh_smooth_type="FACE",
|
||||
path_mode="AUTO",
|
||||
bake_space_transform=False,
|
||||
)
|
||||
|
||||
@@ -61,6 +61,16 @@ def measure_and_place():
|
||||
if asset is None:
|
||||
log(f"MISSING {DEST}/SM_HeroFacade")
|
||||
return
|
||||
# Collision: complex-as-simple, not auto-generated convex hulls.
|
||||
# The facade is 95 separate parts across 26 m; Unreal's auto hull generation
|
||||
# wraps them in a few coarse hulls that bulged into the street (the QA wall
|
||||
# probe went 0/35 -> 2/35 after trying it). Complex-as-simple makes the real
|
||||
# triangles the simple collision, so line traces and capsule sweeps block on
|
||||
# the actual wall — the same approach used for the terrain, for the same
|
||||
# reason. The flag lives on the asset, so save it explicitly.
|
||||
unreal.GigLineAssetTools.set_complex_collision_as_simple(asset, True)
|
||||
saved = unreal.EditorAssetLibrary.save_loaded_asset(asset)
|
||||
log(f"collision: {unreal.GigLineAssetTools.get_collision_complexity(asset)} (saved={saved})")
|
||||
box = asset.get_bounding_box()
|
||||
mn, mx = box.min, box.max
|
||||
log(f"SM_HeroFacade bounds cm: size=({mx.x - mn.x:.1f}, {mx.y - mn.y:.1f}, {mx.z - mn.z:.1f}) "
|
||||
|
||||
Reference in new issue
Block a user