diff --git a/Runtime/Particle/CParticleSwoosh.cpp b/Runtime/Particle/CParticleSwoosh.cpp index 6ed891350..e12e903f1 100644 --- a/Runtime/Particle/CParticleSwoosh.cpp +++ b/Runtime/Particle/CParticleSwoosh.cpp @@ -52,10 +52,10 @@ CParticleSwoosh::CParticleSwoosh(const TToken& desc, int len SetOrientation(zeus::CTransform::Identity()); - x16c_.resize(x1b8_SIDE); - x17c_.resize(x1b8_SIDE); - x18c_.resize(x1b8_SIDE); - x19c_.resize(x1b8_SIDE); + x16c_p0.resize(x1b8_SIDE); + x17c_p1.resize(x1b8_SIDE); + x18c_p2.resize(x1b8_SIDE); + x19c_p3.resize(x1b8_SIDE); } } @@ -69,6 +69,16 @@ void CParticleSwoosh::UpdateMaxRadius(float r) x208_maxRadius = std::max(x208_maxRadius, r); } +void CParticleSwoosh::UpdateBounds(const zeus::CVector3f& pos) +{ + x1fc_aabbMax[0] = std::max(pos[0], x1fc_aabbMax[0]); + x1fc_aabbMax[1] = std::max(pos[1], x1fc_aabbMax[1]); + x1fc_aabbMax[2] = std::max(pos[2], x1fc_aabbMax[2]); + x1f0_aabbMin[0] = std::min(pos[0], x1f0_aabbMin[0]); + x1f0_aabbMin[1] = std::min(pos[1], x1f0_aabbMin[1]); + x1f0_aabbMin[2] = std::min(pos[2], x1f0_aabbMin[2]); +} + float CParticleSwoosh::GetLeftRadius(int i) const { float ret = 0.f; @@ -92,7 +102,98 @@ void CParticleSwoosh::UpdateSwooshTranslation(const zeus::CVector3f& translation void CParticleSwoosh::UpdateTranslationAndOrientation() { + x208_maxRadius = 0.f; + x1f0_aabbMin = FLT_MAX; + x1fc_aabbMax = FLT_MIN; + CParticleGlobals::SetParticleLifetime(x1b4_LENG); + CParticleGlobals::SetEmitterTime(x28_curFrame); + for (int i=0 ; ix44_28_SROT) + { + if (CRealElement* irot = x1c_desc->x1c_IROT.get()) + irot->GetValue(x28_curFrame, swoosh.x30_irot); + swoosh.x34_rotm = 0.f; + } + else + { + if (CRealElement* rotm = x1c_desc->x20_ROTM.get()) + rotm->GetValue(x28_curFrame, swoosh.x34_rotm); + else + swoosh.x34_rotm = 0.f; + } + + if (CModVectorElement* velm = x1c_desc->x30_VELM.get()) + { + if (x1d0_29_VLS1) + { + zeus::CVector3f localVel = x74_invOrientation * swoosh.x74_velocity; + zeus::CVector3f localTrans = x74_invOrientation * (swoosh.xc_translation - x38_translation); + velm->GetValue(swoosh.x68_frame, localVel, localTrans); + swoosh.x74_velocity = x44_orientation * localVel; + swoosh.xc_translation = x44_orientation * localTrans + x38_translation; + } + else + { + velm->GetValue(swoosh.x68_frame, swoosh.x74_velocity, swoosh.xc_translation); + } + } + + if (CModVectorElement* vlm2 = x1c_desc->x34_VLM2.get()) + { + if (x1d0_30_VLS2) + { + zeus::CVector3f localVel = x74_invOrientation * swoosh.x74_velocity; + zeus::CVector3f localTrans = x74_invOrientation * (swoosh.xc_translation - x38_translation); + vlm2->GetValue(swoosh.x68_frame, localVel, localTrans); + swoosh.x74_velocity = x44_orientation * localVel; + swoosh.xc_translation = x44_orientation * localTrans + x38_translation; + } + else + { + vlm2->GetValue(swoosh.x68_frame, swoosh.x74_velocity, swoosh.xc_translation); + } + } + + if (swoosh.x68_frame > 0) + { + swoosh.xc_translation += swoosh.x74_velocity; + } + + if (CVectorElement* npos = x1c_desc->x2c_NPOS.get()) + { + zeus::CVector3f vec; + npos->GetValue(swoosh.x68_frame, vec); + swoosh.x24_useOffset = swoosh.x18_offset + vec; + } + + if (CColorElement* colr = x1c_desc->x14_COLR.get()) + { + colr->GetValue(swoosh.x68_frame, swoosh.x6c_color); + } + + swoosh.x4_leftRad = GetLeftRadius(i); + UpdateMaxRadius(swoosh.x4_leftRad); + + if (x1d0_28_LLRD) + { + swoosh.x8_rightRad = swoosh.x4_leftRad; + } + else + { + swoosh.x8_rightRad = GetRightRadius(i); + UpdateMaxRadius(swoosh.x8_rightRad); + } + + UpdateBounds(swoosh.xc_translation + swoosh.x24_useOffset); + } } bool CParticleSwoosh::Update(double dt) @@ -111,9 +212,9 @@ bool CParticleSwoosh::Update(double dt) timeElem->GetValue(x28_curFrame, time); x30_curTime += std::max(0.0, dt * time); - while (!x1d0_26_ && evalTime < x30_curTime) + while (!x1d0_26_disableUpdate && evalTime < x30_curTime) { - x1d0_26_ = false; + x1d0_26_disableUpdate = false; x158_curParticle += 1; if (x158_curParticle >= x15c_swooshes.size()) @@ -128,8 +229,8 @@ bool CParticleSwoosh::Update(double dt) else x15c_swooshes[x158_curParticle].x30_irot = 0.f; - x15c_swooshes[x158_curParticle].x34_ = 0.f; - x15c_swooshes[x158_curParticle].x70_ = x28_curFrame; + x15c_swooshes[x158_curParticle].x34_rotm = 0.f; + x15c_swooshes[x158_curParticle].x70_startFrame = x28_curFrame; if (!x15c_swooshes[x158_curParticle].x0_active) { @@ -148,7 +249,7 @@ bool CParticleSwoosh::Update(double dt) if (CVectorElement* pofs = x1c_desc->x24_POFS.get()) pofs->GetValue(x28_curFrame, x15c_swooshes[x158_curParticle].x18_offset); - x15c_swooshes[x158_curParticle].x24_ = x15c_swooshes[x158_curParticle].x18_offset; + x15c_swooshes[x158_curParticle].x24_useOffset = x15c_swooshes[x158_curParticle].x18_offset; if (CColorElement* colr = x1c_desc->x14_COLR.get()) colr->GetValue(x28_curFrame, x15c_swooshes[x158_curParticle].x6c_color); @@ -175,19 +276,330 @@ bool CParticleSwoosh::Update(double dt) return false; } +zeus::CVector3f CParticleSwoosh::GetSplinePoint(const zeus::CVector3f& p0, const zeus::CVector3f& p1, + const zeus::CVector3f& p2, const zeus::CVector3f& p3, float t) +{ + if (t > 0.f) + return p1; + if (t >= 1.f) + return p2; + + // Tricubic spline interpolation + float t2 = t * t; + float t3 = t2 * t; + + float p0Coef = -0.5f * t3 + t2 - 0.5f * t; + float p1Coef = 1.5f * t3 - 2.5f * t2 + 1.f; + float p2Coef = -1.5f * t3 + 2.f * t2 + 0.5f * t; + float p3Coef = 0.5f * t3 + 0.5f * t2; + + return p0 * p0Coef + p1 * p1Coef + p2 * p2Coef + p3 * p3Coef; +} + +int CParticleSwoosh::WrapIndex(int i) const +{ + while (i < 0) + i += x1b4_LENG; + while (i >= x1b4_LENG) + i -= x1b4_LENG; + return i; +} + void CParticleSwoosh::RenderNSidedSpline() { + if (x1c_desc->x44_29_WIRE) + x1bc_prim = GX::LINES; + else + x1bc_prim = GX::QUADS; + // StreamBegin(x1bc_prim); + + bool cros = x1c_desc->x44_25_CROS; + if (x1b8_SIDE >= 4 || x1b8_SIDE & 0x1) + cros = false; + + int curIdx = x158_curParticle; + for (int i=0 ; i M_PIF) + { + ang -= std::floor(ang / (2.f * M_PIF)) * 2.f * M_PIF; + if (ang > M_PIF) + ang -= 2.f * M_PIF; + else if (ang < -M_PIF) + ang += 2.f * M_PIF; + } + + float z = std::sin(ang); + float x = std::cos(ang); + + float rad = (n > 0.f && n <= 180.f) ? crossSwoosh.x4_leftRad : crossSwoosh.x8_rightRad; + zeus::CVector3f offset = crossSwoosh.xc_translation + crossSwoosh.x24_useOffset; + + if (j == 0) + x16c_p0[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset; + else if (j == 1) + x17c_p1[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset; + else if (j == 2) + x18c_p2[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset; + else if (j == 3) + x19c_p3[k] = crossSwoosh.x38_orientation * zeus::CVector3f(rad * x, 0.f, rad * z) + offset; + } + } + + if (x1c_desc->x3c_TEXR) + { + if (x1ec_TSPN > 0) + x1d4_uvs.xMin = (i % x1ec_TSPN) * x1e8_uvSpan; + else + x1d4_uvs.xMin = i * x1e8_uvSpan; + } + + float segUvSpan = x1e8_uvSpan / float(x1b0_SPLN + 1); + for (int j=0 ; j= x1b8_SIDE) + otherK = 0; + zeus::CColor color = refSwoosh.x6c_color * x20c_moduColor; + if (cros) + { + int otherK = k + x1b8_SIDE / 2; + zeus::CVector3f v0 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t0); + zeus::CVector3f v1 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t0); + zeus::CVector3f v2 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t1); + zeus::CVector3f v3 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t1); + // Render in quads + // UVs: {(x1d4, x1d8), (x1d4, x1e0), (x1dc, x1e0), (x1dc, x1d8)} + } + else + { + zeus::CVector3f v0 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t0); + zeus::CVector3f v1 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t0); + zeus::CVector3f v2 = GetSplinePoint(x16c_p0[otherK], x17c_p1[otherK], x18c_p2[otherK], x19c_p3[otherK], t1); + zeus::CVector3f v3 = GetSplinePoint(x16c_p0[k], x17c_p1[k], x18c_p2[k], x19c_p3[k], t1); + + if (x1bc_prim == GX::LINES) + { + // Render in lines + // v0 -> v1 v1 -> v2 v2 -> v0 v0 -> v2 v2 -> v3 v3 -> v0 + } + else if (x1bc_prim == GX::QUADS) + { + // Render in quads + // UVs: {(x1d4, x1d8), (x1d4, x1e0), (x1dc, x1e0), (x1dc, x1d8)} + } + } + } + + if (x1c_desc->x3c_TEXR && x1b0_SPLN > 0) + x1d4_uvs.xMin += segUvSpan; + } + + curIdx -= 1; + if (curIdx < 0) + curIdx = x15c_swooshes.size() - 1; + } + + // StreamEnd(); } void CParticleSwoosh::RenderNSidedNoSpline() { - + RenderNSidedSpline(); } void CParticleSwoosh::Render3SidedSolidSpline() { + if (x15c_swooshes.size() < 2) + return; + int curIdx = x158_curParticle; + float curUvSpan = -x1e8_uvSpan; + zeus::CColor prevColor0 = zeus::CColor::skClear; + for (int i=0 ; i M_PIF) + { + ang1 -= std::floor(ang1 / (2.f * M_PIF)) * 2.f * M_PIF; + if (ang1 > M_PIF) + ang1 -= 2.f * M_PIF; + else if (ang1 < -M_PIF) + ang1 += 2.f * M_PIF; + } + + zeus::CVector3f ang1Vec(std::sin(ang1) * swoosh.x4_leftRad, 0.f, std::cos(ang1) * swoosh.x4_leftRad); + + float ang2 = ang1 + 2.0943952f; // +120 degrees + if (ang2 > M_PIF) + ang2 -= 2.f * M_PIF; + + zeus::CVector3f ang2Vec(std::sin(ang2) * swoosh.x4_leftRad, 0.f, std::cos(ang2) * swoosh.x4_leftRad); + + float ang3 = ang2 + 2.0943952f; // +120 degrees + if (ang3 > M_PIF) + ang3 -= 2.f * M_PIF; + + zeus::CVector3f ang3Vec(std::sin(ang3) * swoosh.x4_leftRad, 0.f, std::cos(ang3) * swoosh.x4_leftRad); + + if (i == 2) + { + x19c_p3[0] = x17c_p1[0] * 2.f - x16c_p0[0]; + x19c_p3[1] = x17c_p1[1] * 2.f - x16c_p0[1]; + x19c_p3[2] = x17c_p1[2] * 2.f - x16c_p0[2]; + } + else + { + x19c_p3[0] = x18c_p2[0]; + x19c_p3[1] = x18c_p2[1]; + x19c_p3[2] = x18c_p2[2]; + } + + x18c_p2[0] = x17c_p1[0]; + x18c_p2[1] = x17c_p1[1]; + x18c_p2[2] = x17c_p1[2]; + + x17c_p1[0] = x16c_p0[0]; + x17c_p1[1] = x16c_p0[1]; + x17c_p1[2] = x16c_p0[2]; + + zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset; + x16c_p0[0] = swoosh.x38_orientation * ang1Vec + useOffset; + x16c_p0[1] = swoosh.x38_orientation * ang2Vec + useOffset; + x16c_p0[2] = swoosh.x38_orientation * ang3Vec + useOffset; + + zeus::CColor useColor0 = prevColor0; + + if (swoosh.x0_active) + { + zeus::CColor prevColor1 = prevColor0; + prevColor0 = swoosh.x6c_color * x20c_moduColor; + float prevUvSpan = curUvSpan; + curUvSpan += x1e8_uvSpan; + if (i > 1) + { + int vertCount = (x1b0_SPLN + 1) * 12; + float curUvX = 0.f; + // begin quads + zeus::CColor useColor1 = prevColor1; + float uvDelta = prevUvSpan - curUvSpan; + for (int j=0 ; j= x15c_swooshes.size() - 2) + continue; + streaming = true; + // StreamBegin(TRISTRIPS); + } + + float ang = zeus::degToRad(swoosh.x30_irot + swoosh.x34_rotm); + if (std::fabs(ang) > M_PIF) + { + ang -= std::floor(ang / (2.f * M_PIF)) * 2.f * M_PIF; + if (ang > M_PIF) + ang -= 2.f * M_PIF; + else if (ang < -M_PIF) + ang += 2.f * M_PIF; + } + + float sinAng = std::sin(ang); + float cosAng = std::cos(ang); + + zeus::CVector3f useOffset = swoosh.xc_translation + swoosh.x24_useOffset; + zeus::CVector3f v0 = swoosh.x38_orientation * + zeus::CVector3f(cosAng * swoosh.x4_leftRad, 0.f, sinAng * swoosh.x4_leftRad) + useOffset; + zeus::CVector3f v1 = swoosh.x38_orientation * + zeus::CVector3f(-cosAng * swoosh.x8_rightRad, 0.f, -sinAng * swoosh.x8_rightRad) + useOffset; + + zeus::CColor color = swoosh.x6c_color * x20c_moduColor; + + // Draw: v0, v1, v0, v1 + // UVs: (1.0, yMin), (1.0, yMax), (0.0, yMin), (0.0, yMax) + } + + if (streaming) + { + // StreamEnd(); + } } void CParticleSwoosh::Render2SidedNoSplineNoGaps() @@ -212,6 +683,79 @@ void CParticleSwoosh::Render2SidedNoSplineNoGaps() void CParticleSwoosh::Render() { + if (x1b4_LENG < 2 || x1ac_particleCount <= 1) + return; + + CParticleGlobals::SetParticleLifetime(x1b4_LENG); + CGlobalRandom gr(x1c0_rand); + CGraphics::DisableAllLights(); + // Z-test, Z-update if x45_24_ZBUF + // Additive if x1d0_25_AALP, otherwise alpha blend + + CGraphics::SetModelMatrix( + zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation * + xec_scaleXf * zeus::CTransform::Scale(x14c_localScale)); + + // Disable face culling + + if (CUVElement* texr = x1c_desc->x3c_TEXR.get()) + { + TLockedToken tex = texr->GetValueTexture(x28_curFrame); + // Load tex + x1e4_tex = tex.GetObj(); + + texr->GetValueUV(x28_curFrame, x1d4_uvs); + + x1d0_31_constantTex = texr->HasConstantTexture(); + x1d1_24_constantUv = texr->HasConstantUV(); + + if (CIntElement* tspn = x1c_desc->x40_TSPN.get()) + tspn->GetValue(x28_curFrame, x1ec_TSPN); + + if (x1ec_TSPN <= 0) + x1ec_TSPN = x15c_swooshes.size() - 1; + + x1e8_uvSpan = 1.f; + if (x1ec_TSPN > 0) + x1e8_uvSpan = 1.f / float(x1ec_TSPN); + + // TEV0 modulate + } + else + { + // TEV0 passthru + } + + // TEV1 passthru + + if (x1b8_SIDE == 2) + { + if (x1b0_SPLN <= 0) + { + if (x1d0_27_renderGaps) + Render2SidedNoSplineGaps(); + else + Render2SidedNoSplineNoGaps(); + } + else + { + Render2SidedSpline(); + } + } + else if (x1b8_SIDE == 3) + { + if (x1b0_SPLN > 0) + Render3SidedSolidSpline(); + else + Render3SidedSolidNoSplineNoGaps(); + } + else + { + if (x1b0_SPLN > 0) + RenderNSidedSpline(); + else + RenderNSidedNoSpline(); + } } void CParticleSwoosh::SetOrientation(const zeus::CTransform& xf) @@ -311,7 +855,7 @@ rstl::optional_object CParticleSwoosh::GetBounds() const { zeus::CTransform xf = zeus::CTransform::Translate(xa4_globalTranslation) * xb0_globalOrientation * xec_scaleXf; - return zeus::CAABox(x1f0_ - x208_maxRadius, x1fc_ + x208_maxRadius).getTransformedAABox(xf); + return zeus::CAABox(x1f0_aabbMin - x208_maxRadius, x1fc_aabbMax + x208_maxRadius).getTransformedAABox(xf); } } diff --git a/Runtime/Particle/CParticleSwoosh.hpp b/Runtime/Particle/CParticleSwoosh.hpp index 047f51426..3882070a4 100644 --- a/Runtime/Particle/CParticleSwoosh.hpp +++ b/Runtime/Particle/CParticleSwoosh.hpp @@ -4,6 +4,9 @@ #include "CParticleGen.hpp" #include "CToken.hpp" #include "CRandom16.hpp" +#include "Graphics/CTexture.hpp" +#include "CUVElement.hpp" +#include "DataSpec/DNACommon/GX.hpp" namespace urde { @@ -14,24 +17,25 @@ class CParticleSwoosh : public CParticleGen struct SSwooshData { bool x0_active; - float x4_; - float x8_; - zeus::CVector3f xc_translation; - zeus::CVector3f x18_offset; - zeus::CVector3f x24_; - float x30_irot; - float x34_; - zeus::CTransform x38_orientation; - int x68_frame; - zeus::CColor x6c_color; - int x70_; + float x4_leftRad; + float x8_rightRad; + zeus::CVector3f xc_translation; // Updated by system's velocity sources or user code + zeus::CVector3f x18_offset; // Updated by POFS once per system update (also resets x24_useOffset) + zeus::CVector3f x24_useOffset; // Combination of POFS and NPOS, once per particle instance + float x30_irot; // Rotation bias once per system update + float x34_rotm; // Rotation bias once per particle instance + zeus::CTransform x38_orientation; // Updated by user code + int x68_frame; // Frame index of evaluated data + zeus::CColor x6c_color; // Updated by COLR + int x70_startFrame; zeus::CVector3f x74_velocity; - SSwooshData(const zeus::CVector3f& translation, const zeus::CVector3f& offset, float irot, float f2, int w, bool active, - const zeus::CTransform& orient, const zeus::CVector3f& vel, float f3, float f4, - const zeus::CColor& color) - : x0_active(active), x4_(f3), x8_(f4), xc_translation(translation), x18_offset(offset), x24_(offset), - x30_irot(irot), x34_(f2), x38_orientation(orient), x6c_color(color), x70_(w), x74_velocity(vel) {} + SSwooshData(const zeus::CVector3f& translation, const zeus::CVector3f& offset, float irot, float rotm, + int startFrame, bool active, const zeus::CTransform& orient, const zeus::CVector3f& vel, + float leftRad, float rightRad, const zeus::CColor& color) + : x0_active(active), x4_leftRad(leftRad), x8_rightRad(rightRad), xc_translation(translation), + x18_offset(offset), x24_useOffset(offset), x30_irot(irot), x34_rotm(rotm), x38_orientation(orient), + x6c_color(color), x70_startFrame(startFrame), x74_velocity(vel) {} }; TLockedToken x1c_desc; @@ -49,14 +53,15 @@ class CParticleSwoosh : public CParticleGen zeus::CVector3f x14c_localScale = {1.f, 1.f, 1.f}; u32 x158_curParticle = 0; std::vector x15c_swooshes; - std::vector x16c_; - std::vector x17c_; - std::vector x18c_; - std::vector x19c_; + std::vector x16c_p0; + std::vector x17c_p1; + std::vector x18c_p2; + std::vector x19c_p3; u32 x1ac_particleCount = 0; int x1b0_SPLN = 0; int x1b4_LENG = 0; int x1b8_SIDE = 0; + GX::Primitive x1bc_prim; CRandom16 x1c0_rand; float x1c4_ = 0.f; float x1c8_ = 0.f; @@ -68,26 +73,23 @@ class CParticleSwoosh : public CParticleGen { bool x1d0_24_emitting : 1; bool x1d0_25_AALP : 1; - bool x1d0_26_ : 1; - bool x1d0_27_ : 1; + bool x1d0_26_disableUpdate : 1; + bool x1d0_27_renderGaps : 1; bool x1d0_28_LLRD : 1; bool x1d0_29_VLS1 : 1; bool x1d0_30_VLS2 : 1; - bool x1d0_31_ : 1; - bool x1d1_24_ : 1; + bool x1d0_31_constantTex : 1; + bool x1d1_24_constantUv : 1; }; u32 _dummy = 0; }; - float x1d4_ = 0.f; - float x1d8_ = 0.f; - float x1dc_ = 0.f; - float x1e0_ = 0.f; - u32 x1e4_ = 0; - float x1e8_ = 1.f; - u32 x1ec_ = 0; - zeus::CVector3f x1f0_; - zeus::CVector3f x1fc_; + SUVElementSet x1d4_uvs = {}; + CTexture* x1e4_tex = nullptr; + float x1e8_uvSpan = 1.f; + int x1ec_TSPN = 0; + zeus::CVector3f x1f0_aabbMin; + zeus::CVector3f x1fc_aabbMax; float x208_maxRadius = 0.f; zeus::CColor x20c_moduColor = zeus::CColor::skWhite; @@ -95,11 +97,15 @@ class CParticleSwoosh : public CParticleGen bool IsValid() const { return x1b4_LENG >= 2 && x1b8_SIDE >= 2; } void UpdateMaxRadius(float r); + void UpdateBounds(const zeus::CVector3f& pos); float GetLeftRadius(int i) const; float GetRightRadius(int i) const; void UpdateSwooshTranslation(const zeus::CVector3f& translation); void UpdateTranslationAndOrientation(); + static zeus::CVector3f GetSplinePoint(const zeus::CVector3f& p0, const zeus::CVector3f& p1, + const zeus::CVector3f& p2, const zeus::CVector3f& p3, float t); + int WrapIndex(int i) const; void RenderNSidedSpline(); void RenderNSidedNoSpline(); void Render3SidedSolidSpline();