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>
221 lines
6.1 KiB
C++
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;
|
|
}
|