Files
metin2-client/extension/src/platform/SpeedTreeLib/SpeedTreeWrapper.cpp
T
2026-09-27 19:44:41 -07:00

1098 lines
31 KiB
C++

#include "SpeedTreeLib/StdAfx.h"
#include "SpeedTreeLib/SpeedTreeWrapper.h"
#include "SpeedTreeLib/SpeedTreeForestDirectX8.h"
#include "EterBase/Filename.h"
#include "EterBase/Timer.h"
#include "EterLib/Camera.h"
#include "EterLib/ResourceManager.h"
#include "EterLib/StateManager.h"
#include "EterPack/EterPackManager.h"
#include "../EterLib/RenderCommands3D.h"
#include <spt.h>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
struct CSpeedTreeRT::SGeometry
{
std::string species;
std::string composite_name;
float height = 1000.0f;
float variance = 0.0f;
float radius = 350.0f;
float trunk_radius = 35.0f;
float trunk_height = 750.0f;
uint32_t branch_index_count = 0;
uint32_t leaf_vertex_count = 0;
};
struct CSpeedTreeRT::STextures
{
std::string branch_tex;
std::string composite_tex;
std::string shadow_tex;
};
float CSpeedTreeRT::GetLeafLightingAdjustment() const
{
return 0.5f;
}
float CSpeedTreeRT::SetWindStrength(float fNewStrength, float /*fOldStrength*/, float /*fFrequencyTimeOffset*/)
{
return fNewStrength;
}
void CSpeedTreeRT::GetCollisionObject(unsigned int /*nIndex*/, ECollisionObjectType & eType, float * pPosition, float * pDimensions)
{
eType = CO_CYLINDER;
if (pPosition)
{
pPosition[0] = 0.0f;
pPosition[1] = 0.0f;
pPosition[2] = 0.0f;
}
if (pDimensions)
{
pDimensions[0] = 35.0f;
pDimensions[1] = 600.0f;
pDimensions[2] = 0.0f;
}
}
namespace
{
constexpr float kPI = 3.14159265358979323846f;
constexpr float kTAU = 2.0f * kPI;
constexpr DWORD kTreeFVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1;
std::string to_lower_str(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return s;
}
std::string basename_only(std::string path)
{
for (char & c : path)
{
if (c == '\\')
c = '/';
}
const size_t slash = path.find_last_of('/');
return slash == std::string::npos ? path : path.substr(slash + 1);
}
std::string replace_ext_dds(std::string path)
{
const size_t dot = path.find_last_of('.');
if (dot != std::string::npos)
path.resize(dot);
return path + ".dds";
}
std::string sibling_dds_path(const std::string & spt_file, const std::string & tex_ref)
{
if (tex_ref.empty())
return "";
std::string spt_copy = spt_file;
std::string dir = CFileNameHelper::GetPath(spt_copy);
std::string name = replace_ext_dds(basename_only(tex_ref));
return dir + name;
}
bool is_conifer_species(const std::string & hint)
{
const std::string h = to_lower_str(hint);
static const char * kw[] = {
"cedar", "cypress", "pine", "fir", "spruce", "conifer", "juniper", "christmastree"
};
for (const char * k : kw)
{
if (h.find(k) != std::string::npos)
return true;
}
return false;
}
bool is_palm_species(const std::string & hint)
{
const std::string h = to_lower_str(hint);
static const char * kw[] = { "palm", "banana", "aloe", "fern", "joshua" };
for (const char * k : kw)
{
if (h.find(k) != std::string::npos)
return true;
}
return false;
}
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;
}
struct UVRect
{
float u0 = 0.0f, v0 = 0.0f, u1 = 1.0f, v1 = 1.0f;
};
std::vector<UVRect> foliage_rects(const std::string & species, const std::string & composite, bool atlas)
{
if (!atlas)
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 } };
}
struct TreeVertex
{
TPosition position;
TNormal normal;
DWORD diffuse;
TTextureCoordinate texCoord;
};
struct TreeMeshBuilder
{
std::vector<TreeVertex> verts;
std::vector<uint16_t> indices;
DWORD color = 0xFFFFFFFFu;
static TreeVertex make_vtx(const D3DXVECTOR3 & p, const D3DXVECTOR3 & n, DWORD col, float u, float v)
{
TreeVertex out{};
out.position = TPosition(p.x, p.y, p.z);
out.normal = TNormal(n.x, n.y, n.z);
out.diffuse = col;
out.texCoord = TTextureCoordinate(u, v);
return out;
}
void add_tube_quad(
const D3DXVECTOR3 & b0,
const D3DXVECTOR3 & b1,
const D3DXVECTOR3 & t1,
const D3DXVECTOR3 & t0,
const D3DXVECTOR3 & n0,
const D3DXVECTOR3 & n1,
float u0,
float u1,
float v0,
float v1)
{
if (verts.size() + 4 > 65530)
return;
const uint16_t base = static_cast<uint16_t>(verts.size());
verts.push_back(make_vtx(b0, n0, color, u0, v0));
verts.push_back(make_vtx(b1, n1, color, u1, v0));
verts.push_back(make_vtx(t1, n1, color, u1, v1));
verts.push_back(make_vtx(t0, n0, color, u0, v1));
indices.push_back(base + 0);
indices.push_back(base + 2);
indices.push_back(base + 1);
indices.push_back(base + 0);
indices.push_back(base + 3);
indices.push_back(base + 2);
}
void add_leaf_quad_unindexed(
const D3DXVECTOR3 & a,
const D3DXVECTOR3 & b,
const D3DXVECTOR3 & c,
const D3DXVECTOR3 & d,
const UVRect & r,
bool flip_u)
{
const D3DXVECTOR3 ab = b - a;
const D3DXVECTOR3 ad = d - a;
D3DXVECTOR3 nn;
D3DXVec3Cross(&nn, &ab, &ad);
D3DXVec3Normalize(&nn, &nn);
// Bias leaf normals upward slightly for softer canopy lighting
nn.z = std::fabs(nn.z) * 0.5f + 0.5f;
D3DXVec3Normalize(&nn, &nn);
const float l = flip_u ? r.u1 : r.u0;
const float rr = flip_u ? r.u0 : r.u1;
const TreeVertex v0 = make_vtx(a, nn, color, l, r.v1);
const TreeVertex v1 = make_vtx(b, nn, color, rr, r.v1);
const TreeVertex v2 = make_vtx(c, nn, color, rr, r.v0);
const TreeVertex v3 = make_vtx(d, nn, color, l, r.v0);
verts.push_back(v0);
verts.push_back(v1);
verts.push_back(v2);
verts.push_back(v0);
verts.push_back(v2);
verts.push_back(v3);
}
};
// Z-up tube generator (from/to in Metin2 Z-up centimeters)
void add_tube_zup(
TreeMeshBuilder & m,
const D3DXVECTOR3 & from,
const D3DXVECTOR3 & to,
float r0,
float r1,
int seg,
float bark_repeat)
{
D3DXVECTOR3 axis = to - from;
const float len = D3DXVec3Length(&axis);
if (len < 1e-3f)
return;
axis /= len;
const D3DXVECTOR3 helper = (std::fabs(axis.z) > 0.9f) ? D3DXVECTOR3(1.0f, 0.0f, 0.0f) : D3DXVECTOR3(0.0f, 0.0f, 1.0f);
D3DXVECTOR3 u, w;
D3DXVec3Cross(&u, &axis, &helper);
D3DXVec3Normalize(&u, &u);
D3DXVec3Cross(&w, &axis, &u);
D3DXVec3Normalize(&w, &w);
for (int i = 0; i < seg; ++i)
{
const float a0 = float(i) / float(seg) * kTAU;
const float a1 = float(i + 1) / float(seg) * kTAU;
const D3DXVECTOR3 n0 = u * std::cos(a0) + w * std::sin(a0);
const D3DXVECTOR3 n1 = u * std::cos(a1) + w * std::sin(a1);
m.add_tube_quad(
from + n0 * r0,
from + n1 * r0,
to + n1 * r1,
to + n0 * r1,
n0,
n1,
float(i) / float(seg),
float(i + 1) / float(seg),
bark_repeat,
0.0f);
}
}
void build_branches_zup(
TreeMeshBuilder & wood,
const std::string & species,
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;
add_tube_zup(
wood,
D3DXVECTOR3(0.0f, 0.0f, 0.0f),
D3DXVECTOR3(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(species);
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 D3DXVECTOR3 from(0.0f, 0.0f, z);
const D3DXVECTOR3 to(
std::cos(angle) * len,
std::sin(angle) * len,
z + H * (conifer ? 0.06f : (0.10f + 0.06f * hash01(seed + i * 31U))));
add_tube_zup(
wood,
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 D3DXVECTOR3 tip = to + D3DXVECTOR3(std::cos(side), std::sin(side), 0.65f) * (len * 0.42f);
add_tube_zup(wood, to, tip, trunk_r * 0.16f, trunk_r * 0.05f, 5, H_m * 0.04f);
}
}
}
void build_leaves_zup(
TreeMeshBuilder & leaves,
const std::string & species,
float H,
bool conifer,
bool palm,
const std::vector<UVRect> & rects)
{
const uint32_t seed = species_seed(species);
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);
D3DXVECTOR3 center;
float width = 1.0f;
float height = 1.0f;
if (palm)
{
const float radial = H * (0.10f + 0.18f * hash01(seed + i * 103U));
center = D3DXVECTOR3(
std::cos(a) * radial,
std::sin(a) * radial,
H * (0.78f + 0.12f * hash01(seed + i * 107U)));
width = H * 0.32f;
height = 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));
center = D3DXVECTOR3(std::cos(a) * radial, std::sin(a) * radial, H * zf);
width = H * (0.18f + 0.10f * (1.0f - zf));
height = 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)));
center = D3DXVECTOR3(
std::cos(a) * radial,
std::sin(a) * radial,
H * (0.58f + zf * 0.34f));
width = H * (0.23f + 0.10f * hash01(seed + i * 127U));
height = H * (0.15f + 0.08f * hash01(seed + i * 131U));
}
const D3DXVECTOR3 right(std::cos(a + kPI * 0.5f), std::sin(a + kPI * 0.5f), 0.0f);
const D3DXVECTOR3 up(0.0f, 0.0f, 1.0f);
const UVRect & uv = rects[size_t(i) % rects.size()];
auto add_card = [&](const D3DXVECTOR3 & r, bool flip) {
leaves.add_leaf_quad_unindexed(
center - r * (width * 0.5f) - up * (height * 0.5f),
center + r * (width * 0.5f) - up * (height * 0.5f),
center + r * (width * 0.5f) + up * (height * 0.5f),
center - r * (width * 0.5f) + up * (height * 0.5f),
uv,
flip);
};
add_card(right, (i & 1) != 0);
const D3DXVECTOR3 crossed(std::cos(a), std::sin(a), 0.0f);
add_card(crossed, (i & 1) == 0);
}
}
} // namespace
decltype(CSpeedTreeWrapper::ms_bSelfShadowOn) CSpeedTreeWrapper::ms_bSelfShadowOn = true;
decltype(CSpeedTreeWrapper::ms_dwBranchVertexShader) CSpeedTreeWrapper::ms_dwBranchVertexShader = kTreeFVF;
decltype(CSpeedTreeWrapper::ms_dwLeafVertexShader) CSpeedTreeWrapper::ms_dwLeafVertexShader = kTreeFVF;
CSpeedTreeWrapper::CSpeedTreeWrapper()
: m_pSpeedTree(new CSpeedTreeRT)
, m_pTextureInfo(NULL)
, m_bIsInstance(false)
, m_pInstanceOf(NULL)
, m_pGeometryCache(NULL)
, m_pBranchVertexBuffer(NULL)
, m_unBranchVertexCount(0)
, m_pBranchIndexBuffer(NULL)
, m_pBranchIndexCounts(NULL)
, m_pFrondVertexBuffer(NULL)
, m_unFrondVertexCount(0)
, m_pFrondIndexBuffer(NULL)
, m_pFrondIndexCounts(NULL)
, m_usNumLeafLods(0)
, m_pLeafVertexBuffer(NULL)
, m_pLeavesUpdatedByCpu(NULL)
{
m_afPos[0] = m_afPos[1] = m_afPos[2] = 0.0f;
std::memset(m_afBoundingBox, 0, sizeof(m_afBoundingBox));
}
CSpeedTreeWrapper::~CSpeedTreeWrapper()
{
if (!m_bIsInstance)
{
SAFE_RELEASE(m_pBranchVertexBuffer);
SAFE_RELEASE(m_pBranchIndexBuffer);
SAFE_DELETE_ARRAY(m_pBranchIndexCounts);
SAFE_RELEASE(m_pFrondVertexBuffer);
SAFE_RELEASE(m_pFrondIndexBuffer);
SAFE_DELETE_ARRAY(m_pFrondIndexCounts);
if (m_pLeafVertexBuffer)
{
for (unsigned short i = 0; i < m_usNumLeafLods; ++i)
SAFE_RELEASE(m_pLeafVertexBuffer[i]);
SAFE_DELETE_ARRAY(m_pLeafVertexBuffer);
}
SAFE_DELETE_ARRAY(m_pLeavesUpdatedByCpu);
SAFE_DELETE(m_pTextureInfo);
SAFE_DELETE(m_pGeometryCache);
}
SAFE_DELETE(m_pSpeedTree);
Clear();
}
auto CSpeedTreeWrapper::SetVertexShaders(DWORD dwBranchVertexShader, DWORD dwLeafVertexShader) -> void
{
ms_dwBranchVertexShader = dwBranchVertexShader;
ms_dwLeafVertexShader = dwLeafVertexShader;
}
auto CSpeedTreeWrapper::GetPosition() -> const float *
{
return m_afPos;
}
auto CSpeedTreeWrapper::SetPosition(float x, float y, float z) -> void
{
m_afPos[0] = x;
m_afPos[1] = y;
m_afPos[2] = z;
CGraphicObjectInstance::SetPosition(x, y, z);
CGraphicObjectInstance::Transform();
CalculateBBox();
}
auto CSpeedTreeWrapper::GetBoundingSphere(D3DXVECTOR3 & v3Center, float & fRadius) -> bool
{
const float fX = m_afBoundingBox[3] - m_afBoundingBox[0];
const float fY = m_afBoundingBox[4] - m_afBoundingBox[1];
const float fZ = m_afBoundingBox[5] - m_afBoundingBox[2];
v3Center.x = m_afPos[0];
v3Center.y = m_afPos[1];
v3Center.z = m_afPos[2] + fZ * 0.5f;
fRadius = std::sqrt(fX * fX + fY * fY + fZ * fZ) * 0.5f * 0.9f;
return true;
}
auto CSpeedTreeWrapper::CalculateBBox() -> void
{
const float fX = m_afBoundingBox[3] - m_afBoundingBox[0];
const float fY = m_afBoundingBox[4] - m_afBoundingBox[1];
const float fZ = m_afBoundingBox[5] - m_afBoundingBox[2];
m_v3BBoxMin = D3DXVECTOR3(-fX * 0.5f, -fY * 0.5f, 0.0f);
m_v3BBoxMax = D3DXVECTOR3(+fX * 0.5f, +fY * 0.5f, fZ);
m_v4TBBox[0] = D3DXVECTOR4(m_v3BBoxMin.x, m_v3BBoxMin.y, m_v3BBoxMin.z, 1.0f);
m_v4TBBox[1] = D3DXVECTOR4(m_v3BBoxMin.x, m_v3BBoxMax.y, m_v3BBoxMin.z, 1.0f);
m_v4TBBox[2] = D3DXVECTOR4(m_v3BBoxMax.x, m_v3BBoxMin.y, m_v3BBoxMin.z, 1.0f);
m_v4TBBox[3] = D3DXVECTOR4(m_v3BBoxMax.x, m_v3BBoxMax.y, m_v3BBoxMin.z, 1.0f);
m_v4TBBox[4] = D3DXVECTOR4(m_v3BBoxMin.x, m_v3BBoxMin.y, m_v3BBoxMax.z, 1.0f);
m_v4TBBox[5] = D3DXVECTOR4(m_v3BBoxMin.x, m_v3BBoxMax.y, m_v3BBoxMax.z, 1.0f);
m_v4TBBox[6] = D3DXVECTOR4(m_v3BBoxMax.x, m_v3BBoxMin.y, m_v3BBoxMax.z, 1.0f);
m_v4TBBox[7] = D3DXVECTOR4(m_v3BBoxMax.x, m_v3BBoxMax.y, m_v3BBoxMax.z, 1.0f);
const D3DXMATRIX & c_rmatTransform = GetTransform();
for (DWORD i = 0; i < 8; ++i)
{
D3DXVec4Transform(&m_v4TBBox[i], &m_v4TBBox[i], &c_rmatTransform);
if (0 == i)
{
m_v3TBBoxMin = D3DXVECTOR3(m_v4TBBox[i].x, m_v4TBBox[i].y, m_v4TBBox[i].z);
m_v3TBBoxMax = m_v3TBBoxMin;
}
else
{
m_v3TBBoxMin.x = std::min(m_v3TBBoxMin.x, m_v4TBBox[i].x);
m_v3TBBoxMax.x = std::max(m_v3TBBoxMax.x, m_v4TBBox[i].x);
m_v3TBBoxMin.y = std::min(m_v3TBBoxMin.y, m_v4TBBox[i].y);
m_v3TBBoxMax.y = std::max(m_v3TBBoxMax.y, m_v4TBBox[i].y);
m_v3TBBoxMin.z = std::min(m_v3TBBoxMin.z, m_v4TBBox[i].z);
m_v3TBBoxMax.z = std::max(m_v3TBBoxMax.z, m_v4TBBox[i].z);
}
}
}
auto CSpeedTreeWrapper::OnUpdateCollisionData(const CStaticCollisionDataVector * /*pscdVector*/) -> void
{
ClearCollision();
D3DXMATRIX mat;
D3DXMatrixTranslation(&mat, m_afPos[0], m_afPos[1], m_afPos[2]);
for (UINT i = 0; i < GetCollisionObjectCount(); ++i)
{
CSpeedTreeRT::ECollisionObjectType ObjectType = CSpeedTreeRT::CO_CYLINDER;
CStaticCollisionData CollisionData{};
GetCollisionObject(i, ObjectType, reinterpret_cast<float *>(&CollisionData.v3Position), CollisionData.fDimensions);
if (ObjectType == CSpeedTreeRT::CO_BOX)
continue;
if (ObjectType == CSpeedTreeRT::CO_SPHERE)
CollisionData.dwType = COLLISION_TYPE_SPHERE;
else if (ObjectType == CSpeedTreeRT::CO_CYLINDER)
CollisionData.dwType = COLLISION_TYPE_CYLINDER;
AddCollision(&CollisionData, &mat);
}
}
auto CSpeedTreeWrapper::GetCollisionObjectCount() -> UINT
{
return 1;
}
auto CSpeedTreeWrapper::GetCollisionObject(
unsigned int /*nIndex*/,
CSpeedTreeRT::ECollisionObjectType & eType,
float * pPosition,
float * pDimensions) -> void
{
eType = CSpeedTreeRT::CO_CYLINDER;
if (pPosition)
{
pPosition[0] = 0.0f;
pPosition[1] = 0.0f;
pPosition[2] = 0.0f;
}
if (pDimensions)
{
pDimensions[0] = m_pGeometryCache ? m_pGeometryCache->trunk_radius : 35.0f;
pDimensions[1] = m_pGeometryCache ? m_pGeometryCache->trunk_height : 600.0f;
pDimensions[2] = 0.0f;
}
}
auto CSpeedTreeWrapper::GetTreeSize(float & r_fSize, float & r_fVariance) -> void
{
r_fSize = m_pGeometryCache ? m_pGeometryCache->height : 1000.0f;
r_fVariance = m_pGeometryCache ? m_pGeometryCache->variance : 0.0f;
}
auto CSpeedTreeWrapper::LoadTree(
const char * pszSptFile,
const BYTE * c_pbBlock,
unsigned int uiBlockSize,
unsigned int /*nSeed*/,
float fSize,
float fSizeVariance) -> bool
{
if (!IsNativeTerrainRenderEnabled())
return false;
const std::string spt_path = pszSptFile ? pszSptFile : "";
std::string bytes;
if (c_pbBlock && uiBlockSize > 0)
{
bytes.assign(reinterpret_cast<const char *>(c_pbBlock), uiBlockSize);
}
else if (!spt_path.empty())
{
CMappedFile file;
LPCVOID pvData = NULL;
if (CEterPackManager::Instance().Get(file, spt_path.c_str(), &pvData) && pvData && file.Size() > 0)
bytes.assign(static_cast<const char *>(pvData), file.Size());
}
fmt::SptInfo info;
if (!bytes.empty())
fmt::sniff_spt(bytes, info);
m_pTextureInfo = new CSpeedTreeRT::STextures;
for (const std::string & ref : info.texture_refs)
{
if (to_lower_str(ref).find("bark") != std::string::npos)
{
m_pTextureInfo->branch_tex = ref;
break;
}
}
if (m_pTextureInfo->branch_tex.empty() && !info.texture_refs.empty())
m_pTextureInfo->branch_tex = info.texture_refs.front();
m_pTextureInfo->composite_tex = info.composite_texture;
m_pTextureInfo->shadow_tex = info.self_shadow_texture;
if (!m_pTextureInfo->branch_tex.empty())
{
const std::string p = sibling_dds_path(spt_path, m_pTextureInfo->branch_tex);
LoadTexture(p.c_str(), m_BranchImageInstance);
}
if (!m_pTextureInfo->composite_tex.empty())
{
const std::string p = sibling_dds_path(spt_path, m_pTextureInfo->composite_tex);
LoadTexture(p.c_str(), m_CompositeImageInstance);
}
if (!m_pTextureInfo->shadow_tex.empty())
{
const std::string p = sibling_dds_path(spt_path, m_pTextureInfo->shadow_tex);
LoadTexture(p.c_str(), m_ShadowImageInstance);
}
const float H = (fSize > 150.0f) ? fSize : 1000.0f;
const float R = H * 0.35f;
m_afBoundingBox[0] = -R;
m_afBoundingBox[1] = -R;
m_afBoundingBox[2] = 0.0f;
m_afBoundingBox[3] = +R;
m_afBoundingBox[4] = +R;
m_afBoundingBox[5] = H;
m_pGeometryCache = new CSpeedTreeRT::SGeometry;
m_pGeometryCache->species = spt_path;
m_pGeometryCache->composite_name = info.composite_texture;
m_pGeometryCache->height = H;
m_pGeometryCache->variance = (fSizeVariance >= 0.0f) ? fSizeVariance : 0.0f;
m_pGeometryCache->radius = R;
m_pGeometryCache->trunk_radius = H * 0.035f;
m_pGeometryCache->trunk_height = H * 0.75f;
SetupBuffers();
return true;
}
auto CSpeedTreeWrapper::SetupBuffers() -> void
{
SetupBranchBuffers();
SetupFrondBuffers();
SetupLeafBuffers();
}
auto CSpeedTreeWrapper::SetupBranchBuffers() -> void
{
if (!m_pGeometryCache || !ms_lpd3dDevice)
return;
const float H = m_pGeometryCache->height;
const bool conifer = is_conifer_species(m_pGeometryCache->species);
const bool palm = is_palm_species(m_pGeometryCache->species);
TreeMeshBuilder wood;
wood.color = m_BranchImageInstance.IsEmpty() ? 0xFF4D3621u : 0xFFFFFFFFu;
build_branches_zup(wood, m_pGeometryCache->species, H, conifer, palm);
if (wood.verts.empty() || wood.indices.empty())
return;
m_unBranchVertexCount = static_cast<unsigned int>(wood.verts.size());
if (FAILED(ms_lpd3dDevice->CreateVertexBuffer(
m_unBranchVertexCount * sizeof(TreeVertex),
D3DUSAGE_WRITEONLY,
kTreeFVF,
D3DPOOL_MANAGED,
&m_pBranchVertexBuffer)))
{
m_unBranchVertexCount = 0;
return;
}
BYTE * pVB = NULL;
if (SUCCEEDED(m_pBranchVertexBuffer->Lock(0, 0, &pVB, 0)))
{
std::memcpy(pVB, wood.verts.data(), wood.verts.size() * sizeof(TreeVertex));
m_pBranchVertexBuffer->Unlock();
}
m_pBranchIndexCounts = new unsigned short[1];
m_pBranchIndexCounts[0] = static_cast<unsigned short>(wood.indices.size());
m_pGeometryCache->branch_index_count = static_cast<uint32_t>(wood.indices.size());
if (FAILED(ms_lpd3dDevice->CreateIndexBuffer(
m_pBranchIndexCounts[0] * sizeof(uint16_t),
D3DUSAGE_WRITEONLY,
D3DFMT_INDEX16,
D3DPOOL_MANAGED,
&m_pBranchIndexBuffer)))
{
m_pBranchIndexCounts[0] = 0;
return;
}
BYTE * pIB = NULL;
if (SUCCEEDED(m_pBranchIndexBuffer->Lock(0, 0, &pIB, 0)))
{
std::memcpy(pIB, wood.indices.data(), wood.indices.size() * sizeof(uint16_t));
m_pBranchIndexBuffer->Unlock();
}
}
auto CSpeedTreeWrapper::SetupFrondBuffers() -> void
{
m_unFrondVertexCount = 0;
}
auto CSpeedTreeWrapper::SetupLeafBuffers() -> void
{
if (!m_pGeometryCache || !ms_lpd3dDevice)
return;
const float H = m_pGeometryCache->height;
const bool conifer = is_conifer_species(m_pGeometryCache->species);
const bool palm = is_palm_species(m_pGeometryCache->species);
const bool has_atlas = !m_CompositeImageInstance.IsEmpty();
const std::vector<UVRect> rects = foliage_rects(
m_pGeometryCache->species,
m_pGeometryCache->composite_name,
has_atlas);
TreeMeshBuilder leaves;
leaves.color = has_atlas ? 0xFFFFFFFFu : (conifer ? 0xFF1F471Cu : 0xFF2E6624u);
build_leaves_zup(leaves, m_pGeometryCache->species, H, conifer, palm, rects);
if (leaves.verts.empty())
return;
m_usNumLeafLods = 1;
m_pLeafVertexBuffer = new LPDIRECT3DVERTEXBUFFER8[1];
m_pLeafVertexBuffer[0] = NULL;
m_pLeavesUpdatedByCpu = new bool[1];
m_pLeavesUpdatedByCpu[0] = true;
m_pGeometryCache->leaf_vertex_count = static_cast<uint32_t>(leaves.verts.size());
if (FAILED(ms_lpd3dDevice->CreateVertexBuffer(
leaves.verts.size() * sizeof(TreeVertex),
D3DUSAGE_WRITEONLY,
kTreeFVF,
D3DPOOL_MANAGED,
&m_pLeafVertexBuffer[0])))
{
m_pGeometryCache->leaf_vertex_count = 0;
return;
}
BYTE * pVB = NULL;
if (SUCCEEDED(m_pLeafVertexBuffer[0]->Lock(0, 0, &pVB, 0)))
{
std::memcpy(pVB, leaves.verts.data(), leaves.verts.size() * sizeof(TreeVertex));
m_pLeafVertexBuffer[0]->Unlock();
}
}
auto CSpeedTreeWrapper::SetupBranchForTreeType() const -> void
{
LPDIRECT3DTEXTURE8 lpd3dTexture = NULL;
if (!m_BranchImageInstance.IsEmpty())
lpd3dTexture = const_cast<CGraphicImageInstance &>(m_BranchImageInstance).GetTextureReference().GetD3DTexture();
STATEMANAGER.SetTexture(0, lpd3dTexture);
STATEMANAGER.SetTexture(1, NULL);
if (m_unBranchVertexCount > 0 && m_pBranchVertexBuffer && m_pBranchIndexBuffer)
{
STATEMANAGER.SetStreamSource(0, m_pBranchVertexBuffer, sizeof(TreeVertex));
STATEMANAGER.SetIndices(m_pBranchIndexBuffer, 0);
}
}
auto CSpeedTreeWrapper::RenderBranches() const -> void
{
if (!m_pGeometryCache || m_unBranchVertexCount == 0 || !m_pBranchIndexCounts || m_pBranchIndexCounts[0] < 3)
return;
PositionTree();
STATEMANAGER.DrawIndexedPrimitive(
D3DPT_TRIANGLELIST,
0,
m_unBranchVertexCount,
0,
m_pBranchIndexCounts[0] / 3);
}
auto CSpeedTreeWrapper::SetupFrondForTreeType() const -> void
{
}
auto CSpeedTreeWrapper::RenderFronds() const -> void
{
}
auto CSpeedTreeWrapper::SetupLeafForTreeType() const -> void
{
LPDIRECT3DTEXTURE8 lpd3dTexture = NULL;
if (!m_CompositeImageInstance.IsEmpty())
lpd3dTexture = const_cast<CGraphicImageInstance &>(m_CompositeImageInstance).GetTextureReference().GetD3DTexture();
STATEMANAGER.SetTexture(0, lpd3dTexture);
STATEMANAGER.SetTexture(1, NULL);
if (m_pLeafVertexBuffer && m_pLeafVertexBuffer[0])
STATEMANAGER.SetStreamSource(0, m_pLeafVertexBuffer[0], sizeof(TreeVertex));
}
auto CSpeedTreeWrapper::RenderLeaves() const -> void
{
if (!m_pGeometryCache || m_pGeometryCache->leaf_vertex_count < 3 || !m_pLeafVertexBuffer || !m_pLeafVertexBuffer[0])
return;
PositionTree();
STATEMANAGER.DrawPrimitive(D3DPT_TRIANGLELIST, 0, m_pGeometryCache->leaf_vertex_count / 3);
}
auto CSpeedTreeWrapper::EndLeafForTreeType() -> void
{
}
auto CSpeedTreeWrapper::RenderBillboards() const -> void
{
}
auto CSpeedTreeWrapper::OnRender() -> void
{
if (!IsNativeTerrainRenderEnabled())
return;
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
STATEMANAGER.SaveRenderState(D3DRS_LIGHTING, TRUE);
STATEMANAGER.SaveRenderState(D3DRS_COLORVERTEX, TRUE);
STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, FALSE);
STATEMANAGER.SaveRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);
STATEMANAGER.SaveRenderState(D3DRS_ALPHAREF, 0x00000060);
STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
STATEMANAGER.SetVertexShader(ms_dwBranchVertexShader);
SetupBranchForTreeType();
RenderBranches();
STATEMANAGER.SetVertexShader(ms_dwLeafVertexShader);
STATEMANAGER.SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
SetupLeafForTreeType();
RenderLeaves();
EndLeafForTreeType();
STATEMANAGER.RestoreRenderState(D3DRS_LIGHTING);
STATEMANAGER.RestoreRenderState(D3DRS_COLORVERTEX);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAFUNC);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAREF);
STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE);
}
auto CSpeedTreeWrapper::OnRenderPCBlocker() -> void
{
if (!IsNativeTerrainRenderEnabled())
return;
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR);
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
STATEMANAGER.SaveRenderState(D3DRS_LIGHTING, TRUE);
STATEMANAGER.SaveRenderState(D3DRS_COLORVERTEX, TRUE);
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, 0x80FFFFFF);
STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, FALSE);
STATEMANAGER.SaveRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);
STATEMANAGER.SaveRenderState(D3DRS_ALPHAREF, 0x00000030);
STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
STATEMANAGER.SetVertexShader(ms_dwBranchVertexShader);
SetupBranchForTreeType();
RenderBranches();
STATEMANAGER.SetVertexShader(ms_dwLeafVertexShader);
STATEMANAGER.SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
SetupLeafForTreeType();
RenderLeaves();
EndLeafForTreeType();
STATEMANAGER.RestoreRenderState(D3DRS_LIGHTING);
STATEMANAGER.RestoreRenderState(D3DRS_COLORVERTEX);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND);
STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND);
STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAFUNC);
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAREF);
STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE);
}
auto CSpeedTreeWrapper::GetInstances(unsigned int & nCount) -> CSpeedTreeWrapper **
{
nCount = static_cast<unsigned int>(m_vInstances.size());
return nCount ? &m_vInstances[0] : NULL;
}
auto CSpeedTreeWrapper::MakeInstance() -> CSpeedTreeWrapper *
{
CSpeedTreeWrapper * pInstance = new CSpeedTreeWrapper;
pInstance->m_bIsInstance = true;
pInstance->m_cBranchMaterial = m_cBranchMaterial;
pInstance->m_cLeafMaterial = m_cLeafMaterial;
pInstance->m_cFrondMaterial = m_cFrondMaterial;
if (!m_CompositeImageInstance.IsEmpty())
pInstance->m_CompositeImageInstance.SetImagePointer(m_CompositeImageInstance.GetGraphicImagePointer());
if (!m_BranchImageInstance.IsEmpty())
pInstance->m_BranchImageInstance.SetImagePointer(m_BranchImageInstance.GetGraphicImagePointer());
if (!m_ShadowImageInstance.IsEmpty())
pInstance->m_ShadowImageInstance.SetImagePointer(m_ShadowImageInstance.GetGraphicImagePointer());
pInstance->m_pTextureInfo = m_pTextureInfo;
pInstance->m_pGeometryCache = m_pGeometryCache;
pInstance->m_pBranchIndexBuffer = m_pBranchIndexBuffer;
pInstance->m_pBranchIndexCounts = m_pBranchIndexCounts;
pInstance->m_pBranchVertexBuffer = m_pBranchVertexBuffer;
pInstance->m_unBranchVertexCount = m_unBranchVertexCount;
pInstance->m_pFrondIndexBuffer = m_pFrondIndexBuffer;
pInstance->m_pFrondIndexCounts = m_pFrondIndexCounts;
pInstance->m_pFrondVertexBuffer = m_pFrondVertexBuffer;
pInstance->m_unFrondVertexCount = m_unFrondVertexCount;
pInstance->m_pLeafVertexBuffer = m_pLeafVertexBuffer;
pInstance->m_usNumLeafLods = m_usNumLeafLods;
pInstance->m_pLeavesUpdatedByCpu = m_pLeavesUpdatedByCpu;
std::memcpy(pInstance->m_afPos, m_afPos, sizeof(m_afPos));
std::memcpy(pInstance->m_afBoundingBox, m_afBoundingBox, sizeof(m_afBoundingBox));
pInstance->m_pInstanceOf = this;
m_vInstances.push_back(pInstance);
return pInstance;
}
auto CSpeedTreeWrapper::DeleteInstance(CSpeedTreeWrapper * pInstance) -> void
{
if (!pInstance)
return;
auto it = std::find(m_vInstances.begin(), m_vInstances.end(), pInstance);
if (it != m_vInstances.end())
m_vInstances.erase(it);
delete pInstance;
}
auto CSpeedTreeWrapper::Advance() -> void
{
}
auto CSpeedTreeWrapper::GetBranchTexture() const -> LPDIRECT3DTEXTURE8
{
if (m_BranchImageInstance.IsEmpty())
return NULL;
return const_cast<CGraphicImageInstance &>(m_BranchImageInstance).GetTextureReference().GetD3DTexture();
}
auto CSpeedTreeWrapper::CleanUpMemory() -> void
{
}
auto CSpeedTreeWrapper::PositionTree() const -> void
{
D3DXMATRIX matTranslation;
D3DXMatrixTranslation(&matTranslation, m_afPos[0], m_afPos[1], m_afPos[2]);
STATEMANAGER.SetTransform(D3DTS_WORLD, &matTranslation);
}
auto CSpeedTreeWrapper::LoadTexture(const char * pFilename, CGraphicImageInstance & rImage) -> bool
{
if (!pFilename || !pFilename[0])
return false;
CResource * pResource = CResourceManager::Instance().GetResourcePointer(pFilename);
if (!pResource)
return false;
rImage.SetImagePointer(static_cast<CGraphicImage *>(pResource));
return !rImage.IsEmpty();
}
auto CSpeedTreeWrapper::SetShaderConstants(const float * /*pMaterial*/) const -> void
{
}