// PORT: stand-in for the closed IDV SpeedTreeRT 1.6.0 library (40250 extern/library/SpeedTreeRT.lib, // Windows COFF). The class interface is the SDK header (port/common/shim/sdk/SpeedTreeRT.h) and the // 40250 SpeedTreeLib wrapper/forest call it unchanged; this file implements the members they call. // // What is the SDK's contract and kept here: // - GetGeometry fills SGeometry the way SpeedTreeWrapper reads it: one contiguous branch strip per // LOD, static-lit m_pColors (LIGHT_STATIC; light 0 from SetLightAttributes, the tree material), // m_pTexCoords1 self-shadow coordinates, camera-facing leaf clusters (m_pCenterCoords + // m_pLeafMapCoords rebuilt from SetCamera) with m_pLeafMapTexCoords, alpha test values of 84 // (SetLeafTargetAlphaMask default 0x54 == c_nDefaultAlphaTestValue). // - SetLodLevel(1.0f), which the wrapper always applies, selects discrete LOD 0 with billboards // 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. // 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. // - Materials are the SDK defaults (white); the .spt material block is not parsed. #include "SpeedTreeLib/StdAfx.h" #include #include #include "../EterLib/RenderCommands3D.h" #include #include #include #include #include #include #include #include #include namespace { constexpr float kPI = 3.14159265358979323846f; constexpr float kTAU = 2.0f * kPI; constexpr float kAlphaTestValue = 84.0f; // SetLeafTargetAlphaMask(0x54) default constexpr float kProxyHeight = 1000.0f; struct Vec3 { float x = 0.0f, y = 0.0f, z = 0.0f; Vec3() = default; Vec3(float a, float b, float c) : x(a), y(b), z(c) {} Vec3 operator+(const Vec3 & o) const { return { x + o.x, y + o.y, z + o.z }; } Vec3 operator-(const Vec3 & o) const { return { x - o.x, y - o.y, z - o.z }; } Vec3 operator*(float s) const { return { x * s, y * s, z * s }; } }; float dot(const Vec3 & a, const Vec3 & b) { return a.x * b.x + a.y * b.y + a.z * b.z; } Vec3 cross(const Vec3 & a, const Vec3 & b) { return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x }; } Vec3 normalize(const Vec3 & v) { const float l = std::sqrt(dot(v, v)); return l > 1e-6f ? v * (1.0f / l) : Vec3(0.0f, 0.0f, 1.0f); } std::string to_lower_str(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return s; } std::string basename_only(std::string path) { std::replace(path.begin(), path.end(), '\\', '/'); const size_t slash = path.find_last_of('/'); return slash == std::string::npos ? path : path.substr(slash + 1); } bool has_any(const std::string & hint, std::initializer_list keys) { const std::string h = to_lower_str(hint); for (const char * k : keys) if (h.find(k) != std::string::npos) return true; return false; } bool is_conifer_species(const std::string & hint) { return has_any(hint, { "cedar", "cypress", "pine", "fir", "spruce", "conifer", "juniper", "christmastree" }); } bool is_palm_species(const std::string & hint) { return has_any(hint, { "palm", "banana", "aloe", "fern", "joshua" }); } uint32_t hash32(uint32_t s) { s ^= s >> 16; s *= 0x7feb352dU; s ^= s >> 15; s *= 0x846ca68bU; s ^= s >> 16; return s; } float hash01(uint32_t s) { return float(hash32(s) & 0x00FFFFFFU) / float(0x01000000U); } uint32_t species_seed(const std::string & s) { uint32_t h = 2166136261U; for (unsigned char c : s) { h ^= c; h *= 16777619U; } return h; } // Composite-atlas leaf rects, 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) { 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) { 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 (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 } }; } // .spt header: 1000 "__IdvSpt_02_", 1002, 2000 , 2001 , // 2002 . bool read_spt_size(const unsigned char * p, size_t n, float & size, float & variance) { size_t o = 0; auto i32 = [&](int32_t & v) { if (o + 4 > n) return false; std::memcpy(&v, p + o, 4); o += 4; return true; }; int32_t t = 0, len = 0; if (!i32(t) || t != 1000 || !i32(len) || len < 0 || (o += size_t(len)) > n) return false; if (!i32(t) || t != 1002 || !i32(t) || t != 2000 || !i32(len) || len < 0 || (o += size_t(len)) > n) return false; int32_t bits = 0; if (!i32(t) || t != 2001 || !i32(bits)) return false; std::memcpy(&size, &bits, 4); if (!i32(t) || t != 2002 || !i32(bits)) return false; std::memcpy(&variance, &bits, 4); return true; } struct SStaticLight { float afAttributes[16] = { -0.707f, 0.0f, 0.707f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f }; bool bOn = false; }; SStaticLight g_aLights[8]; float g_afCameraPos[3] = { 0.0f, 0.0f, 0.0f }; float g_afCameraDir[3] = { 0.0f, 1.0f, 0.0f }; bool g_bTextureFlip = false; bool g_bDropToBillboard = false; std::string g_strError; void SetError(const char * pError) { g_strError = pError ? pError : ""; } const float c_afDefaultMaterial[13] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 5.0f }; // Static lighting (SLS_BASIC): material ambient x light ambient + material diffuse x light diffuse x N.L // for every light switched on, clamped per channel. unsigned long static_color(const Vec3 & n, const float * mat) { float rgb[3] = { 0.0f, 0.0f, 0.0f }; bool any = false; for (const SStaticLight & light : g_aLights) { if (!light.bOn) continue; any = true; const float * a = light.afAttributes; const float ndotl = std::max(0.0f, dot(n, normalize(Vec3(a[0], a[1], a[2])))); for (int c = 0; c < 3; ++c) rgb[c] += mat[3 + c] * a[6 + c] + mat[c] * a[3 + c] * ndotl; } if (!any) rgb[0] = rgb[1] = rgb[2] = 1.0f; auto channel = [](float v) { return static_cast(std::clamp(v, 0.0f, 1.0f) * 255.0f + 0.5f); }; return 0xFF000000UL | (channel(rgb[0]) << 16) | (channel(rgb[1]) << 8) | channel(rgb[2]); } // Geometry shared by a tree and its instances (the SDK shares it through m_pRefCount). struct STreeData { std::string strName; float fFileSize = -1.0f, fFileVariance = 0.0f; float fSize = -1.0f, fVariance = 0.0f; std::string strBranchTexture, strCompositeTexture, strSelfShadowTexture; // branches std::vector vBranchCoords, vBranchNormals, vBranchTex0, vBranchTex1; std::vector vBranchColors; std::vector vBranchStrip; unsigned short usBranchStripLength = 0; const unsigned short * pBranchStrip = nullptr; // leaves struct SCluster { Vec3 vCenter, vNormal; float fWidth = 0.0f, fHeight = 0.0f; UVRect rect; bool bFlip = false; }; std::vector vClusters; std::vector vLeafCenters, vLeafNormals, vLeafTexCoords, vLeafCoords; std::vector vLeafColors; std::vector vLeafMapIndices; 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; float afBranchMaterial[13], afFrondMaterial[13], afLeafMaterial[13]; float fLeafLightingAdjustment = 0.75f; bool bComputed = false; STreeData() { std::memcpy(afBranchMaterial, c_afDefaultMaterial, sizeof(afBranchMaterial)); std::memcpy(afFrondMaterial, c_afDefaultMaterial, sizeof(afFrondMaterial)); std::memcpy(afLeafMaterial, c_afDefaultMaterial, sizeof(afLeafMaterial)); } void Parse(const unsigned char * p, size_t n) { fmt::SptInfo info; fmt::sniff_spt(std::string(reinterpret_cast(p), n), info); for (const std::string & ref : info.texture_refs) if (to_lower_str(ref).find("bark") != std::string::npos) { strBranchTexture = basename_only(ref); break; } if (strBranchTexture.empty() && !info.texture_refs.empty()) strBranchTexture = basename_only(info.texture_refs.front()); strCompositeTexture = basename_only(info.composite_texture); strSelfShadowTexture = basename_only(info.self_shadow_texture); read_spt_size(p, n, fFileSize, fFileVariance); fSize = fFileSize; fVariance = fFileVariance; } void AppendStrip(const std::vector & sub) { if (sub.empty()) return; if (!vBranchStrip.empty()) { // degenerate join; keep the next strip on an even triangle so its winding is unchanged vBranchStrip.push_back(vBranchStrip.back()); vBranchStrip.push_back(sub.front()); if (vBranchStrip.size() & 1) vBranchStrip.push_back(sub.front()); } vBranchStrip.insert(vBranchStrip.end(), sub.begin(), sub.end()); } void AddTube(const Vec3 & from, const Vec3 & to, float r0, float r1, int seg, float bark_repeat) { Vec3 axis = to - from; if (dot(axis, axis) < 1e-6f) return; axis = normalize(axis); const Vec3 helper = std::fabs(axis.z) > 0.9f ? Vec3(1.0f, 0.0f, 0.0f) : Vec3(0.0f, 0.0f, 1.0f); const Vec3 u = normalize(cross(axis, helper)); const Vec3 w = normalize(cross(axis, u)); std::vector strip; for (int i = 0; i <= seg; ++i) { if (vBranchCoords.size() / 3 + 2 > 65530) break; const float a = float(i) / float(seg) * kTAU; const Vec3 n = u * std::cos(a) + w * std::sin(a); const float s = float(i) / float(seg); for (int end = 0; end < 2; ++end) { const Vec3 p = end ? to + n * r1 : from + n * r0; strip.push_back(static_cast(vBranchCoords.size() / 3)); vBranchCoords.insert(vBranchCoords.end(), { p.x, p.y, p.z }); vBranchNormals.insert(vBranchNormals.end(), { n.x, n.y, n.z }); vBranchTex0.insert(vBranchTex0.end(), { s, end ? 0.0f : bark_repeat }); } } AppendStrip(strip); } void BuildBranches(float H, bool conifer, bool palm) { const float trunk_top = H * (palm ? 0.82f : (conifer ? 0.90f : 0.76f)); const float trunk_r = H * (palm ? 0.028f : 0.035f); const float H_m = H * 0.01f; AddTube(Vec3(0.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, trunk_top), trunk_r * 1.35f, trunk_r * 0.42f, 9, H_m * 0.22f); if (palm) return; const int count = conifer ? 9 : 8; const uint32_t seed = species_seed(strName); for (int i = 0; i < count; ++i) { const float f = (i + 1.0f) / (count + 1.0f); const float z = H * (conifer ? (0.28f + f * 0.52f) : (0.32f + f * 0.34f)); const float angle = kTAU * (f * 1.6180339f + hash01(seed + i * 17U)); const float len = H * (conifer ? (0.24f * (1.0f - f * 0.55f)) : (0.18f + 0.08f * hash01(seed + i * 29U))); const Vec3 from(0.0f, 0.0f, z); const Vec3 to(std::cos(angle) * len, std::sin(angle) * len, z + H * (conifer ? 0.06f : (0.10f + 0.06f * hash01(seed + i * 31U)))); AddTube(from, to, trunk_r * (0.55f - 0.20f * f), trunk_r * 0.12f, 6, H_m * 0.08f); if (!conifer && (i % 2 == 0)) { const float side = angle + (hash01(seed + i * 37U) > 0.5f ? 0.65f : -0.65f); const Vec3 tip = to + Vec3(std::cos(side), std::sin(side), 0.65f) * (len * 0.42f); AddTube(to, tip, trunk_r * 0.16f, trunk_r * 0.05f, 5, H_m * 0.04f); } } } void BuildLeaves(float H, bool conifer, bool palm) { const std::vector rects = foliage_rects(strName, strCompositeTexture); const uint32_t seed = species_seed(strName); const int count = palm ? 16 : 24; for (int i = 0; i < count; ++i) { const float a = kTAU * (float(i) * 0.6180339f + hash01(seed + i * 101U) * 0.15f); SCluster c; if (palm) { const float radial = H * (0.10f + 0.18f * hash01(seed + i * 103U)); c.vCenter = Vec3(std::cos(a) * radial, std::sin(a) * radial, H * (0.78f + 0.12f * hash01(seed + i * 107U))); c.fWidth = H * 0.32f; c.fHeight = H * 0.18f; } else if (conifer) { const float zf = 0.30f + 0.62f * (float(i) + 0.5f) / float(count); const float radial = H * 0.23f * (1.0f - zf * 0.70f) * (0.35f + 0.65f * hash01(seed + i * 109U)); c.vCenter = Vec3(std::cos(a) * radial, std::sin(a) * radial, H * zf); c.fWidth = H * (0.18f + 0.10f * (1.0f - zf)); c.fHeight = H * 0.18f; } else { const float zf = hash01(seed + i * 109U); const float zn = zf * 2.0f - 1.0f; const float radial = H * 0.34f * std::sqrt(std::max(0.05f, 1.0f - zn * zn)) * (0.25f + 0.75f * std::sqrt(hash01(seed + i * 113U))); c.vCenter = Vec3(std::cos(a) * radial, std::sin(a) * radial, H * (0.58f + zf * 0.34f)); c.fWidth = H * (0.23f + 0.10f * hash01(seed + i * 127U)); c.fHeight = H * (0.15f + 0.08f * hash01(seed + i * 131U)); } // cluster normal: away from the crown centre, biased up const Vec3 out = normalize(c.vCenter - Vec3(0.0f, 0.0f, H * 0.6f)); c.vNormal = normalize(Vec3(out.x, out.y, std::fabs(out.z) * 0.5f + 0.5f)); c.rect = rects[size_t(i) % rects.size()]; c.bFlip = (i & 1) != 0; vClusters.push_back(c); } } void Compute() { const bool enabled = IsNativeTerrainRenderEnabled(); const float H = kProxyHeight; const float R = H * 0.35f; afBoundingBox[0] = -R; afBoundingBox[1] = -R; afBoundingBox[2] = 0.0f; 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. if (enabled) { const bool conifer = is_conifer_species(strName); const bool palm = is_palm_species(strName); BuildBranches(H, conifer, palm); BuildLeaves(H, conifer, palm); } const size_t nBranchVerts = vBranchCoords.size() / 3; vBranchColors.resize(nBranchVerts); vBranchTex1.resize(nBranchVerts * 2); for (size_t i = 0; i < nBranchVerts; ++i) { const Vec3 n(vBranchNormals[i * 3], vBranchNormals[i * 3 + 1], vBranchNormals[i * 3 + 2]); vBranchColors[i] = static_color(n, afBranchMaterial); // self-shadow map projected straight down over the bounding box vBranchTex1[i * 2] = (vBranchCoords[i * 3] - afBoundingBox[0]) / (afBoundingBox[3] - afBoundingBox[0]); vBranchTex1[i * 2 + 1] = (vBranchCoords[i * 3 + 1] - afBoundingBox[1]) / (afBoundingBox[4] - afBoundingBox[1]); } if (g_bTextureFlip) for (size_t i = 0; i < nBranchVerts; ++i) { vBranchTex0[i * 2 + 1] = 1.0f - vBranchTex0[i * 2 + 1]; vBranchTex1[i * 2 + 1] = 1.0f - vBranchTex1[i * 2 + 1]; } usBranchStripLength = static_cast(vBranchStrip.size()); pBranchStrip = vBranchStrip.empty() ? nullptr : vBranchStrip.data(); const size_t nLeaves = vClusters.size(); vLeafCenters.resize(nLeaves * 3); vLeafNormals.resize(nLeaves * 3); vLeafColors.resize(nLeaves); vLeafMapIndices.assign(nLeaves, 0); vLeafTexCoords.resize(nLeaves * 8); vLeafCoords.assign(nLeaves * 16, 0.0f); vLeafTexPtrs.resize(nLeaves); vLeafCoordPtrs.resize(nLeaves); for (size_t i = 0; i < nLeaves; ++i) { const SCluster & c = vClusters[i]; std::memcpy(&vLeafCenters[i * 3], &c.vCenter, 12); std::memcpy(&vLeafNormals[i * 3], &c.vNormal, 12); vLeafColors[i] = static_color(c.vNormal, afLeafMaterial); // corners 0..3: (+r,+u) (-r,+u) (-r,-u) (+r,-u); drawn as 0,1,2 / 0,2,3 const float uR = c.bFlip ? c.rect.u0 : c.rect.u1, uL = c.bFlip ? c.rect.u1 : c.rect.u0; float * t = &vLeafTexCoords[i * 8]; const float vTop = g_bTextureFlip ? c.rect.v0 : 1.0f - c.rect.v0; const float vBottom = g_bTextureFlip ? c.rect.v1 : 1.0f - c.rect.v1; t[0] = uR; t[1] = vTop; t[2] = uL; t[3] = vTop; t[4] = uL; t[5] = vBottom; t[6] = uR; t[7] = vBottom; vLeafTexPtrs[i] = t; vLeafCoordPtrs[i] = &vLeafCoords[i * 16]; } bComputed = true; } // SDK leaf clusters face the camera given to SetCamera. void UpdateLeafCoords() { const Vec3 dir = normalize(Vec3(g_afCameraDir[0], g_afCameraDir[1], g_afCameraDir[2])); Vec3 right = cross(dir, Vec3(0.0f, 0.0f, 1.0f)); right = dot(right, right) < 1e-6f ? Vec3(1.0f, 0.0f, 0.0f) : normalize(right); const Vec3 up = normalize(cross(right, dir)); for (size_t i = 0; i < vClusters.size(); ++i) { const SCluster & c = vClusters[i]; const Vec3 r = right * (c.fWidth * 0.5f), u = up * (c.fHeight * 0.5f); const Vec3 corners[4] = { r + u, u - r, Vec3() - r - u, r - u }; float * p = &vLeafCoords[i * 16]; for (int k = 0; k < 4; ++k) { p[k * 4 + 0] = corners[k].x; p[k * 4 + 1] = corners[k].y; p[k * 4 + 2] = corners[k].z; p[k * 4 + 3] = 0.0f; } } } }; } // namespace struct CSpeedTreeRT::SImpl { std::shared_ptr pTree = std::make_shared(); const CSpeedTreeRT * pInstanceOf = nullptr; float afPosition[3] = { 0.0f, 0.0f, 0.0f }; float fLodLevel = 1.0f; float fLodNear = 100.0f, fLodFar = 1000.0f; float fWindStrength = 0.0f; unsigned int uiLocalStart = 0, uiLocalSpan = 0; unsigned int uiSeed = 1; bool bLeafRocking = false; unsigned int uiRockingGroups = 1; ELightingMethod eBranchLighting = LIGHT_STATIC, eLeafLighting = LIGHT_STATIC, eFrondLighting = LIGHT_STATIC; EWindMethod eBranchWind = WIND_NONE, eLeafWind = WIND_NONE, eFrondWind = WIND_NONE; float afLeafLodSizes[1] = { 1.0f }; }; CSpeedTreeRT::SGeometry::SIndexed::SIndexed() : m_nDiscreteLodLevel(-1), m_usNumStrips(0), m_pStripLengths(nullptr), m_pStrips(nullptr), m_usVertexCount(0), m_pColors(nullptr), m_pNormals(nullptr), m_pBinormals(nullptr), m_pTangents(nullptr), m_pCoords(nullptr), m_pTexCoords0(nullptr), m_pTexCoords1(nullptr), m_pWindWeights(nullptr), m_pWindMatrixIndices(nullptr) { } CSpeedTreeRT::SGeometry::SIndexed::~SIndexed() {} CSpeedTreeRT::SGeometry::SLeaf::SLeaf() : m_bIsActive(false), m_fAlphaTestValue(0.0f), m_nDiscreteLodLevel(-1), m_usLeafCount(0), m_pLeafMapIndices(nullptr), m_pLeafClusterIndices(nullptr), m_pCenterCoords(nullptr), m_pLeafMapTexCoords(nullptr), m_pLeafMapCoords(nullptr), m_pColors(nullptr), m_pNormals(nullptr), m_pBinormals(nullptr), m_pTangents(nullptr), m_pWindWeights(nullptr), m_pWindMatrixIndices(nullptr) { } CSpeedTreeRT::SGeometry::SLeaf::~SLeaf() {} CSpeedTreeRT::SGeometry::SBillboard::SBillboard() : m_bIsActive(false), m_pTexCoords(nullptr), m_pCoords(nullptr), m_fAlphaTestValue(0.0f) { } CSpeedTreeRT::SGeometry::SBillboard::~SBillboard() {} CSpeedTreeRT::SGeometry::SGeometry() : m_fBranchAlphaTestValue(0.0f), m_fFrondAlphaTestValue(0.0f) {} CSpeedTreeRT::SGeometry::~SGeometry() {} CSpeedTreeRT::STextures::STextures() : m_pBranchTextureFilename(nullptr), m_uiLeafTextureCount(0), m_pLeafTextureFilenames(nullptr), m_uiFrondTextureCount(0), m_pFrondTextureFilenames(nullptr), m_pCompositeFilename(nullptr), m_pSelfShadowFilename(nullptr) { } CSpeedTreeRT::STextures::~STextures() {} CSpeedTreeRT::CSpeedTreeRT() : m_pImpl(new SImpl) {} CSpeedTreeRT::~CSpeedTreeRT() { delete m_pImpl; } void * CSpeedTreeRT::operator new(size_t nSize) { return ::operator new(nSize); } void * CSpeedTreeRT::operator new[](size_t nSize) { return ::operator new[](nSize); } void CSpeedTreeRT::operator delete(void * pRawMemory) { ::operator delete(pRawMemory); } void CSpeedTreeRT::operator delete[](void * pRawMemory) { ::operator delete[](pRawMemory); } bool CSpeedTreeRT::LoadTree(const char * pFilename) { SetError(pFilename ? "SpeedTreeRT: file loading is not supported, pass the pack block" : "SpeedTreeRT: no file name"); return false; } bool CSpeedTreeRT::LoadTree(const unsigned char * pBlock, unsigned int nNumBytes) { if (!pBlock || nNumBytes < 16) { SetError("SpeedTreeRT: empty tree block"); return false; } m_pImpl->pTree = std::make_shared(); m_pImpl->pTree->Parse(pBlock, nNumBytes); return true; } bool CSpeedTreeRT::Compute(const float * /*pTransform*/, unsigned int nSeed, bool /*bCompositeStrips*/) { STreeData & tree = *m_pImpl->pTree; if (tree.bComputed) { SetError("SpeedTreeRT: tree already computed"); return false; } m_pImpl->uiSeed = nSeed; tree.Compute(); return true; } CSpeedTreeRT * CSpeedTreeRT::MakeInstance(void) { CSpeedTreeRT * pInstance = new CSpeedTreeRT; *pInstance->m_pImpl = *m_pImpl; pInstance->m_pImpl->pInstanceOf = this; return pInstance; } const CSpeedTreeRT * CSpeedTreeRT::InstanceOf(void) const { return m_pImpl->pInstanceOf; } void CSpeedTreeRT::DeleteTransientData(void) {} void CSpeedTreeRT::GetTreeSize(float & fSize, float & fVariance) const { fSize = m_pImpl->pTree->fSize; fVariance = m_pImpl->pTree->fVariance; } void CSpeedTreeRT::SetTreeSize(float fNewSize, float fNewVariance) { m_pImpl->pTree->fSize = fNewSize; m_pImpl->pTree->fVariance = fNewVariance; } unsigned int CSpeedTreeRT::GetSeed() const { return m_pImpl->uiSeed; } const float * CSpeedTreeRT::GetTreePosition(void) const { return m_pImpl->afPosition; } void CSpeedTreeRT::SetTreePosition(float x, float y, float z) { m_pImpl->afPosition[0] = x; m_pImpl->afPosition[1] = y; m_pImpl->afPosition[2] = z; } CSpeedTreeRT::ELightingMethod CSpeedTreeRT::GetBranchLightingMethod(void) const { return m_pImpl->eBranchLighting; } void CSpeedTreeRT::SetBranchLightingMethod(ELightingMethod eMethod) { m_pImpl->eBranchLighting = eMethod; } CSpeedTreeRT::ELightingMethod CSpeedTreeRT::GetLeafLightingMethod(void) const { return m_pImpl->eLeafLighting; } void CSpeedTreeRT::SetLeafLightingMethod(ELightingMethod eMethod) { m_pImpl->eLeafLighting = eMethod; } CSpeedTreeRT::ELightingMethod CSpeedTreeRT::GetFrondLightingMethod(void) const { return m_pImpl->eFrondLighting; } void CSpeedTreeRT::SetFrondLightingMethod(ELightingMethod eMethod) { m_pImpl->eFrondLighting = eMethod; } float CSpeedTreeRT::GetLeafLightingAdjustment() const { return m_pImpl->pTree->fLeafLightingAdjustment; } void CSpeedTreeRT::SetLeafLightingAdjustment(float fScalar) { m_pImpl->pTree->fLeafLightingAdjustment = fScalar; } bool CSpeedTreeRT::GetLightState(unsigned int nLightIndex) { return nLightIndex < 8 && g_aLights[nLightIndex].bOn; } void CSpeedTreeRT::SetLightState(unsigned int nLightIndex, bool bLightOn) { if (nLightIndex < 8) g_aLights[nLightIndex].bOn = bLightOn; } const float * CSpeedTreeRT::GetLightAttributes(unsigned int nLightIndex) { return nLightIndex < 8 ? g_aLights[nLightIndex].afAttributes : nullptr; } void CSpeedTreeRT::SetLightAttributes(unsigned int nLightIndex, const float * pLightAttributes) { if (nLightIndex < 8 && pLightAttributes) std::memcpy(g_aLights[nLightIndex].afAttributes, pLightAttributes, sizeof(g_aLights[nLightIndex].afAttributes)); } const float * CSpeedTreeRT::GetBranchMaterial(void) const { return m_pImpl->pTree->afBranchMaterial; } void CSpeedTreeRT::SetBranchMaterial(const float * pMaterial) { std::memcpy(m_pImpl->pTree->afBranchMaterial, pMaterial, 13 * sizeof(float)); } const float * CSpeedTreeRT::GetLeafMaterial(void) const { return m_pImpl->pTree->afLeafMaterial; } void CSpeedTreeRT::SetLeafMaterial(const float * pMaterial) { std::memcpy(m_pImpl->pTree->afLeafMaterial, pMaterial, 13 * sizeof(float)); } const float * CSpeedTreeRT::GetFrondMaterial(void) const { return m_pImpl->pTree->afFrondMaterial; } void CSpeedTreeRT::SetFrondMaterial(const float * pMaterial) { std::memcpy(m_pImpl->pTree->afFrondMaterial, pMaterial, 13 * sizeof(float)); } void CSpeedTreeRT::GetCamera(float * pPosition, float * pDirection) { if (pPosition) std::memcpy(pPosition, g_afCameraPos, sizeof(g_afCameraPos)); if (pDirection) std::memcpy(pDirection, g_afCameraDir, sizeof(g_afCameraDir)); } void CSpeedTreeRT::SetCamera(const float * pPosition, const float * pDirection) { if (pPosition) std::memcpy(g_afCameraPos, pPosition, sizeof(g_afCameraPos)); if (pDirection) std::memcpy(g_afCameraDir, pDirection, sizeof(g_afCameraDir)); } // WRAPPER_USE_NO_WIND: the wind entry points only keep their state. void CSpeedTreeRT::SetTime(float /*fTime*/) {} void CSpeedTreeRT::ComputeWindEffects(bool /*bBranches*/, bool /*bLeaves*/, bool /*bFronds*/) {} void CSpeedTreeRT::ResetLeafWindState(void) {} bool CSpeedTreeRT::GetLeafRockingState(void) const { return m_pImpl->bLeafRocking; } void CSpeedTreeRT::SetLeafRockingState(bool bFlag) { m_pImpl->bLeafRocking = bFlag; } void CSpeedTreeRT::SetNumLeafRockingGroups(unsigned int nRockingGroups) { m_pImpl->uiRockingGroups = nRockingGroups; } CSpeedTreeRT::EWindMethod CSpeedTreeRT::GetLeafWindMethod(void) const { return m_pImpl->eLeafWind; } void CSpeedTreeRT::SetLeafWindMethod(EWindMethod eMethod) { m_pImpl->eLeafWind = eMethod; } CSpeedTreeRT::EWindMethod CSpeedTreeRT::GetBranchWindMethod(void) const { return m_pImpl->eBranchWind; } void CSpeedTreeRT::SetBranchWindMethod(EWindMethod eMethod) { m_pImpl->eBranchWind = eMethod; } CSpeedTreeRT::EWindMethod CSpeedTreeRT::GetFrondWindMethod(void) const { return m_pImpl->eFrondWind; } void CSpeedTreeRT::SetFrondWindMethod(EWindMethod eMethod) { m_pImpl->eFrondWind = eMethod; } float CSpeedTreeRT::GetWindStrength(void) const { return m_pImpl->fWindStrength; } float CSpeedTreeRT::SetWindStrength(float fNewStrength, float /*fOldStrength*/, float /*fFrequencyTimeOffset*/) { const float fOld = m_pImpl->fWindStrength; m_pImpl->fWindStrength = fNewStrength; return fOld; } void CSpeedTreeRT::SetNumWindMatrices(unsigned int /*nNumMatrices*/) {} void CSpeedTreeRT::SetWindMatrix(unsigned int /*nMatrixIndex*/, const float * /*pMatrix*/) {} void CSpeedTreeRT::GetLocalMatrices(unsigned int & nStartingIndex, unsigned int & nMatrixSpan) { nStartingIndex = m_pImpl->uiLocalStart; nMatrixSpan = m_pImpl->uiLocalSpan; } void CSpeedTreeRT::SetLocalMatrices(unsigned int nStartingMatrix, unsigned int nMatrixSpan) { m_pImpl->uiLocalStart = nStartingMatrix; m_pImpl->uiLocalSpan = nMatrixSpan; } void CSpeedTreeRT::ComputeLodLevel(void) { const float dx = m_pImpl->afPosition[0] - g_afCameraPos[0]; const float dy = m_pImpl->afPosition[1] - g_afCameraPos[1]; const float dz = m_pImpl->afPosition[2] - g_afCameraPos[2]; const float fDistance = std::sqrt(dx * dx + dy * dy + dz * dz); const float fRange = std::max(1e-3f, m_pImpl->fLodFar - m_pImpl->fLodNear); m_pImpl->fLodLevel = 1.0f - std::clamp((fDistance - m_pImpl->fLodNear) / fRange, 0.0f, 1.0f); } float CSpeedTreeRT::GetLodLevel(void) const { return m_pImpl->fLodLevel; } void CSpeedTreeRT::SetLodLevel(float fLodLevel) { m_pImpl->fLodLevel = std::clamp(fLodLevel, 0.0f, 1.0f); } void CSpeedTreeRT::SetDropToBillboard(bool bFlag) { g_bDropToBillboard = bFlag; } void CSpeedTreeRT::GetLodLimits(float & fNear, float & fFar) const { fNear = m_pImpl->fLodNear; fFar = m_pImpl->fLodFar; } void CSpeedTreeRT::SetLodLimits(float fNear, float fFar) { m_pImpl->fLodNear = fNear; m_pImpl->fLodFar = fFar; } short CSpeedTreeRT::GetDiscreteBranchLodLevel(float /*fLodLevel*/) const { return 0; } unsigned short CSpeedTreeRT::GetDiscreteLeafLodLevel(float /*fLodLevel*/) const { return 0; } short CSpeedTreeRT::GetDiscreteFrondLodLevel(float /*fLodLevel*/) const { return 0; } unsigned short CSpeedTreeRT::GetNumBranchLodLevels(void) const { return 1; } unsigned short CSpeedTreeRT::GetNumLeafLodLevels(void) const { return 1; } unsigned short CSpeedTreeRT::GetNumFrondLodLevels(void) const { return 1; } const float * CSpeedTreeRT::GetLeafBillboardTable(unsigned int & nEntryCount) const { nEntryCount = 0; return nullptr; } const float * CSpeedTreeRT::GetLeafLodSizeAdjustments(void) { return m_pImpl->afLeafLodSizes; } void CSpeedTreeRT::GetGeometry(SGeometry & sGeometry, unsigned long ulBitVector, short /*sOverrideBranchLodValue*/, short /*sOverrideFrondLodValue*/, short /*sOverrideLeafLodValue*/) { STreeData & tree = *m_pImpl->pTree; if (ulBitVector & SpeedTree_BranchGeometry) { SGeometry::SIndexed & b = sGeometry.m_sBranches; b = SGeometry::SIndexed(); b.m_nDiscreteLodLevel = 0; b.m_usVertexCount = static_cast(tree.vBranchCoords.size() / 3); b.m_usNumStrips = tree.usBranchStripLength ? 1 : 0; b.m_pStripLengths = &tree.usBranchStripLength; b.m_pStrips = &tree.pBranchStrip; b.m_pColors = tree.vBranchColors.data(); b.m_pNormals = tree.vBranchNormals.data(); b.m_pCoords = tree.vBranchCoords.data(); b.m_pTexCoords0 = tree.vBranchTex0.data(); b.m_pTexCoords1 = tree.vBranchTex1.data(); sGeometry.m_fBranchAlphaTestValue = kAlphaTestValue; } if (ulBitVector & SpeedTree_FrondGeometry) { sGeometry.m_sFronds = SGeometry::SIndexed(); sGeometry.m_sFronds.m_nDiscreteLodLevel = 0; sGeometry.m_fFrondAlphaTestValue = kAlphaTestValue; } if (ulBitVector & SpeedTree_LeafGeometry) { tree.UpdateLeafCoords(); SGeometry::SLeaf & l = sGeometry.m_sLeaves0; l = SGeometry::SLeaf(); l.m_bIsActive = !tree.vClusters.empty(); l.m_fAlphaTestValue = kAlphaTestValue; l.m_nDiscreteLodLevel = 0; l.m_usLeafCount = static_cast(tree.vClusters.size()); l.m_pLeafMapIndices = tree.vLeafMapIndices.data(); l.m_pCenterCoords = tree.vLeafCenters.data(); l.m_pLeafMapTexCoords = tree.vLeafTexPtrs.data(); l.m_pLeafMapCoords = tree.vLeafCoordPtrs.data(); l.m_pColors = tree.vLeafColors.data(); l.m_pNormals = tree.vLeafNormals.data(); sGeometry.m_sLeaves1 = SGeometry::SLeaf(); } if (ulBitVector & SpeedTree_BillboardGeometry) { sGeometry.m_sBillboard0 = SGeometry::SBillboard(); sGeometry.m_sBillboard1 = SGeometry::SBillboard(); sGeometry.m_sHorizontalBillboard = SGeometry::SBillboard(); } } void CSpeedTreeRT::GetTextures(STextures & sTextures) const { const STreeData & tree = *m_pImpl->pTree; sTextures = STextures(); sTextures.m_pBranchTextureFilename = tree.strBranchTexture.c_str(); sTextures.m_pCompositeFilename = tree.strCompositeTexture.empty() ? nullptr : tree.strCompositeTexture.c_str(); sTextures.m_pSelfShadowFilename = tree.strSelfShadowTexture.empty() ? nullptr : tree.strSelfShadowTexture.c_str(); } bool CSpeedTreeRT::GetTextureFlip(void) { return g_bTextureFlip; } void CSpeedTreeRT::SetTextureFlip(bool bFlag) { g_bTextureFlip = bFlag; } const char * CSpeedTreeRT::GetCurrentError(void) { return g_strError.c_str(); } void CSpeedTreeRT::ResetError(void) { g_strError.clear(); } 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) { const STreeData & tree = *m_pImpl->pTree; eType = CO_CYLINDER; if (pPosition) pPosition[0] = pPosition[1] = pPosition[2] = 0.0f; if (pDimensions) { pDimensions[0] = tree.fTrunkRadius; pDimensions[1] = tree.fTrunkHeight; pDimensions[2] = 0.0f; } }