// 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; }