fix(level-builder): 锚点补偿+旋转修复+天空盒+解析器去重
- asset_mapping: 38种BSP资产实测锚点+pivot_offset_xy自动补偿 - scene_builder: Rotator改关键字参数修正旋转轴映射 - scene_builder: spawn前XY锚点补偿(支持Yaw旋转) - scene_builder: 新增ExponentialHeightFog+大气效果后处理 - build_cmdline: 构建后自动添加雾+GameMode+DirectionalLight大气 - build_guide_parser: 去重+全局地面+旋转列名兼容+边界段范围修正 - BarrierWall实际尺寸修正(60×210非20×100) - 搭建指南: 全局地面中心坐标+西边界旋转统一+院门改双开门
This commit is contained in:
@@ -4,208 +4,339 @@
|
||||
资产根路径: Content/BlockingStarterPack/Meshes/
|
||||
|
||||
路径格式: /Game/<ContentSubPath>/<AssetName>.<AssetName>
|
||||
|
||||
pivot_offset_xy: 锚点偏离视觉中心的 XY 偏移量(局部坐标系)。
|
||||
- 搭建指南中所有位置统一为「视觉中心」
|
||||
- builder 生成 Actor 时减去此偏移 × 缩放 × 旋转,得到实际 spawn 位置
|
||||
- (0, 0) 表示锚点已在中心(如底面中心类资产),无需补偿
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
# 资产路径前缀
|
||||
MESH_ROOT = "/Game/BlockingStarterPack/Meshes"
|
||||
MATERIAL_ROOT = "/Game/BlockingStarterPack/Materials"
|
||||
|
||||
MESH_ASSETS = {
|
||||
# ---- 建筑 — 墙体 ----
|
||||
# 实测: min=(0, -thickness/2, 0), max=(width, thickness/2, height)
|
||||
# 锚点在底边一端,X方向偏移半个宽度
|
||||
"Wall_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_400x300.SM_Wall_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (400, 10, 300)),
|
||||
"pivot_offset_xy": (200, 0),
|
||||
"category": "wall",
|
||||
},
|
||||
"Wall_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_200x300.SM_Wall_200x300",
|
||||
"default_size": (200, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (200, 10, 300)),
|
||||
"pivot_offset_xy": (100, 0),
|
||||
"category": "wall",
|
||||
},
|
||||
"Wall_100x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_100x300.SM_Wall_100x300",
|
||||
"default_size": (100, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (100, 10, 300)),
|
||||
"pivot_offset_xy": (50, 0),
|
||||
"category": "wall",
|
||||
},
|
||||
"Wall_400x100": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_400x100.SM_Wall_400x100",
|
||||
"default_size": (400, 20, 100),
|
||||
"actual_bounds": ((0, -10, 0), (400, 10, 100)),
|
||||
"pivot_offset_xy": (200, 0),
|
||||
"category": "wall",
|
||||
},
|
||||
"WallDoor_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoor_200x300.SM_WallDoor_200x300",
|
||||
"default_size": (200, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (200, 10, 300)),
|
||||
"pivot_offset_xy": (100, 0),
|
||||
"category": "wall_door",
|
||||
},
|
||||
"WallDoorGarage_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoorGarage_400x300.SM_WallDoorGarage_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (400, 10, 300)),
|
||||
"pivot_offset_xy": (200, 0),
|
||||
"category": "wall_door",
|
||||
},
|
||||
"WallDoorDouble_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoorDouble_400x300.SM_WallDoorDouble_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (400, 10, 300)),
|
||||
"pivot_offset_xy": (200, 0),
|
||||
"category": "wall_door",
|
||||
},
|
||||
"WallWindow_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallWindow_400x300.SM_WallWindow_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (400, 10, 300)),
|
||||
"pivot_offset_xy": (200, 0),
|
||||
"category": "wall_window",
|
||||
},
|
||||
"WallWindowSmall_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallWindowSmall_200x300.SM_WallWindowSmall_200x300",
|
||||
"default_size": (200, 20, 300),
|
||||
"actual_bounds": ((0, -10, 0), (200, 10, 300)),
|
||||
"pivot_offset_xy": (100, 0),
|
||||
"category": "wall_window",
|
||||
},
|
||||
# ---- 建筑 — 地板 ----
|
||||
# 实测: min=(0, 0, -20), max=(width, height, 2)
|
||||
# 锚点在角落,XY 都偏移半尺寸
|
||||
"Floor_1000x1000": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_1000x1000.SM_Floor_1000x1000",
|
||||
"default_size": (1000, 1000, 10),
|
||||
"actual_bounds": ((0, 0, -20), (1000, 1000, 2)),
|
||||
"pivot_offset_xy": (500, 500),
|
||||
"category": "floor",
|
||||
},
|
||||
"Floor_300x400": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_300x400.SM_Floor_300x400",
|
||||
"default_size": (300, 400, 10),
|
||||
"actual_bounds": ((0, 0, -20), (300, 400, 2)),
|
||||
"pivot_offset_xy": (150, 200),
|
||||
"category": "floor",
|
||||
},
|
||||
"Floor_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_200x300.SM_Floor_200x300",
|
||||
"default_size": (200, 300, 10),
|
||||
"actual_bounds": ((0, 0, -20), (200, 300, 2)),
|
||||
"pivot_offset_xy": (100, 150),
|
||||
"category": "floor",
|
||||
},
|
||||
# ---- 建筑 — 门 ----
|
||||
"RotativeDoor_01": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Doors/SM_RotativeDoor_01.SM_RotativeDoor_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-100, -12, 0), (0, 12, 216)),
|
||||
"pivot_offset_xy": (50, 0), # off-center, pivot at hinge side
|
||||
"category": "door",
|
||||
},
|
||||
"GarageDoorOpen_01": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Doors/SM_GarageDoorOpen_01.SM_GarageDoorOpen_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-150, -224.5, 220), (150, 5.9, 296.4)),
|
||||
"pivot_offset_xy": (0, 109), # X centered, Y offset
|
||||
"category": "door",
|
||||
},
|
||||
# ---- 建筑 — 窗户 ----
|
||||
"FakeWindowsFrame": {
|
||||
"path": f"{MESH_ROOT}/Architecture/WindowsSet/SM_FakeWindowsFrame.SM_FakeWindowsFrame",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-70, 0, 96), (70, 14, 260)),
|
||||
"pivot_offset_xy": (0, 7),
|
||||
"category": "window",
|
||||
},
|
||||
# ---- 建筑 — 围栏/围挡 ----
|
||||
# 实测: min=(0, -30, 0), max=(600, 30, 210) — 实际60cm厚, 210cm高!
|
||||
"BarrierWall_600": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Barriers/SM_BarrierWall_600.SM_BarrierWall_600",
|
||||
"default_size": (600, 20, 100),
|
||||
"default_size": (600, 60, 210),
|
||||
"actual_bounds": ((0, -30, 0), (600, 30, 210)),
|
||||
"pivot_offset_xy": (300, 0),
|
||||
"category": "barrier",
|
||||
},
|
||||
"BarrierWall_200": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Barriers/SM_BarrierWall_200.SM_BarrierWall_200",
|
||||
"default_size": (200, 20, 100),
|
||||
"default_size": (200, 60, 210),
|
||||
"actual_bounds": ((0, -30, 0), (200, 30, 210)),
|
||||
"pivot_offset_xy": (100, 0),
|
||||
"category": "barrier",
|
||||
},
|
||||
"Fence_Small_400": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_Fence_Small_400.SM_Fence_Small_400",
|
||||
"default_size": (400, 5, 100),
|
||||
"actual_bounds": ((0, 0, 0), (404, 6, 112)),
|
||||
"pivot_offset_xy": (200, 3),
|
||||
"category": "fence",
|
||||
},
|
||||
# ---- 建筑 — 横梁 ----
|
||||
"Beam_300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Beams/SM_Beam_300.SM_Beam_300",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-20, -20, 0), (20, 20, 300)),
|
||||
"pivot_offset_xy": (0, 0), # X/Y centered
|
||||
"category": "beam",
|
||||
},
|
||||
# ---- 建筑 — 大型建筑 ----
|
||||
"Building_01": {
|
||||
"path": f"{MESH_ROOT}/BigBuildings/SM_Building_01.SM_Building_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-925, -725, 0), (1025, 725, 4010)),
|
||||
"pivot_offset_xy": (50, 0), # nearly centered, slight X offset
|
||||
"category": "building",
|
||||
},
|
||||
# ---- 家具 ----
|
||||
# 所有家具锚点在底面中心,无需补偿
|
||||
"SimpleTable_01": {
|
||||
"path": f"{MESH_ROOT}/Furnitures/SM_SimpleTable_01.SM_SimpleTable_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-80.6, -43.8, 0), (80.6, 43.8, 74)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "furniture",
|
||||
},
|
||||
"SimpleTable_02": {
|
||||
"path": f"{MESH_ROOT}/Furnitures/SM_SimpleTable_02.SM_SimpleTable_02",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-119.6, -49.8, 0), (119.6, 49.8, 74)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "furniture",
|
||||
},
|
||||
"BedSingle": {
|
||||
"path": f"{MESH_ROOT}/Bedroom/SM_BedSingle.SM_BedSingle",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-57.2, -1.2, 0), (59, 214.8, 68)),
|
||||
"pivot_offset_xy": (1, 107), # nearly corner in Y
|
||||
"category": "furniture",
|
||||
},
|
||||
"Monitor_01": {
|
||||
"path": f"{MESH_ROOT}/Furnitures/SM_Monitor_01.SM_Monitor_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-27.4, -14.3, 0), (27.4, 4.5, 37.9)),
|
||||
"pivot_offset_xy": (0, 5), # slight Y offset
|
||||
"category": "furniture",
|
||||
},
|
||||
# ---- 工业/装饰 ----
|
||||
"Barrel": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_Barrel.SM_Barrel",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-36.6, -36.6, 0), (36.6, 36.6, 111.8)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "industrial",
|
||||
},
|
||||
"WoodenBox_Small": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_WoodenBox_Small.SM_WoodenBox_Small",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-35, -35, 0), (35, 35, 70)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "industrial",
|
||||
},
|
||||
"WoodenBox_Normal": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_WoodenBox_Normal.SM_WoodenBox_Normal",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-45, -45, 0), (45, 45, 90)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "industrial",
|
||||
},
|
||||
"WoodenPallet_01": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_WoodenPallet_01.SM_WoodenPallet_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-94.2, -94.7, 0), (93.9, 94.1, 20)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "industrial",
|
||||
},
|
||||
"Bucket_01": {
|
||||
"path": f"{MESH_ROOT}/Decorations/SM_Bucket_01.SM_Bucket_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-15.3, -13.9, 0), (15.3, 13.9, 26.5)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "industrial",
|
||||
},
|
||||
"TrashCans_01": {
|
||||
"path": f"{MESH_ROOT}/Trashs/SM_TrashCans_01.SM_TrashCans_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-26.8, -41.3, 0), (26.8, 30.1, 118.1)),
|
||||
"pivot_offset_xy": (0, 6), # slight Y offset
|
||||
"category": "industrial",
|
||||
},
|
||||
"Container_Normal": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_Container_Normal.SM_Container_Normal",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-122.4, -292.7, 0), (122.4, 292.7, 260)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "industrial",
|
||||
},
|
||||
# ---- 载具 ----
|
||||
"Car01": {
|
||||
"path": f"{MESH_ROOT}/Vehicles/SM_Car01.SM_Car01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-80, -220, 0), (80, 200, 180)),
|
||||
"pivot_offset_xy": (0, 10), # slight Y offset
|
||||
"category": "vehicle",
|
||||
},
|
||||
# ---- 基础体 ----
|
||||
# Cube: 角落锚点! min=(0,0,0), max=(100,100,100)
|
||||
"Cube": {
|
||||
"path": f"{MESH_ROOT}/Primitives/SM_Cube.SM_Cube",
|
||||
"default_size": (100, 100, 100),
|
||||
"actual_bounds": ((0, 0, 0), (100, 100, 100)),
|
||||
"pivot_offset_xy": (50, 50),
|
||||
"category": "primitive",
|
||||
},
|
||||
# Cylinder: 底面中心,无需补偿
|
||||
"Cylinder": {
|
||||
"path": f"{MESH_ROOT}/Primitives/SM_Cylinder.SM_Cylinder",
|
||||
"default_size": (100, 100, 100),
|
||||
"actual_bounds": ((-50, -50, 0), (50, 50, 100)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "primitive",
|
||||
},
|
||||
"CirclePlatform": {
|
||||
"path": f"{MESH_ROOT}/Extra/SM_CirclePlatform.SM_CirclePlatform",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-48, -48, 0), (48, 48, 4)),
|
||||
"pivot_offset_xy": (0, 0),
|
||||
"category": "primitive",
|
||||
},
|
||||
# ---- 其他 ----
|
||||
"Tree_01": {
|
||||
"path": f"{MESH_ROOT}/Vegetations/SM_Tree_01.SM_Tree_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-352, -349, 0), (410, 454, 845)),
|
||||
"pivot_offset_xy": (29, 53), # slightly off-center
|
||||
"category": "vegetation",
|
||||
},
|
||||
"RoadLamp_01": {
|
||||
"path": f"{MESH_ROOT}/Lights/SM_RoadLamp_01.SM_RoadLamp_01",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-20, -14.2, 0), (20, 230, 850)),
|
||||
"pivot_offset_xy": (0, 108), # off-center in Y
|
||||
"category": "light_fixture",
|
||||
},
|
||||
"DisplayPanels_400x200": {
|
||||
"path": f"{MESH_ROOT}/DisplayPanels/SM_DisplayPanels_400x200.SM_DisplayPanels_400x200",
|
||||
"default_size": None,
|
||||
"actual_bounds": ((-200, 0, -100), (200, 8, 100)),
|
||||
"pivot_offset_xy": (0, 4),
|
||||
"category": "decoration",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_pivot_compensation(asset_key: str, scale: list, rotation: list) -> tuple:
|
||||
"""计算锚点偏移补偿。
|
||||
|
||||
搭建指南位置 = 视觉中心。实际 spawn 位置 = 视觉中心 - offset(缩放后旋转后)。
|
||||
|
||||
Args:
|
||||
asset_key: 资产映射 key
|
||||
scale: [sx, sy, sz] 缩放
|
||||
rotation: [pitch, yaw, roll] 度数
|
||||
|
||||
Returns:
|
||||
(dx, dy) 需要从位置中减去的 XY 偏移
|
||||
"""
|
||||
info = MESH_ASSETS.get(asset_key)
|
||||
if not info:
|
||||
return (0.0, 0.0)
|
||||
|
||||
offset = info.get("pivot_offset_xy", (0, 0))
|
||||
if offset == (0, 0):
|
||||
return (0.0, 0.0)
|
||||
|
||||
# 缩放偏移
|
||||
ox = offset[0] * scale[0]
|
||||
oy = offset[1] * scale[1]
|
||||
|
||||
# 应用 Yaw 旋转(绕 Z 轴)
|
||||
yaw_rad = math.radians(rotation[1]) # rotation[1] = Yaw
|
||||
cos_y = math.cos(yaw_rad)
|
||||
sin_y = math.sin(yaw_rad)
|
||||
rx = ox * cos_y - oy * sin_y
|
||||
ry = ox * sin_y + oy * cos_y
|
||||
|
||||
return (rx, ry)
|
||||
|
||||
Reference in New Issue
Block a user