diff --git a/Art/blender/vehicle/gigline-sedan.blend b/Art/blender/vehicle/gigline-sedan.blend index d58052e..c65248a 100644 --- a/Art/blender/vehicle/gigline-sedan.blend +++ b/Art/blender/vehicle/gigline-sedan.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2296338fcd7e7b4ea2264a2c99b1c1437d4c3d323dae46a9fac9430dacc24b -size 102442 +oid sha256:62a71eb86eda821801f7d4928de848d0390fe4f8621a1c3090d9c25cbaba1b39 +size 102439 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SK_Vehicle_Sedan.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SK_Vehicle_Sedan.uasset new file mode 100644 index 0000000..cc0816e --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Vehicle/SK_Vehicle_Sedan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d436faeb7d150e64f777fb6a061febe8d338ba78a2f35f7e2cc55cef93ccd86f +size 254347 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SK_Vehicle_Sedan_Skeleton.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SK_Vehicle_Sedan_Skeleton.uasset new file mode 100644 index 0000000..084a19c --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Vehicle/SK_Vehicle_Sedan_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fdd844087fdb0552ed99de2ab617a4b670cda0ba77d5bcea25413bc23312f0c +size 3064 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body.uasset index 507ea43..f29f254 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0df31c43c5716c654d2f4913f6d4648a97b08dd77f20d980e5baee427200942 +oid sha256:0edff2128161a1d113449c3a849df2a9a9b1e37f47ad2bf608ac1465a7c870a0 size 84317 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD1.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD1.uasset index 02bc981..16ef7b1 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD1.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD1.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9015eeab8e1a56e047c1bfaef2a7dcc6ae2099565624b23fc3970581da8c81f2 -size 79710 +oid sha256:5e1d4112392bc169a04fc5cfc11b8bb75e618f9723008bf5694ad45ace1d1678 +size 83801 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD2.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD2.uasset index 6e01d6e..2bc0dba 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD2.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Body_LOD2.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e4fcd96a037de9222dbb64109dad5beadbbaebc49190bd993d21b4457c4eda0 -size 75223 +oid sha256:466d57262edd6c0f54805b9c68a627ca064219f2393fb1f7b355befeeb90c908 +size 79148 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Glass.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Glass.uasset index 551966d..4e0c888 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Glass.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Glass.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3386071f9781e97040141cc327f817bb6a2429a6d5b6f8a5b2823c287451c4df +oid sha256:b3cdaf03455f25ace0e6f08764c56b403653e121366777ae9b5d63bf4b5293a3 size 75795 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Front.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Front.uasset index c5dfda8..f8d0d6f 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Front.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Front.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1bf2ca90dff4313f3ad4b762419b50e6de233efb9fd80236cd46bef5de035d4 +oid sha256:d815814b61be3e945ea084821d536bbfd02aefaaf6e0802cd272fdc6b750ac93 size 74377 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Rear.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Rear.uasset index c292896..02f3c71 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Rear.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Light_Rear.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2abfbaba56d9da4924f992b3961616af18640411424618ac524ba4d6871c6f17 +oid sha256:a629f6d3d33d40d9ed72f4339427c62c766ce7b697f55dbe6947465a3d6f44c0 size 74371 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Mirrors.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Mirrors.uasset index 5c3e74e..6dc7f79 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Mirrors.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Mirrors.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26492303a0a8ee16ffefb15b7113acadd9de0f6c8fe12125cb8f1608d4e31cf2 +oid sha256:06c822c774bfea5af0694f3155c0826140d0f7dddc2afda846287111e1bd3fbd size 74303 diff --git a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Wheel.uasset b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Wheel.uasset index 56e3cc1..c22b657 100644 --- a/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Wheel.uasset +++ b/Game/GigLine/Content/Hollywood/Vehicle/SM_Vehicle_Wheel.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78415d0eff6f59821c30eb15b850b7dd80a3f80b1212a18ed7265dbd67de2a47 +oid sha256:bf057727388c60970c083bfde74029b0f484432515d31275876627a6cf8c4a45 size 76772 diff --git a/TODO.md b/TODO.md index 04cab6d..a7b6c56 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,10 @@ Actionable task list for this project. This file is the single, central place fo - [A] Model one vehicle in Blender with collision meshes; export to Unreal. **Model done** (`Tools/vehicle_build.py` → `Art/blender/vehicle/gigline-sedan.blend`, preview in `Art/blender/vehicle/preview.png`): 4.72 x 1.80 x 1.44 m, 2.78 m wheelbase, 1.62 m track, 0.33 m wheels, 252 triangles, four `UCX_` convex hulls. Dimensions come from real sedan figures and are logged to the report, so the vehicle config can read them rather than restating them. - **Imported into Unreal** (`Tools/ue_vehicle_import.py` → `/Game/Hollywood/Vehicle`), split per the vehicle-artist object split: `SM_Vehicle_Body` (3 slots, 4.76 x 1.76 x 1.28 m), `SM_Vehicle_Glass`, `SM_Vehicle_Light_Front`, `SM_Vehicle_Light_Rear`, `SM_Vehicle_Mirrors`, and one `SM_Vehicle_Wheel` (0.22 x 0.66 x 0.66 m) reused four times. The body FBX carries the `UCX_` hulls as simple collision. - **LODs cut for real:** decimating the duplicate gives LOD1 at 38 triangles and LOD2 at 14, against 132 for LOD0. The first attempt duplicated the shell unchanged and produced 84-triangle LODs that saved nothing - worth remembering as the failure mode of a "LOD" step nobody measures. - **LOD group wiring is open:** `StaticMeshEditorSubsystem.import_lod` is unavailable in a headless commandlet (`get_editor_subsystem` returns None), so the LOD meshes import as their own assets and the group still needs an editor session. -- [ ] Set up Chaos Vehicle with wheel and physics configuration. +- [A] Set up Chaos Vehicle with wheel and physics configuration. **Blocked on the physics asset, which cannot be generated headless.** + - **Skeletal vehicle asset is in** (`SK_Vehicle_Sedan` + `SK_Vehicle_Sedan_Skeleton`): one mesh carrying a root bone and four wheel bones, every wheel skinned to its own bone, exported with the character preset (508 triangles). Chaos drives a skeletal mesh, so the static split alone was not drivable. + - **Blocked:** Chaos needs a `PhysicsAsset`, and `unreal.PhysicsAssetFactory` in 5.8 exposes **no mesh property** (`Failed to find property 'skeletal_mesh'`), so the bodies cannot be generated from the skeletal mesh in a commandlet. Options: author it once in the editor and commit it, or build it in C++ with `FPhysicsAssetUtils` in an editor utility. + - **Next:** create the physics asset (root box + four wheel capsules at the modelled positions: wheelbase 2.78 m, track 1.62 m, radius 0.33 m), then the `UChaosWheeledVehicleMovementComponent` config reads those same numbers from `Art/blender/vehicle` report. - [ ] Implement enter/exit interaction. - [ ] Build a closed driving route with boundaries. - [ ] Tune basic handling to a drivable state. diff --git a/Tools/ue_vehicle_import.py b/Tools/ue_vehicle_import.py index 056b6b4..82ca336 100644 --- a/Tools/ue_vehicle_import.py +++ b/Tools/ue_vehicle_import.py @@ -102,6 +102,33 @@ def main(): mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{name}") log(f"{name}: imported as its own asset ({'ok' if mesh else 'MISSING'}); " "LOD group wiring needs an editor session") + + # Skeletal vehicle for Chaos: a root bone plus one bone per wheel, everything skinned. + skel_path = os.path.join(SOURCE, "vehicle_skeletal.fbx") + if os.path.exists(skel_path): + task = unreal.AssetImportTask() + task.filename = skel_path + task.destination_path = DEST + task.destination_name = "SK_Vehicle_Sedan" + task.automated = True + task.save = True + task.replace_existing = True + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_as_skeletal = True + ui.import_animations = False + ui.import_materials = True + ui.skeletal_mesh_import_data.import_uniform_scale = 1.0 + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + skel = unreal.EditorAssetLibrary.load_asset(f"{DEST}/SK_Vehicle_Sedan") + if skel: + skeleton = skel.get_editor_property("skeleton") + # Skeleton has no bone_names property in 5.8; read the reference skeleton + # through the asset registry instead of guessing a property name. + log(f"SK_Vehicle_Sedan: imported, skeleton={skeleton.get_name() if skeleton else 'NONE'}") + else: + log("SK_Vehicle_Sedan: MISSING after import") lines.append("VERDICT: PASS") diff --git a/Tools/vehicle_build.py b/Tools/vehicle_build.py index d17997c..59cef6b 100644 --- a/Tools/vehicle_build.py +++ b/Tools/vehicle_build.py @@ -167,6 +167,80 @@ def build_collision(mats): return hulls +def build_skeletal(joined, wheels, out_fbx, exp_dir): + """Bind the whole vehicle to a five-bone rig for Chaos. + + Chaos Vehicles drive a *skeletal* mesh: the movement component names wheel bones and + spins them, and the physics asset supplies the collision. The static split above + stays for parts that do not deform, but the drivable asset has to be skeletal, so + this builds a rig with a root plus one bone per wheel and skins every wheel to its + own bone at full weight. + """ + bpy.ops.object.armature_add(location=(0.0, 0.0, 0.0)) + rig = bpy.context.active_object + rig.name = "VehicleRig" + bpy.ops.object.mode_set(mode="EDIT") + edit_bones = rig.data.edit_bones + root = edit_bones[0] + root.name = "root" + root.head = (0.0, 0.0, 0.0) + root.tail = (0.0, 0.0, 0.30) + half_base = DIMS["wheelbase"] * 0.5 + half_track = DIMS["track"] * 0.5 + r = DIMS["wheel_radius"] + wheel_positions = { + "Wheel_FL": (half_base, half_track), + "Wheel_FR": (half_base, -half_track), + "Wheel_RL": (-half_base, half_track), + "Wheel_RR": (-half_base, -half_track), + } + for name, (x, y) in wheel_positions.items(): + bone = edit_bones.new(name) + bone.head = (x, y, r) + # Point the bone along the axle: the wheel spins about its own Y. + bone.tail = (x, y - math.copysign(0.25, y), r) + bone.parent = root + bpy.ops.object.mode_set(mode="OBJECT") + + # Join every visible system plus the wheels into one mesh, then weight by part. + parts = [obj for obj in joined.values()] + list(wheels) + bpy.ops.object.select_all(action="DESELECT") + for part in parts: + part.select_set(True) + bpy.context.view_layer.objects.active = joined["body"] + bpy.ops.object.join() + mesh = bpy.context.active_object + mesh.name = "SM_Vehicle_Skeletal" + + groups = {name: mesh.vertex_groups.new(name=name) for name in ["root", *wheel_positions]} + # Join order is preserved per part: re-derive which vertices belong to which wheel by + # their world position, since the join loses the object boundaries. + half_base = DIMS["wheelbase"] * 0.5 + half_track = DIMS["track"] * 0.5 + for vertex in mesh.data.vertices: + co = vertex.co + nearest = None + for name, (x, y) in wheel_positions.items(): + if abs(co.x - x) < 0.45 and abs(co.y - y) < 0.16 and co.z < DIMS["wheel_radius"] * 2.2: + nearest = name + break + groups[nearest or "root"].add([vertex.index], 1.0, "REPLACE") + + armature = mesh.modifiers.new(name="Armature", type="ARMATURE") + armature.object = rig + mesh.parent = rig + + bpy.ops.object.select_all(action="DESELECT") + rig.select_set(True) + mesh.select_set(True) + bpy.context.view_layer.objects.active = rig + path = os.path.join(exp_dir, "vehicle_skeletal.fbx") + fbx_export_preset.export_character(path, selection=True) + log(f"skeletal vehicle exported: {path}, bones root + 4 wheels, " + f"{sum(len(p.vertices) - 2 for p in mesh.data.polygons)} triangles") + return path + + def main(argv=None) -> int: argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] ap = argparse.ArgumentParser(description=__doc__, @@ -272,10 +346,14 @@ def main(argv=None) -> int: fbx_export_preset.export_static(os.path.join(exp_dir, "wheel.fbx"), selection=True) exported["wheel"] = os.path.join(exp_dir, "wheel.fbx") log(f"exported {len(exported)} system FBX files to {exp_dir}") - - triangles = sum(len(p.vertices) - 2 for p in shell.data.polygons) per_system = {key: sum(len(p.vertices) - 2 for p in obj.data.polygons) for key, obj in joined.items()} + system_names = {key: obj.name for key, obj in joined.items()} + wheel_names = [w.name for w in wheels] + wheel_triangles = sum(len(p.vertices) - 2 for p in wheels[0].data.polygons) + + triangles = sum(len(p.vertices) - 2 for p in shell.data.polygons) + shell_triangles = triangles for hull in hull_join: triangles += sum(len(p.vertices) - 2 for p in hull.data.polygons) # LODs per the vehicle-artist targets: LOD1 drops mirrors and lamp detail, LOD2 is a @@ -304,17 +382,23 @@ def main(argv=None) -> int: log(f"{lod_key}: exported, {sum(len(p.vertices) - 2 for p in lod.data.polygons)} triangles") bpy.data.objects.remove(lod, do_unlink=True) + # Skeletal asset last: joining the parts for it destroys the objects the LOD and + # system steps need. The first attempt called it earlier and died on + # "StructRNA of type Object has been removed". + skeletal_path = build_skeletal(joined, wheels, args.fbx, exp_dir) + exported["skeletal"] = skeletal_path + report = { "asset": "SM_Vehicle_Sedan", "dimensions_m": DIMS, "body_parts": len(body), - "triangles": triangles, + "triangles": shell_triangles, "collision_hulls": [h.name for h in hull_join], - "wheels": [w.name for w in wheels], - "systems": {key: {"object": obj.name, "triangles": per_system.get(key, 0), + "wheels": wheel_names, + "systems": {key: {"object": system_names.get(key), "triangles": per_system.get(key, 0), "fbx": exported.get(key)} - for key, obj in joined.items()}, - "wheel_triangles": sum(len(p.vertices) - 2 for p in wheels[0].data.polygons), + for key in system_names}, + "wheel_triangles": wheel_triangles, "lods": lod_paths, "log": PARTS, }