diff --git a/audit/history.jsonl b/audit/history.jsonl index 00569c29..71b4a3e6 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -571,3 +571,5 @@ {"date": "2026-09-28T05:29:23+00:00", "unit": "EterLib/GrpImageTexture.cpp", "action": "unit K: DXT DDS textures upload the file's own mip levels (40250 CreateDDSTexture: max(1,dwMipMapCount), dwLinearSize>>2i Copy) instead of a regenerated chain; non-DDS keep the D3DX_DEFAULT full chain; mem: textures one level; invented is_ui_tex no-mip rule (icon/ui/locale/<=128px) deleted; dxt_decode_test registered in ctest with mip-level cases", "evidence": "ctest -j3 36/36; fake-server spawn frame 900: near terrain diff 0.00, far terrain 2.7/255 (minified levels now from the file), rest is UI timing (area title/chat); far-terrain look NEEDS_LIVE"} {"date": "2026-09-28T05:33:21+00:00", "unit": "SpeedTreeLib/SpeedTreeWrapper.cpp", "action": "SpeedTree follow-up closed without code change: .spt 0x4E25 is one composite rect per file (87/87, independent of 1-3 leaf maps) and overlays on the atlases put it on the billboard tree image for most species, so it is not the leaf-map texcoords; the hand-picked leaf rects stay DIVERGENT, note updated", "evidence": "byte dump of b1_baobab_rt.spt tail (0x4E20/0x4E22 name/0x4E23 b/0x4E24 b/0x4E25 8f/0x4E21); count over 87 .spt files; atlas overlays of 9 species"} {"date": "2026-09-30T03:07:37+00:00", "unit": "MilesLib/*", "action": "MilesLib platform port: 40250 SoundBase/SoundData/SoundInstance2D/3D/Stream/SoundManager*/Type copied verbatim (only includes + one unused QPC local differ) over an AIL_* software mixer (MilesAil.cpp: 2D balance pan, 3D inverse distance min 1/max 200 + listener pan, threaded stream decode, loop counts, decompress_ASI/ADPCM to float WAV); host NativeAudioEngine drains MtMilesMix; CSoundManager Create/Destroy in PythonBoot (IsNoSoundCard), listener+Update from PythonApplicationCamera, Save/RestoreVolume on window focus, CSoundData::SetPackMode in PackInitialize; old AudioCommands/SoundInstance bridge deleted; 142/142 PORTED", "evidence": "port_miles_test ok (2D/3D/stream fades/DataToInstance); ctest all pass except pre-existing flaky login_flow; desktop live client MT_AUDIO_TRACE peaks 1.17/0.70 at login and ~0.02 in game; Android device not attached, NEEDS device run"} +{"date": "2026-09-30T03:19:50+00:00", "unit": "SpeedTreeLib/SpeedTreeWrapper.cpp", "action": "CSpeedTreeRT stand-in reads the .spt collision block (12000: 12002 sphere, 12003 cylinder) and the composite texcoord block (10000: 10002 leaf maps, 10003 frond, 10004 billboard) instead of a fixed trunk cylinder and hand-picked leaf rects; GetCollisionObjectCount/GetCollisionObject DIVERGENT -> PORTED; SetupLeafBuffers note updated (earlier 'no per-leaf-map texcoords' conclusion was wrong, the rects are in section 10000)", "evidence": "port_speedtree_test ok; token layout checked over 118 .spt extracted from the 40250 packs; fake-server Yongan view (camera 4500/3/0): tree crown now drawn from its own leaf-map cells, the wrong fruit/autumn cell is gone"} +{"date": "2026-09-30T03:19:50+00:00", "unit": "SpeedTreeLib/SpeedGrassRT.cpp", "action": "SpeedGrassRT/SpeedGrassWrapper/BoundaryShapeManager N_A: compiled (USE_SPEEDGRASS) but no caller outside those files", "evidence": "LC_ALL=C grep over ClientVS22/source for CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager"} diff --git a/audit/port-map/SpeedTreeLib/BoundaryShapeManager.cpp.json b/audit/port-map/SpeedTreeLib/BoundaryShapeManager.cpp.json new file mode 100644 index 00000000..cf17c0d2 --- /dev/null +++ b/audit/port-map/SpeedTreeLib/BoundaryShapeManager.cpp.json @@ -0,0 +1,32 @@ +{ + "reference": "SpeedTreeLib/BoundaryShapeManager.cpp", + "reference_sha256": "6fdfc42ca249991740567658bc0dced8dedddb387e2f4e1dcbad53b0d38fc964", + "priority": "P4", + "contracts": [], + "functions": { + "CBoundaryShapeManager::CBoundaryShapeManager": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CBoundaryShapeManager::~CBoundaryShapeManager": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CBoundaryShapeManager::LoadBsfFile": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CBoundaryShapeManager::PointInside": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CBoundaryShapeManager::PointInShape": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CBoundaryShapeManager::RandomPoint": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + } + } +} diff --git a/audit/port-map/SpeedTreeLib/SpeedGrassRT.cpp.json b/audit/port-map/SpeedTreeLib/SpeedGrassRT.cpp.json new file mode 100644 index 00000000..737d710b --- /dev/null +++ b/audit/port-map/SpeedTreeLib/SpeedGrassRT.cpp.json @@ -0,0 +1,100 @@ +{ + "reference": "SpeedTreeLib/SpeedGrassRT.cpp", + "reference_sha256": "1fbd873c76a7aa57935035ea45a720527b9fd8ee478352806934fbb5f9c86d42", + "priority": "P4", + "contracts": [], + "functions": { + "VecInterpolate": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::SBlade::SBlade": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::SRegion::SRegion": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::CSpeedGrassRT": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::~CSpeedGrassRT": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::DeleteRegions": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::GetRegions": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::ParseBsfFile": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::CustomPlacement": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::GetLodParams": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::SetLodParams": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::Cull": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::SetWindDirection": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::GetWindDirection": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::GetCameraPos": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::SetCamera": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::SetPerspective": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::CreateRegions": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::RotateAxisFromIdentity": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::ComputeFrustum": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::ComputeUnitBillboard": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::ConvertCoordsToCell": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassRT::OutsideFrustum": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + } + } +} diff --git a/audit/port-map/SpeedTreeLib/SpeedGrassWrapper.cpp.json b/audit/port-map/SpeedTreeLib/SpeedGrassWrapper.cpp.json new file mode 100644 index 00000000..594d41ce --- /dev/null +++ b/audit/port-map/SpeedTreeLib/SpeedGrassWrapper.cpp.json @@ -0,0 +1,36 @@ +{ + "reference": "SpeedTreeLib/SpeedGrassWrapper.cpp", + "reference_sha256": "c737f71f0115acabec0eb1ba4cf4a0ec9a169c8ac5f9ac32a789d2194ac00f53", + "priority": "P4", + "contracts": [], + "functions": { + "CSpeedGrassWrapper::CSpeedGrassWrapper": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassWrapper::~CSpeedGrassWrapper": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassWrapper::Draw": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassWrapper::InitFromBsfFile": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassWrapper::Color": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassWrapper::Height": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + }, + "CSpeedGrassWrapper::InitGraphics": { + "status": "N_A", + "note": "compiled in 40250 (Constants.h defines USE_SPEEDGRASS) but unreachable: CSpeedGrassRT/CSpeedGrassWrapper/CBoundaryShapeManager are referenced only inside SpeedGrass*/BoundaryShapeManager* (grep over ClientVS22/source); nothing to port" + } + } +} diff --git a/audit/port-map/SpeedTreeLib/SpeedTreeWrapper.cpp.json b/audit/port-map/SpeedTreeLib/SpeedTreeWrapper.cpp.json index b90d175a..d2d5c539 100644 --- a/audit/port-map/SpeedTreeLib/SpeedTreeWrapper.cpp.json +++ b/audit/port-map/SpeedTreeLib/SpeedTreeWrapper.cpp.json @@ -68,7 +68,8 @@ "impl": [ "src/port/SpeedTreeLib/SpeedTreeWrapper.cpp:CSpeedTreeWrapper::SetupLeafBuffers" ], - "note": "verbatim. DIVERGENT input: the stand-in's leaf clusters and composite-atlas rects are hand-picked (the .spt stores no per-leaf-map texcoords: leaf entries 0x0FA0-0x0FA7 carry colour/path/origin/size only, and the single 8-float 0x4E25 rect in the 0x4E20 composite block, one per file for 1-3 leaf maps, frames the billboard cell in most atlases; billboards never render in 40250 since SetLodLevel(1.0)); NEEDS_LIVE" + "note": "verbatim. Leaf texture coordinates are the .spt composite-atlas rects (10000 section: 10002 leaf maps, 10003 frond map, 10004 billboard, 8 floats per card, v flipped under SetTextureFlip), replacing the hand-picked rects; a tree without leaf maps (palms, ferns, bare winter trees) draws the proxy clusters with its frond rect. DIVERGENT input remains: cluster positions/sizes are the stand-in's procedural proxy, not the closed engine's; NEEDS_LIVE", + "test": "tests/port/port_speedtree_test.cpp" }, "CSpeedTreeWrapper::Advance": { "status": "PORTED", @@ -196,18 +197,20 @@ ] }, "CSpeedTreeWrapper::GetCollisionObjectCount": { - "status": "DIVERGENT", + "status": "PORTED", "impl": [ "src/port/SpeedTreeLib/SpeedTreeWrapper.cpp:CSpeedTreeWrapper::GetCollisionObjectCount" ], - "note": "verbatim; the stand-in returns one trunk CO_CYLINDER (radius 35, height 750), not the .spt collision objects" + "note": "verbatim; the CSpeedTreeRT stand-in returns the .spt collision block as stored (12000 section: 12002 sphere x y z r, 12003 cylinder x y z r h; 1-5 objects, none for aloe/fern ground plants, checked on all 118 pack .spt); the wrapper passes no size override (LoadTree fSize -1), so no SetTreeSize rescale applies", + "test": "tests/port/port_speedtree_test.cpp" }, "CSpeedTreeWrapper::GetCollisionObject": { - "status": "DIVERGENT", + "status": "PORTED", "impl": [ "src/port/SpeedTreeLib/SpeedTreeWrapper.cpp:CSpeedTreeWrapper::GetCollisionObject" ], - "note": "verbatim; the stand-in returns one trunk CO_CYLINDER (radius 35, height 750), not the .spt collision objects" + "note": "verbatim; the CSpeedTreeRT stand-in returns the .spt collision block as stored (12000 section: 12002 sphere x y z r, 12003 cylinder x y z r h; 1-5 objects, none for aloe/fern ground plants, checked on all 118 pack .spt); the wrapper passes no size override (LoadTree fSize -1), so no SetTreeSize rescale applies", + "test": "tests/port/port_speedtree_test.cpp" }, "CSpeedTreeWrapper::GetPosition": { "status": "PORTED", diff --git a/src/platform/SpeedTreeLib/SpeedTreeRT.cpp b/src/platform/SpeedTreeLib/SpeedTreeRT.cpp index 26326b8e..7c6ae3ef 100644 --- a/src/platform/SpeedTreeLib/SpeedTreeRT.cpp +++ b/src/platform/SpeedTreeLib/SpeedTreeRT.cpp @@ -12,11 +12,15 @@ // inactive; there is only that LOD. // - GetTextures returns bare file names: the wrapper prefixes the .spt directory. // - GetTreeSize reads the Size / SizeVariance header tokens (2001 / 2002) of the .spt. +// - GetCollisionObject returns the .spt collision block (12000 section) as stored: 12002 sphere +// (x, y, z, radius), 12003 cylinder (x, y, z, radius, height); a tree without the section has none. +// - Leaf texture coordinates are the .spt composite-atlas rects (10000 section), v flipped under +// SetTextureFlip. // What is not the SDK (DIVERGENT; the notes are on the wrapper in audit/port-map/SpeedTreeLib/): // - Branch and leaf geometry cannot be generated from the .spt parameters without the closed // engine; it is the previous procedural proxy (species hints from the file name, 1000-unit -// height, crossed-branch trunk, hand-picked composite atlas rects), no fronds. -// - Collision objects are one trunk cylinder; the .spt collision block is not parsed. +// height, crossed-branch trunk, leaf clusters), no fronds; a tree without leaf maps draws its +// proxy clusters with its frond-map rect. // - Materials are the SDK defaults (white); the .spt material block is not parsed. #include "SpeedTreeLib/StdAfx.h" @@ -116,47 +120,56 @@ uint32_t species_seed(const std::string & s) return h; } -// Composite-atlas leaf rects, D3D image space (v down). +// Composite-atlas rect, D3D image space (v down). struct UVRect { float u0 = 0.0f, v0 = 0.0f, u1 = 1.0f, v1 = 1.0f; }; -std::vector foliage_rects(const std::string & species, const std::string & composite) +// .spt texture-coordinate block: 10000, then 10002 leaf maps, 10003 frond maps, 10004 +// billboards, each entry 8 floats: the four card corners (+r,+u) (-r,+u) (-r,-u) (+r,-u) as +// (u, v) in the composite atlas with v up (the SDK flips v under SetTextureFlip), then 10001. Checked on +// the 118 .spt files of the 40250 packs: 0-2 leaf maps, always 1 frond map, 0-1 billboards. +struct SSptTexCoords { - if (composite.empty()) - return { { 0.0f, 0.0f, 1.0f, 1.0f } }; - const std::string s = to_lower_str(species); - const std::string c = to_lower_str(composite); - const bool fall = s.find("fall") != std::string::npos; - const bool winter = s.find("winter") != std::string::npos; - if (c.find("b1") != std::string::npos) + std::vector vLeaves, vFronds, vBillboards; +}; + +bool read_spt_texcoords(const unsigned char * p, size_t n, SSptTexCoords & out) +{ + auto i32_at = [&](size_t o) { + int32_t v = 0; + std::memcpy(&v, p + o, 4); + return v; + }; + for (size_t o = 0; o + 8 <= n; ++o) { - if (fall) - return { { 0.00f, 0.05f, 0.25f, 0.25f }, { 0.25f, 0.25f, 0.50f, 0.50f } }; - return { { 0.25f, 0.02f, 0.50f, 0.23f }, { 0.25f, 0.18f, 0.50f, 0.36f }, { 0.00f, 0.27f, 0.27f, 0.49f } }; + if (i32_at(o) != 10000 || i32_at(o + 4) != 10002) + continue; + SSptTexCoords coords; + size_t c = o + 4; + for (int32_t token : { 10002, 10003, 10004 }) + { + if (c + 8 > n || i32_at(c) != token) + return false; + const int32_t count = i32_at(c + 4); + c += 8; + if (count < 0 || count > 64 || c + size_t(count) * 32 > n) + return false; + std::vector & rects = token == 10002 ? coords.vLeaves : token == 10003 ? coords.vFronds : coords.vBillboards; + for (int32_t i = 0; i < count; ++i, c += 32) + { + float f[8]; + std::memcpy(f, p + c, sizeof(f)); + rects.push_back({ f[2], 1.0f - f[1], f[0], 1.0f - f[5] }); + } + } + if (c + 4 > n || i32_at(c) != 10001) + return false; + out = std::move(coords); + return true; } - if (c.find("b2") != std::string::npos) - { - if (fall) - return { { 0.00f, 0.00f, 0.25f, 0.25f }, { 0.25f, 0.25f, 0.50f, 0.50f } }; - return { { 0.50f, 0.38f, 0.75f, 0.63f }, { 0.50f, 0.63f, 0.75f, 0.88f }, { 0.00f, 0.38f, 0.25f, 0.62f } }; - } - if (c.find("b3") != std::string::npos) - { - if (fall) - return { { 0.25f, 0.25f, 0.50f, 0.50f } }; - return { { 0.00f, 0.25f, 0.25f, 0.50f }, { 0.00f, 0.50f, 0.25f, 0.75f }, { 0.25f, 0.50f, 0.50f, 0.75f } }; - } - if (c.find("n1") != std::string::npos) - { - if (winter) - return { { 0.00f, 0.36f, 0.50f, 0.58f }, { 0.25f, 0.55f, 0.52f, 0.75f } }; - return { { 0.00f, 0.72f, 0.28f, 0.96f }, { 0.25f, 0.74f, 0.53f, 0.97f } }; - } - if (c.find("n2") != std::string::npos) - return { { 0.00f, 0.48f, 0.27f, 0.75f }, { 0.26f, 0.73f, 0.58f, 1.00f }, { 0.75f, 0.48f, 1.00f, 0.80f } }; - return { { 0.0f, 0.0f, 1.0f, 1.0f } }; + return false; } // .spt header: 1000 "__IdvSpt_02_", 1002, 2000 , 2001 , @@ -186,6 +199,59 @@ bool read_spt_size(const unsigned char * p, size_t n, float & size, float & vari return true; } +struct SCollisionObject +{ + CSpeedTreeRT::ECollisionObjectType eType = CSpeedTreeRT::CO_SPHERE; + float afPosition[3] = { 0.0f, 0.0f, 0.0f }; + float afDimensions[3] = { 0.0f, 0.0f, 0.0f }; +}; + +// .spt collision block: 12000, then per object 12002 (sphere) or 12003 (cylinder) as floats, then 12001. The section sits after the texture-coordinate block (10000 +// .. 10001) and before 13000; small ground plants (aloe flowers, ferns) have none. Checked on all 118 +// .spt files of the 40250 packs: 1-5 objects, only these two tokens. +void read_spt_collision(const unsigned char * p, size_t n, std::vector & out) +{ + auto i32_at = [&](size_t o) { + int32_t v = 0; + std::memcpy(&v, p + o, 4); + return v; + }; + for (size_t o = 0; o + 8 <= n; ++o) + { + if (i32_at(o) != 12000) + continue; + const int32_t next = i32_at(o + 4); + if (next != 12001 && next != 12002 && next != 12003) + continue; + std::vector objects; + size_t c = o + 4; + for (;;) + { + if (c + 4 > n) + return; + const int32_t t = i32_at(c); + c += 4; + if (t == 12001) + break; + const size_t count = t == 12002 ? 4 : t == 12003 ? 5 : 0; + if (count == 0 || c + count * 4 > n) + return; + SCollisionObject obj; + obj.eType = t == 12002 ? CSpeedTreeRT::CO_SPHERE : CSpeedTreeRT::CO_CYLINDER; + float f[5] = {}; + std::memcpy(f, p + c, count * 4); + c += count * 4; + std::memcpy(obj.afPosition, f, sizeof(obj.afPosition)); + obj.afDimensions[0] = f[3]; + obj.afDimensions[1] = count == 5 ? f[4] : 0.0f; + objects.push_back(obj); + } + out = std::move(objects); + return; + } +} + struct SStaticLight { float afAttributes[16] = { @@ -258,7 +324,8 @@ struct STreeData std::vector vLeafTexPtrs, vLeafCoordPtrs; float afBoundingBox[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - float fTrunkRadius = 35.0f, fTrunkHeight = 600.0f; + std::vector vCollisionObjects; + SSptTexCoords texCoords; float afBranchMaterial[13], afFrondMaterial[13], afLeafMaterial[13]; float fLeafLightingAdjustment = 0.75f; bool bComputed = false; @@ -285,6 +352,8 @@ struct STreeData strCompositeTexture = basename_only(info.composite_texture); strSelfShadowTexture = basename_only(info.self_shadow_texture); read_spt_size(p, n, fFileSize, fFileVariance); + read_spt_collision(p, n, vCollisionObjects); + read_spt_texcoords(p, n, texCoords); fSize = fFileSize; fVariance = fFileVariance; } @@ -366,7 +435,11 @@ struct STreeData void BuildLeaves(float H, bool conifer, bool palm) { - const std::vector rects = foliage_rects(strName, strCompositeTexture); + // Leaf clusters use the tree's leaf maps; a tree without leaf maps (palms, ferns, bare winter + // trees) gets its foliage from fronds in the SDK, which the proxy stands in for with the frond map. + std::vector rects = texCoords.vLeaves.empty() ? texCoords.vFronds : texCoords.vLeaves; + if (rects.empty()) + rects.push_back(UVRect()); const uint32_t seed = species_seed(strName); const int count = palm ? 16 : 24; for (int i = 0; i < count; ++i) @@ -418,8 +491,6 @@ struct STreeData afBoundingBox[3] = R; afBoundingBox[4] = R; afBoundingBox[5] = H; - fTrunkRadius = H * 0.035f; - fTrunkHeight = H * 0.75f; // PORT: the Godot host draws its own trees unless the native terrain renderer is on; the tree // then has no geometry so the forest submits nothing. @@ -846,18 +917,23 @@ void CSpeedTreeRT::GetBoundingBox(float * pBounds) const std::memcpy(pBounds, m_pImpl->pTree->afBoundingBox, 6 * sizeof(float)); } -unsigned int CSpeedTreeRT::GetCollisionObjectCount(void) { return 1; } - -void CSpeedTreeRT::GetCollisionObject(unsigned int /*nIndex*/, ECollisionObjectType & eType, float * pPosition, float * pDimensions) +unsigned int CSpeedTreeRT::GetCollisionObjectCount(void) { - const STreeData & tree = *m_pImpl->pTree; - eType = CO_CYLINDER; + return static_cast(m_pImpl->pTree->vCollisionObjects.size()); +} + +void CSpeedTreeRT::GetCollisionObject(unsigned int nIndex, ECollisionObjectType & eType, float * pPosition, float * pDimensions) +{ + const auto & objects = m_pImpl->pTree->vCollisionObjects; + if (nIndex >= objects.size()) + { + SetError("SpeedTreeRT: collision object index out of range"); + return; + } + const auto & o = objects[nIndex]; + eType = o.eType; if (pPosition) - pPosition[0] = pPosition[1] = pPosition[2] = 0.0f; + std::memcpy(pPosition, o.afPosition, sizeof(o.afPosition)); if (pDimensions) - { - pDimensions[0] = tree.fTrunkRadius; - pDimensions[1] = tree.fTrunkHeight; - pDimensions[2] = 0.0f; - } + std::memcpy(pDimensions, o.afDimensions, sizeof(o.afDimensions)); } diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt index 68de3532..24cbc467 100644 --- a/src/port/CMakeLists.txt +++ b/src/port/CMakeLists.txt @@ -200,6 +200,11 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME) target_link_libraries(port_miles_test PRIVATE port_platform) add_test(NAME port.miles COMMAND $) + # SpeedTreeLib: the CSpeedTreeRT stand-in's .spt size and collision blocks. + add_executable(port_speedtree_test ${PROJECT_SOURCE_DIR}/tests/port/port_speedtree_test.cpp) + target_link_libraries(port_speedtree_test PRIVATE port_platform) + add_test(NAME port.speedtree COMMAND $) + # 批次 2V1: Packet.h struct sizes against the classic wire layouts. add_executable(port_packet_test ${PROJECT_SOURCE_DIR}/tests/port/port_packet_test.cpp ${PROJECT_SOURCE_DIR}/tests/port/port_packet_test_wire.cpp) diff --git a/tests/port/port_speedtree_test.cpp b/tests/port/port_speedtree_test.cpp new file mode 100644 index 00000000..fa6e162d --- /dev/null +++ b/tests/port/port_speedtree_test.cpp @@ -0,0 +1,220 @@ +// SpeedTreeLib: the CSpeedTreeRT stand-in (platform/SpeedTreeLib/SpeedTreeRT.cpp) reads the .spt size and +// collision blocks the 40250 wrapper asks for (GetTreeSize, GetCollisionObject in OnUpdateCollisionData). +// The blocks are built here in the .spt token layout of the 40250 pack files. +#include "SpeedTreeLib/StdAfx.h" + +#include +#include "../../src/platform/EterLib/RenderCommands3D.h" + +#include +#include +#include +#include +#include +#include + +static int g_failures = 0; +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \ + ++g_failures; \ + } \ + } while (0) +#define CHECK_NEAR(a, b) CHECK(std::fabs(double(a) - double(b)) < 1e-3) + +namespace +{ + +struct Block +{ + std::vector bytes; + void i32(int32_t v) { bytes.insert(bytes.end(), reinterpret_cast(&v), reinterpret_cast(&v) + 4); } + void f32(float v) { bytes.insert(bytes.end(), reinterpret_cast(&v), reinterpret_cast(&v) + 4); } + void str(const std::string & s) + { + i32(int32_t(s.size())); + bytes.insert(bytes.end(), s.begin(), s.end()); + } +}; + +// 1000 "__IdvSpt_02_", 1002, 2000 , 2001 size, 2002 variance, then filler up to the collision block. +Block header(float size, float variance) +{ + Block b; + b.i32(1000); + b.str("__IdvSpt_02_"); + b.i32(1002); + b.i32(2000); + b.str("BaobabBark.tga"); + b.i32(2001); + b.f32(size); + b.i32(2002); + b.f32(variance); + // texture-coordinate block: two leaf maps, one frond map, one billboard; corners (+r,+u) (-r,+u) + // (-r,-u) (+r,-u) with v up (b1_baobab_rt.spt) + b.i32(10000); + b.i32(10002); + b.i32(2); + for (float f : { 0.5f, 0.25f, 0.25f, 0.25f, 0.25f, 0.0f, 0.5f, 0.0f }) + b.f32(f); + for (float f : { 0.25f, 0.25f, 0.0f, 0.25f, 0.0f, 0.0f, 0.25f, 0.0f }) + b.f32(f); + b.i32(10003); + b.i32(1); + for (float f : { 0.625f, 0.735f, 0.5f, 0.735f, 0.5f, 0.613f, 0.625f, 0.613f }) + b.f32(f); + b.i32(10004); + b.i32(1); + for (float f : { 0.75f, 0.985f, 0.625f, 0.985f, 0.625f, 0.735f, 0.75f, 0.735f }) + b.f32(f); + b.i32(10001); + return b; +} + +// The same block without leaf maps (palms, ferns, bare winter trees). +Block header_no_leaves() +{ + Block b; + b.i32(1000); + b.str("__IdvSpt_02_"); + b.i32(1002); + b.i32(2000); + b.str("PalmBark.tga"); + b.i32(2001); + b.f32(50.0f); + b.i32(2002); + b.f32(0.0f); + b.i32(10000); + b.i32(10002); + b.i32(0); + b.i32(10003); + b.i32(1); + for (float f : { 0.375f, 0.625f, 0.25f, 0.625f, 0.25f, 0.5f, 0.375f, 0.5f }) + b.f32(f); + b.i32(10004); + b.i32(0); + b.i32(10001); + return b; +} + +const float * leaf_texcoords(CSpeedTreeRT & tree, unsigned i) +{ + CSpeedTreeRT::SGeometry geometry; + tree.GetGeometry(geometry, SpeedTree_LeafGeometry); + CHECK(geometry.m_sLeaves0.m_usLeafCount > i); + return geometry.m_sLeaves0.m_usLeafCount > i ? geometry.m_sLeaves0.m_pLeafMapTexCoords[i] : nullptr; +} + +void test_leaf_texcoords() +{ + SetNativeTerrainRenderEnabled(true); + CSpeedTreeRT::SetTextureFlip(true); // as CSpeedTreeWrapper::LoadTree + + Block b = header(1100.0f, 150.0f); + CSpeedTreeRT tree; + CHECK(tree.LoadTree(b.bytes.data(), unsigned(b.bytes.size()))); + CHECK(tree.Compute(nullptr, 1)); + // leaf 0: leaf map 0, v flipped + if (const float * t = leaf_texcoords(tree, 0)) + { + const float want[8] = { 0.5f, 0.75f, 0.25f, 0.75f, 0.25f, 1.0f, 0.5f, 1.0f }; + for (int k = 0; k < 8; ++k) + CHECK_NEAR(t[k], want[k]); + } + // leaf 1: leaf map 1, mirrored in u by the proxy + if (const float * t = leaf_texcoords(tree, 1)) + { + const float want[8] = { 0.0f, 0.75f, 0.25f, 0.75f, 0.25f, 1.0f, 0.0f, 1.0f }; + for (int k = 0; k < 8; ++k) + CHECK_NEAR(t[k], want[k]); + } + + // no leaf maps: the proxy clusters use the frond map + Block palm = header_no_leaves(); + CSpeedTreeRT palmTree; + CHECK(palmTree.LoadTree(palm.bytes.data(), unsigned(palm.bytes.size()))); + CHECK(palmTree.Compute(nullptr, 1)); + if (const float * t = leaf_texcoords(palmTree, 0)) + { + const float want[8] = { 0.375f, 0.375f, 0.25f, 0.375f, 0.25f, 0.5f, 0.375f, 0.5f }; + for (int k = 0; k < 8; ++k) + CHECK_NEAR(t[k], want[k]); + } + SetNativeTerrainRenderEnabled(false); +} + +void test_collision() +{ + Block b = header(1100.0f, 150.0f); + b.i32(12000); + b.i32(12003); // cylinder x y z radius height + b.f32(0.0f); + b.f32(0.0f); + b.f32(0.0f); + b.f32(164.47f); + b.f32(582.55f); + b.i32(12002); // sphere x y z radius + b.f32(-10.44f); + b.f32(18.5f); + b.f32(775.42f); + b.f32(450.15f); + b.i32(12001); + b.i32(13000); + b.i32(13001); + + CSpeedTreeRT tree; + CHECK(tree.LoadTree(b.bytes.data(), unsigned(b.bytes.size()))); + float size = 0.0f, variance = 0.0f; + tree.GetTreeSize(size, variance); + CHECK_NEAR(size, 1100.0f); + CHECK_NEAR(variance, 150.0f); + + CHECK(tree.GetCollisionObjectCount() == 2); + CSpeedTreeRT::ECollisionObjectType type = CSpeedTreeRT::CO_BOX; + float pos[3] = {}, dim[3] = {}; + tree.GetCollisionObject(0, type, pos, dim); + CHECK(type == CSpeedTreeRT::CO_CYLINDER); + CHECK_NEAR(pos[0], 0.0f); + CHECK_NEAR(pos[2], 0.0f); + CHECK_NEAR(dim[0], 164.47f); + CHECK_NEAR(dim[1], 582.55f); + tree.GetCollisionObject(1, type, pos, dim); + CHECK(type == CSpeedTreeRT::CO_SPHERE); + CHECK_NEAR(pos[0], -10.44f); + CHECK_NEAR(pos[1], 18.5f); + CHECK_NEAR(pos[2], 775.42f); + CHECK_NEAR(dim[0], 450.15f); + + // instances share the tree data + CSpeedTreeRT * instance = tree.MakeInstance(); + CHECK(instance->GetCollisionObjectCount() == 2); + delete instance; +} + +void test_no_collision() +{ + // aloe flowers / ferns: no 12000 section, no collision objects + Block b = header(50.0f, 0.0f); + b.i32(13000); + b.i32(13001); + CSpeedTreeRT tree; + CHECK(tree.LoadTree(b.bytes.data(), unsigned(b.bytes.size()))); + CHECK(tree.GetCollisionObjectCount() == 0); +} + +} // namespace + +int main() +{ + test_collision(); + test_no_collision(); + test_leaf_texcoords(); + if (g_failures) + { + std::fprintf(stderr, "port_speedtree_test: %d failure(s)\n", g_failures); + return 1; + } + std::printf("port_speedtree_test: ok\n"); + return 0; +}