Files
mtgodot-poc/tests/port/port_speedtree_test.cpp
T
shenleiandClaude Opus 5.5 ed61b2d20f SpeedTree: read the .spt collision and composite texcoord blocks
The CSpeedTreeRT stand-in now returns the collision objects stored in
the .spt (12000 section: spheres and cylinders) instead of one fixed trunk
cylinder, and takes leaf texture rects from the 10000 section instead of
hand-picked atlas rects (trees without leaf maps use the frond rect).
SpeedGrass/BoundaryShapeManager are N_A (no 40250 caller).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:20:34 +09:00

221 lines
6.1 KiB
C++

// 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 <SpeedTreeRT.h>
#include "../../src/platform/EterLib/RenderCommands3D.h"
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
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<unsigned char> bytes;
void i32(int32_t v) { bytes.insert(bytes.end(), reinterpret_cast<unsigned char *>(&v), reinterpret_cast<unsigned char *>(&v) + 4); }
void f32(float v) { bytes.insert(bytes.end(), reinterpret_cast<unsigned char *>(&v), reinterpret_cast<unsigned char *>(&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 <bark>, 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;
}