mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-04-28 15:57:59 +03:00
1053 lines
35 KiB
C++
1053 lines
35 KiB
C++
#include "framework.h"
|
|
#include "Renderer/Renderer.h"
|
|
|
|
#include <execution>
|
|
#include <stack>
|
|
#include <tuple>
|
|
|
|
#include "Game/control/control.h"
|
|
#include "Game/effects/Hair.h"
|
|
#include "Game/Lara/lara_struct.h"
|
|
#include "Game/savegame.h"
|
|
#include "Game/Setup.h"
|
|
#include "Objects/Generic/Object/objects.h"
|
|
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
|
|
#include "Scripting/Include/ScriptInterfaceLevel.h"
|
|
#include "Specific/level.h"
|
|
|
|
using namespace TEN::Effects::Hair;
|
|
using namespace TEN::Renderer::Graphics;
|
|
|
|
namespace TEN::Renderer
|
|
{
|
|
template class VertexBuffer<Vertex>;
|
|
|
|
bool Renderer::PrepareDataForTheRenderer()
|
|
{
|
|
TENLog("Preparing renderer...", LogLevel::Info);
|
|
|
|
_lastBlendMode = BlendMode::Unknown;
|
|
_lastCullMode = CullMode::Unknown;
|
|
_lastDepthState = DepthState::Unknown;
|
|
|
|
_moveableObjects.resize(ID_NUMBER_OBJECTS);
|
|
_spriteSequences.resize(ID_NUMBER_OBJECTS);
|
|
_rooms.resize(g_Level.Rooms.size());
|
|
|
|
_meshes.clear();
|
|
|
|
_dynamicLightList = 0;
|
|
for (auto& dynamicLightList : _dynamicLights)
|
|
dynamicLightList.clear();
|
|
|
|
int allocatedItemSize = (int)g_Level.Items.size() + MAX_SPAWNED_ITEM_COUNT;
|
|
|
|
auto item = RendererItem();
|
|
_items = std::vector<RendererItem>(allocatedItemSize, item);
|
|
|
|
auto effect = RendererEffect();
|
|
_effects = std::vector<RendererEffect>(allocatedItemSize, effect);
|
|
|
|
TENLog("Allocated renderer object memory.", LogLevel::Info);
|
|
|
|
_animatedTextures.resize(g_Level.AnimatedTextures.size());
|
|
for (int i = 0; i < g_Level.AnimatedTextures.size(); i++)
|
|
{
|
|
TEXTURE* texture = &g_Level.AnimatedTextures[i];
|
|
Texture2D normal;
|
|
if (texture->normalMapData.size() < 1)
|
|
{
|
|
normal = CreateDefaultNormalTexture();
|
|
}
|
|
else
|
|
{
|
|
normal = Texture2D(_device.Get(), texture->normalMapData.data(), (int)texture->normalMapData.size());
|
|
}
|
|
|
|
TexturePair tex = std::make_tuple(Texture2D(_device.Get(), texture->colorMapData.data(), (int)texture->colorMapData.size()), normal);
|
|
_animatedTextures[i] = tex;
|
|
}
|
|
|
|
std::transform(g_Level.AnimatedTexturesSequences.begin(), g_Level.AnimatedTexturesSequences.end(), std::back_inserter(_animatedTextureSets), [](ANIMATED_TEXTURES_SEQUENCE& sequence)
|
|
{
|
|
RendererAnimatedTextureSet set{};
|
|
|
|
set.NumTextures = sequence.NumFrames;
|
|
set.Type = (AnimatedTextureType)sequence.Type;
|
|
set.Fps = sequence.Fps;
|
|
|
|
std::transform(sequence.Frames.begin(), sequence.Frames.end(), std::back_inserter(set.Textures), [](ANIMATED_TEXTURES_FRAME& frm)
|
|
{
|
|
RendererAnimatedTexture tex{};
|
|
|
|
tex.UV[0].x = frm.x1;
|
|
tex.UV[0].y = frm.y1;
|
|
tex.UV[1].x = frm.x2;
|
|
tex.UV[1].y = frm.y2;
|
|
tex.UV[2].x = frm.x3;
|
|
tex.UV[2].y = frm.y3;
|
|
tex.UV[3].x = frm.x4;
|
|
tex.UV[3].y = frm.y4;
|
|
|
|
float UMin = std::min({ tex.UV[0].x, tex.UV[1].x, tex.UV[2].x, tex.UV[3].x });
|
|
float VMin = std::min({ tex.UV[0].y, tex.UV[1].y, tex.UV[2].y, tex.UV[3].y });
|
|
float UMax = std::max({ tex.UV[0].x, tex.UV[1].x, tex.UV[2].x, tex.UV[3].x });
|
|
float VMax = std::max({ tex.UV[0].y, tex.UV[1].y, tex.UV[2].y, tex.UV[3].y });
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
tex.NormalizedUV[i].x = (tex.UV[i].x - UMin) / (UMax - UMin);
|
|
tex.NormalizedUV[i].y = (tex.UV[i].y - VMin) / (VMax - VMin);
|
|
}
|
|
|
|
return tex;
|
|
});
|
|
|
|
return set;
|
|
});
|
|
|
|
if (_animatedTextureSets.size() > 0)
|
|
TENLog("Generated " + std::to_string(_animatedTextureSets.size()) + " animated texture sets.", LogLevel::Info);
|
|
|
|
_roomTextures.resize(g_Level.RoomTextures.size());
|
|
for (int i = 0; i < g_Level.RoomTextures.size(); i++)
|
|
{
|
|
TEXTURE *texture = &g_Level.RoomTextures[i];
|
|
Texture2D normal;
|
|
if (texture->normalMapData.size() < 1)
|
|
{
|
|
normal = CreateDefaultNormalTexture();
|
|
}
|
|
else
|
|
{
|
|
normal = Texture2D(_device.Get(), texture->normalMapData.data(), (int)texture->normalMapData.size());
|
|
}
|
|
|
|
TexturePair tex = std::make_tuple(Texture2D(_device.Get(), texture->colorMapData.data(), (int)texture->colorMapData.size()), normal);
|
|
_roomTextures[i] = tex;
|
|
|
|
#ifdef DUMP_TEXTURES
|
|
char filename[255];
|
|
sprintf(filename, "dump\\room_%d.png", i);
|
|
|
|
std::ofstream outfile(filename, std::ios::out | std::ios::binary);
|
|
outfile.write(reinterpret_cast<const char*>(texture->colorMapData.data()), texture->colorMapData.size());
|
|
#endif
|
|
}
|
|
|
|
if (_roomTextures.size() > 0)
|
|
TENLog("Generated " + std::to_string(_roomTextures.size()) + " room texture atlases.", LogLevel::Info);
|
|
|
|
_moveablesTextures.resize(g_Level.MoveablesTextures.size());
|
|
for (int i = 0; i < g_Level.MoveablesTextures.size(); i++)
|
|
{
|
|
TEXTURE *texture = &g_Level.MoveablesTextures[i];
|
|
Texture2D normal;
|
|
if (texture->normalMapData.size() < 1)
|
|
{
|
|
normal = CreateDefaultNormalTexture();
|
|
}
|
|
else
|
|
{
|
|
normal = Texture2D(_device.Get(), texture->normalMapData.data(), (int)texture->normalMapData.size());
|
|
}
|
|
|
|
TexturePair tex = std::make_tuple(Texture2D(_device.Get(), texture->colorMapData.data(), (int)texture->colorMapData.size()), normal);
|
|
_moveablesTextures[i] = tex;
|
|
|
|
#ifdef DUMP_TEXTURES
|
|
char filename[255];
|
|
sprintf(filename, "dump\\moveable_%d.png", i);
|
|
|
|
std::ofstream outfile(filename, std::ios::out | std::ios::binary);
|
|
outfile.write(reinterpret_cast<const char*>(texture->colorMapData.data()), texture->colorMapData.size());
|
|
#endif
|
|
}
|
|
|
|
if (_moveablesTextures.size() > 0)
|
|
TENLog("Generated " + std::to_string(_moveablesTextures.size()) + " moveable texture atlases.", LogLevel::Info);
|
|
|
|
_staticTextures.resize(g_Level.StaticsTextures.size());
|
|
for (int i = 0; i < g_Level.StaticsTextures.size(); i++)
|
|
{
|
|
TEXTURE *texture = &g_Level.StaticsTextures[i];
|
|
Texture2D normal;
|
|
if (texture->normalMapData.size() < 1)
|
|
{
|
|
normal = CreateDefaultNormalTexture();
|
|
}
|
|
else
|
|
{
|
|
normal = Texture2D(_device.Get(), texture->normalMapData.data(), (int)texture->normalMapData.size());
|
|
}
|
|
|
|
TexturePair tex = std::make_tuple(Texture2D(_device.Get(), texture->colorMapData.data(), (int)texture->colorMapData.size()), normal);
|
|
_staticTextures[i] = tex;
|
|
|
|
#ifdef DUMP_TEXTURES
|
|
char filename[255];
|
|
sprintf(filename, "dump\\static_%d.png", i);
|
|
|
|
std::ofstream outfile(filename, std::ios::out | std::ios::binary);
|
|
outfile.write(reinterpret_cast<const char*>(texture->colorMapData.data()), texture->colorMapData.size());
|
|
#endif
|
|
}
|
|
|
|
if (_staticTextures.size() > 0)
|
|
TENLog("Generated " + std::to_string(_staticTextures.size()) + " static mesh texture atlases.", LogLevel::Info);
|
|
|
|
_spritesTextures.resize(g_Level.SpritesTextures.size());
|
|
for (int i = 0; i < g_Level.SpritesTextures.size(); i++)
|
|
{
|
|
auto& texture = g_Level.SpritesTextures[i];
|
|
_spritesTextures[i] = Texture2D(_device.Get(), texture.colorMapData.data(), (int)texture.colorMapData.size());
|
|
}
|
|
|
|
if (_spritesTextures.size() > 0)
|
|
TENLog("Generated " + std::to_string((int)_spritesTextures.size()) + " sprite atlases.", LogLevel::Info);
|
|
|
|
_skyTexture = Texture2D(_device.Get(), g_Level.SkyTexture.colorMapData.data(), (int)g_Level.SkyTexture.colorMapData.size());
|
|
|
|
TENLog("Loaded sky texture.", LogLevel::Info);
|
|
|
|
int totalVertices = 0;
|
|
int totalIndices = 0;
|
|
for (auto& room : g_Level.Rooms)
|
|
for (auto& bucket : room.buckets)
|
|
{
|
|
totalVertices += bucket.numQuads * 4 + bucket.numTriangles * 3;
|
|
totalIndices += bucket.numQuads * 6 + bucket.numTriangles * 3;
|
|
}
|
|
|
|
if (!totalVertices || !totalIndices)
|
|
throw std::exception("Level has no textured room geometry.");
|
|
|
|
_roomsVertices.resize(totalVertices);
|
|
_roomsIndices.resize(totalIndices);
|
|
|
|
TENLog("Loaded total " + std::to_string(totalVertices) + " room vertices.", LogLevel::Info);
|
|
|
|
int lastVertex = 0;
|
|
int lastIndex = 0;
|
|
|
|
TENLog("Preparing room data...", LogLevel::Info);
|
|
|
|
for (int i = 0; i < g_Level.Rooms.size(); i++)
|
|
{
|
|
ROOM_INFO& room = g_Level.Rooms[i];
|
|
|
|
RendererRoom* r = &_rooms[i];
|
|
|
|
r->RoomNumber = i;
|
|
r->AmbientLight = Vector4(room.ambient.x, room.ambient.y, room.ambient.z, 1.0f);
|
|
r->ItemsToDraw.reserve(MAX_ITEMS_DRAW);
|
|
r->EffectsToDraw.reserve(MAX_ITEMS_DRAW);
|
|
|
|
Vector3 boxMin = Vector3(room.Position.x + BLOCK(1), room.TopHeight - CLICK(1), room.Position.z + BLOCK(1));
|
|
Vector3 boxMax = Vector3(room.Position.x + (room.XSize - 1) * BLOCK(1), room.BottomHeight + CLICK(1), room.Position.z + (room.ZSize - 1) * BLOCK(1));
|
|
Vector3 center = (boxMin + boxMax) / 2.0f;
|
|
Vector3 extents = boxMax - center;
|
|
r->BoundingBox = BoundingBox(center, extents);
|
|
|
|
r->Neighbors.clear();
|
|
for (int j : room.NeighborRoomNumbers)
|
|
if (g_Level.Rooms[j].Active())
|
|
r->Neighbors.push_back(j);
|
|
|
|
if (room.doors.size() != 0)
|
|
{
|
|
r->Doors.resize((int)room.doors.size());
|
|
|
|
for (int l = 0; l < room.doors.size(); l++)
|
|
{
|
|
RendererDoor* door = &r->Doors[l];
|
|
ROOM_DOOR* oldDoor = &room.doors[l];
|
|
|
|
door->RoomNumber = oldDoor->room;
|
|
door->Normal = oldDoor->normal;
|
|
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
door->AbsoluteVertices[k] = Vector4(
|
|
room.Position.x + oldDoor->vertices[k].x,
|
|
room.Position.y + oldDoor->vertices[k].y,
|
|
room.Position.z + oldDoor->vertices[k].z,
|
|
1.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (room.mesh.size() != 0)
|
|
{
|
|
r->Statics.resize(room.mesh.size());
|
|
|
|
for (int l = 0; l < (int)room.mesh.size(); l++)
|
|
{
|
|
RendererStatic* staticInfo = &r->Statics[l];
|
|
MESH_INFO* oldMesh = &room.mesh[l];
|
|
|
|
oldMesh->Dirty = true;
|
|
|
|
staticInfo->ObjectNumber = oldMesh->staticNumber;
|
|
staticInfo->RoomNumber = oldMesh->roomNumber;
|
|
staticInfo->Color = oldMesh->color;
|
|
staticInfo->AmbientLight = r->AmbientLight;
|
|
staticInfo->Pose = staticInfo->PrevPose = oldMesh->pos;
|
|
staticInfo->OriginalSphere = Statics[staticInfo->ObjectNumber].visibilityBox.ToLocalBoundingSphere();
|
|
staticInfo->IndexInRoom = l;
|
|
|
|
staticInfo->Update(GetInterpolationFactor());
|
|
}
|
|
}
|
|
|
|
if (room.positions.empty())
|
|
continue;
|
|
|
|
for (auto& levelBucket : room.buckets)
|
|
{
|
|
RendererBucket bucket{};
|
|
|
|
bucket.Animated = levelBucket.animated;
|
|
bucket.BlendMode = static_cast<BlendMode>(levelBucket.blendMode);
|
|
bucket.Texture = levelBucket.texture;
|
|
bucket.StartVertex = lastVertex;
|
|
bucket.StartIndex = lastIndex;
|
|
bucket.NumVertices += levelBucket.numQuads * 4 + levelBucket.numTriangles * 3;
|
|
bucket.NumIndices += levelBucket.numQuads * 6 + levelBucket.numTriangles * 3;
|
|
bucket.Centre = Vector3::Zero;
|
|
|
|
for (auto& poly : levelBucket.polygons)
|
|
{
|
|
RendererPolygon newPoly;
|
|
|
|
newPoly.Shape = poly.shape;
|
|
|
|
newPoly.Centre = (
|
|
room.positions[poly.indices[0]] +
|
|
room.positions[poly.indices[1]] +
|
|
room.positions[poly.indices[2]]) / 3.0f;
|
|
|
|
Vector3 p1 = room.positions[poly.indices[0]];
|
|
Vector3 p2 = room.positions[poly.indices[1]];
|
|
Vector3 p3 = room.positions[poly.indices[2]];
|
|
|
|
Vector3 n = (p2 - p1).Cross(p3 - p1);
|
|
n.Normalize();
|
|
|
|
newPoly.Normal = n;
|
|
|
|
int baseVertices = lastVertex;
|
|
for (int k = 0; k < poly.indices.size(); k++)
|
|
{
|
|
Vertex* vertex = &_roomsVertices[lastVertex];
|
|
int index = poly.indices[k];
|
|
|
|
vertex->Position.x = room.Position.x + room.positions[index].x;
|
|
vertex->Position.y = room.Position.y + room.positions[index].y;
|
|
vertex->Position.z = room.Position.z + room.positions[index].z;
|
|
|
|
bucket.Centre += vertex->Position;
|
|
|
|
vertex->Normal = poly.normals[k];
|
|
vertex->UV = poly.textureCoordinates[k];
|
|
vertex->Color = Vector4(room.colors[index].x, room.colors[index].y, room.colors[index].z, 1.0f);
|
|
vertex->Tangent = poly.tangents[k];
|
|
vertex->Binormal = poly.binormals[k];
|
|
vertex->AnimationFrameOffset = poly.animatedFrame;
|
|
vertex->IndexInPoly = k;
|
|
vertex->OriginalIndex = index;
|
|
vertex->Effects = Vector4(room.effects[index].x, room.effects[index].y, room.effects[index].z, 0);
|
|
|
|
const unsigned long long primes[]{ 73856093ULL, 19349663ULL, 83492791ULL };
|
|
vertex->Hash = (unsigned int)std::hash<float>{}
|
|
((vertex->Position.x)* primes[0]) ^
|
|
((unsigned int)std::hash<float>{}(vertex->Position.y) * primes[1]) ^
|
|
(unsigned int)std::hash<float>{}(vertex->Position.z) * primes[2];
|
|
|
|
lastVertex++;
|
|
}
|
|
|
|
if (poly.shape == 0)
|
|
{
|
|
newPoly.BaseIndex = lastIndex;
|
|
|
|
_roomsIndices[lastIndex + 0] = baseVertices + 0;
|
|
_roomsIndices[lastIndex + 1] = baseVertices + 1;
|
|
_roomsIndices[lastIndex + 2] = baseVertices + 3;
|
|
_roomsIndices[lastIndex + 3] = baseVertices + 2;
|
|
_roomsIndices[lastIndex + 4] = baseVertices + 3;
|
|
_roomsIndices[lastIndex + 5] = baseVertices + 1;
|
|
|
|
lastIndex += 6;
|
|
}
|
|
else
|
|
{
|
|
newPoly.BaseIndex = lastIndex;
|
|
|
|
_roomsIndices[lastIndex + 0] = baseVertices + 0;
|
|
_roomsIndices[lastIndex + 1] = baseVertices + 1;
|
|
_roomsIndices[lastIndex + 2] = baseVertices + 2;
|
|
|
|
lastIndex += 3;
|
|
}
|
|
|
|
bucket.Polygons.push_back(newPoly);
|
|
}
|
|
|
|
bucket.Centre /= bucket.NumIndices;
|
|
|
|
r->Buckets.push_back(bucket);
|
|
}
|
|
|
|
if (room.lights.size() != 0)
|
|
{
|
|
r->Lights.resize(room.lights.size());
|
|
|
|
for (int l = 0; l < room.lights.size(); l++)
|
|
{
|
|
RendererLight* light = &r->Lights[l];
|
|
ROOM_LIGHT* oldLight = &room.lights[l];
|
|
|
|
if (oldLight->type == 0)
|
|
{
|
|
light->Color = Vector3(oldLight->r, oldLight->g, oldLight->b) * oldLight->intensity;
|
|
light->Intensity = oldLight->intensity;
|
|
light->Direction = Vector3(oldLight->dx, oldLight->dy, oldLight->dz);
|
|
light->CastShadows = oldLight->castShadows;
|
|
light->Type = LightType::Sun;
|
|
light->Luma = Luma(light->Color);
|
|
}
|
|
else if (oldLight->type == 1)
|
|
{
|
|
light->Position = Vector3(oldLight->x, oldLight->y, oldLight->z);
|
|
light->Color = Vector3(oldLight->r, oldLight->g, oldLight->b) * oldLight->intensity;
|
|
light->Intensity = oldLight->intensity;
|
|
light->In = oldLight->in;
|
|
light->Out = oldLight->out;
|
|
light->CastShadows = oldLight->castShadows;
|
|
light->Type = LightType::Point;
|
|
light->Luma = Luma(light->Color);
|
|
}
|
|
else if (oldLight->type == 3)
|
|
{
|
|
light->Position = Vector3(oldLight->x, oldLight->y, oldLight->z);
|
|
light->Color = Vector3(oldLight->r, oldLight->g, oldLight->b) * oldLight->intensity;
|
|
light->Intensity = oldLight->intensity;
|
|
light->In = oldLight->in;
|
|
light->Out = oldLight->out;
|
|
light->CastShadows = false;
|
|
light->Type = LightType::Shadow;
|
|
light->Luma = Luma(light->Color);
|
|
}
|
|
else if (oldLight->type == 2)
|
|
{
|
|
light->Position = Vector3(oldLight->x, oldLight->y, oldLight->z);
|
|
light->Color = Vector3(oldLight->r, oldLight->g, oldLight->b) * oldLight->intensity;
|
|
light->Intensity = oldLight->intensity;
|
|
light->Direction = Vector3(oldLight->dx, oldLight->dy, oldLight->dz);
|
|
light->In = oldLight->length;
|
|
light->Out = oldLight->cutoff;
|
|
light->InRange = oldLight->in;
|
|
light->OutRange = oldLight->out;
|
|
light->CastShadows = oldLight->castShadows;
|
|
light->Type = LightType::Spot;
|
|
light->Luma = Luma(light->Color);
|
|
}
|
|
else if (oldLight->type == 4)
|
|
{
|
|
light->Position = Vector3(oldLight->x, oldLight->y, oldLight->z);
|
|
light->Color = Vector3(oldLight->r, oldLight->g, oldLight->b);
|
|
light->Intensity = oldLight->intensity;
|
|
light->In = oldLight->in;
|
|
light->Out = oldLight->out;
|
|
light->Type = LightType::FogBulb;
|
|
light->Luma = Luma(light->Color);
|
|
}
|
|
|
|
// Monty's temp variables for sorting
|
|
light->LocalIntensity = 0;
|
|
light->Distance = 0;
|
|
light->RoomNumber = i;
|
|
light->AffectNeighbourRooms = light->Type != LightType::Sun;
|
|
|
|
oldLight++;
|
|
}
|
|
}
|
|
}
|
|
_roomsVertexBuffer = VertexBuffer<Vertex>(_device.Get(), (int)_roomsVertices.size(), &_roomsVertices[0]);
|
|
_roomsIndexBuffer = IndexBuffer(_device.Get(), (int)_roomsIndices.size(), _roomsIndices.data());
|
|
|
|
std::for_each(std::execution::par_unseq,
|
|
_rooms.begin(),
|
|
_rooms.end(),
|
|
[](RendererRoom& room)
|
|
{
|
|
std::sort(
|
|
room.Buckets.begin(),
|
|
room.Buckets.end(),
|
|
[](RendererBucket& a, RendererBucket& b)
|
|
{
|
|
if (a.BlendMode == b.BlendMode)
|
|
return (a.Texture < b.Texture);
|
|
else
|
|
return (a.BlendMode < b.BlendMode);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
TENLog("Preparing object data...", LogLevel::Info);
|
|
|
|
bool isSkinPresent = false;
|
|
|
|
totalVertices = 0;
|
|
totalIndices = 0;
|
|
for (int i = 0; i < MoveablesIds.size(); i++)
|
|
{
|
|
int objNum = MoveablesIds[i];
|
|
ObjectInfo* obj = &Objects[objNum];
|
|
|
|
for (int j = 0; j < obj->nmeshes; j++)
|
|
{
|
|
MESH* mesh = &g_Level.Meshes[obj->meshIndex + j];
|
|
|
|
for (auto& bucket : mesh->buckets)
|
|
{
|
|
totalVertices += bucket.numQuads * 4 + bucket.numTriangles * 3;
|
|
totalIndices += bucket.numQuads * 6 + bucket.numTriangles * 3;
|
|
}
|
|
}
|
|
}
|
|
_moveablesVertices.resize(totalVertices);
|
|
_moveablesIndices.resize(totalIndices);
|
|
|
|
lastVertex = 0;
|
|
lastIndex = 0;
|
|
for (int i = 0; i < MoveablesIds.size(); i++)
|
|
{
|
|
int objNum = MoveablesIds[i];
|
|
ObjectInfo *obj = &Objects[objNum];
|
|
|
|
if (obj->nmeshes > 0)
|
|
{
|
|
_moveableObjects[MoveablesIds[i]] = RendererObject();
|
|
RendererObject &moveable = *_moveableObjects[MoveablesIds[i]];
|
|
moveable.Id = MoveablesIds[i];
|
|
moveable.DoNotDraw = (obj->drawRoutine == nullptr);
|
|
moveable.ShadowType = obj->shadowType;
|
|
|
|
for (int j = 0; j < obj->nmeshes; j++)
|
|
{
|
|
// HACK: mesh pointer 0 is the placeholder for Lara's body parts and is right hand with pistols
|
|
// We need to override the bone index because the engine will take mesh 0 while drawing pistols anim,
|
|
// and vertices have bone index 0 and not 10.
|
|
RendererMesh *mesh = GetRendererMeshFromTrMesh(
|
|
&moveable,
|
|
&g_Level.Meshes[obj->meshIndex + j],
|
|
j, MoveablesIds[i] == ID_LARA_SKIN_JOINTS,
|
|
MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY, &lastVertex, &lastIndex);
|
|
|
|
moveable.ObjectMeshes.push_back(mesh);
|
|
_meshes.push_back(mesh);
|
|
}
|
|
|
|
if (objNum == ID_IMP_ROCK || objNum == ID_ENERGY_BUBBLES || objNum == ID_BUBBLES || objNum == ID_BODY_PART)
|
|
{
|
|
// HACK: these objects must have nmeshes = 0 because engine will use them in a different way while drawing Effects.
|
|
// In Core's code this was done in SETUP.C but we must do it here because we need to create renderer's meshes.
|
|
obj->nmeshes = 0;
|
|
}
|
|
else
|
|
{
|
|
for (int j = 0; j < obj->nmeshes; j++)
|
|
{
|
|
moveable.LinearizedBones.push_back(new RendererBone(j));
|
|
moveable.AnimationTransforms.push_back(Matrix::Identity);
|
|
moveable.BindPoseTransforms.push_back(Matrix::Identity);
|
|
}
|
|
|
|
if (obj->nmeshes > 1)
|
|
{
|
|
int *bone = &g_Level.Bones[obj->boneIndex];
|
|
|
|
std::stack<RendererBone *> stack;
|
|
|
|
RendererBone *currentBone = moveable.LinearizedBones[0];
|
|
RendererBone *stackBone = moveable.LinearizedBones[0];
|
|
|
|
for (int mi = 0; mi < obj->nmeshes - 1; mi++)
|
|
{
|
|
int j = mi + 1;
|
|
|
|
int opcode = *(bone++);
|
|
int linkX = *(bone++);
|
|
int linkY = *(bone++);
|
|
int linkZ = *(bone++);
|
|
|
|
byte flags = opcode & 0x1C;
|
|
|
|
moveable.LinearizedBones[j]->ExtraRotationFlags = flags;
|
|
|
|
switch (opcode & 0x03)
|
|
{
|
|
case 0:
|
|
moveable.LinearizedBones[j]->Parent = currentBone;
|
|
moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ);
|
|
currentBone->Children.push_back(moveable.LinearizedBones[j]);
|
|
currentBone = moveable.LinearizedBones[j];
|
|
break;
|
|
|
|
case 1:
|
|
if (stack.empty())
|
|
continue;
|
|
|
|
currentBone = stack.top();
|
|
stack.pop();
|
|
|
|
moveable.LinearizedBones[j]->Parent = currentBone;
|
|
moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ);
|
|
currentBone->Children.push_back(moveable.LinearizedBones[j]);
|
|
currentBone = moveable.LinearizedBones[j];
|
|
break;
|
|
|
|
case 2:
|
|
stack.push(currentBone);
|
|
|
|
moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ);
|
|
moveable.LinearizedBones[j]->Parent = currentBone;
|
|
currentBone->Children.push_back(moveable.LinearizedBones[j]);
|
|
currentBone = moveable.LinearizedBones[j];
|
|
break;
|
|
|
|
case 3:
|
|
if (stack.empty())
|
|
continue;
|
|
|
|
RendererBone *theBone = stack.top();
|
|
stack.pop();
|
|
|
|
moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ);
|
|
moveable.LinearizedBones[j]->Parent = theBone;
|
|
theBone->Children.push_back(moveable.LinearizedBones[j]);
|
|
currentBone = moveable.LinearizedBones[j];
|
|
stack.push(theBone);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int n = 0; n < obj->nmeshes; n++)
|
|
{
|
|
moveable.LinearizedBones[n]->Transform = Matrix::CreateTranslation(
|
|
moveable.LinearizedBones[n]->Translation.x,
|
|
moveable.LinearizedBones[n]->Translation.y,
|
|
moveable.LinearizedBones[n]->Translation.z);
|
|
}
|
|
|
|
moveable.Skeleton = moveable.LinearizedBones[0];
|
|
BuildHierarchy(&moveable);
|
|
|
|
// Fix player skin joints and hair units.
|
|
if (MoveablesIds[i] == ID_LARA_SKIN_JOINTS)
|
|
{
|
|
isSkinPresent = true;
|
|
int bonesToCheck[2] = { 0, 0 };
|
|
|
|
const auto& objSkin = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN);
|
|
|
|
for (int j = 1; j < obj->nmeshes; j++)
|
|
{
|
|
const auto* jointMesh = moveable.ObjectMeshes[j];
|
|
const auto* jointBone = moveable.LinearizedBones[j];
|
|
|
|
bonesToCheck[0] = jointBone->Parent->Index;
|
|
bonesToCheck[1] = j;
|
|
|
|
for (int b1 = 0; b1 < jointMesh->Buckets.size(); b1++)
|
|
{
|
|
const auto* jointBucket = &jointMesh->Buckets[b1];
|
|
|
|
for (int v1 = 0; v1 < jointBucket->NumVertices; v1++)
|
|
{
|
|
auto* jointVertex = &_moveablesVertices[jointBucket->StartVertex + v1];
|
|
|
|
bool isDone = false;
|
|
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
const auto* skinMesh = objSkin.ObjectMeshes[bonesToCheck[k]];
|
|
const auto* skinBone = objSkin.LinearizedBones[bonesToCheck[k]];
|
|
|
|
for (int b2 = 0; b2 < skinMesh->Buckets.size(); b2++)
|
|
{
|
|
const auto* skinBucket = &skinMesh->Buckets[b2];
|
|
for (int v2 = 0; v2 < skinBucket->NumVertices; v2++)
|
|
{
|
|
auto* skinVertex = &_moveablesVertices[skinBucket->StartVertex + v2];
|
|
|
|
// NOTE: Don't vectorize these coordinates, it breaks the connection in some cases. -- Lwmte, 21.12.24
|
|
|
|
int x1 = _moveablesVertices[jointBucket->StartVertex + v1].Position.x + jointBone->GlobalTranslation.x;
|
|
int y1 = _moveablesVertices[jointBucket->StartVertex + v1].Position.y + jointBone->GlobalTranslation.y;
|
|
int z1 = _moveablesVertices[jointBucket->StartVertex + v1].Position.z + jointBone->GlobalTranslation.z;
|
|
|
|
int x2 = _moveablesVertices[skinBucket->StartVertex + v2].Position.x + skinBone->GlobalTranslation.x;
|
|
int y2 = _moveablesVertices[skinBucket->StartVertex + v2].Position.y + skinBone->GlobalTranslation.y;
|
|
int z2 = _moveablesVertices[skinBucket->StartVertex + v2].Position.z + skinBone->GlobalTranslation.z;
|
|
|
|
// Joint vertex and skin mesh vertex are aligned, connect them.
|
|
if (abs(x1 - x2) < 2 && abs(y1 - y2) < 2 && abs(z1 - z2) < 2)
|
|
{
|
|
jointVertex->BoneIndex[0] = bonesToCheck[k];
|
|
jointVertex->Position = skinVertex->Position;
|
|
jointVertex->Normal = skinVertex->Normal;
|
|
|
|
isDone = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isDone)
|
|
break;
|
|
}
|
|
|
|
if (isDone)
|
|
break;
|
|
}
|
|
|
|
// Joint vertex and skin mesh vertex are not connected, specify both bone weights for blending.
|
|
if (!isDone)
|
|
{
|
|
jointVertex->BoneIndex[0] = j;
|
|
jointVertex->BoneWeight[0] = 0.5f;
|
|
jointVertex->BoneIndex[1] = jointBone->Parent->Index;
|
|
jointVertex->BoneWeight[1] = 0.5f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ((MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY) && isSkinPresent)
|
|
{
|
|
bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young);
|
|
bool isSecond = isYoung && MoveablesIds[i] == ID_HAIR_SECONDARY;
|
|
const auto& skinObj = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN);
|
|
const auto& settings = g_GameFlow->GetSettings()->Hair;
|
|
|
|
for (int j = 0; j < obj->nmeshes; j++)
|
|
{
|
|
const auto* currentMesh = moveable.ObjectMeshes[j];
|
|
const auto* currentBone = moveable.LinearizedBones[j];
|
|
|
|
for (const auto& currentBucket : currentMesh->Buckets)
|
|
{
|
|
for (int v1 = 0; v1 < currentBucket.NumVertices; v1++)
|
|
{
|
|
auto* currentVertex = &_moveablesVertices[currentBucket.StartVertex + v1];
|
|
currentVertex->BoneIndex[0] = j + 1;
|
|
|
|
// Link mesh 0 to root mesh.
|
|
if (j == 0)
|
|
{
|
|
const auto& vertices0 = isYoung ? settings[(int)PlayerHairType::YoungLeft].Indices :
|
|
settings[(int)PlayerHairType::Normal].Indices;
|
|
|
|
const auto& vertices1 = isYoung ? settings[(int)PlayerHairType::YoungRight].Indices :
|
|
settings[(int)PlayerHairType::Normal].Indices;
|
|
|
|
int rootMesh = HairUnit::GetRootMeshID(isSecond ? 1 : 0);
|
|
|
|
const auto* parentMesh = skinObj.ObjectMeshes[rootMesh];
|
|
const auto* parentBone = skinObj.LinearizedBones[rootMesh];
|
|
|
|
// Link listed vertices.
|
|
if ((!isSecond && currentVertex->OriginalIndex >= vertices0.size()) ||
|
|
(isSecond && currentVertex->OriginalIndex >= vertices1.size()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int b2 = 0; b2 < parentMesh->Buckets.size(); b2++)
|
|
{
|
|
const auto* parentBucket = &parentMesh->Buckets[b2];
|
|
for (int v2 = 0; v2 < parentBucket->NumVertices; v2++)
|
|
{
|
|
const auto* parentVertex = &_moveablesVertices[parentBucket->StartVertex + v2];
|
|
if ((parentVertex->OriginalIndex == vertices1[currentVertex->OriginalIndex] && isSecond) ||
|
|
(parentVertex->OriginalIndex == vertices0[currentVertex->OriginalIndex] && !isSecond))
|
|
{
|
|
currentVertex->BoneIndex[0] = 0;
|
|
currentVertex->Position = parentVertex->Position;
|
|
currentVertex->Normal = parentVertex->Normal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Link meshes > 0 to parent meshes.
|
|
else
|
|
{
|
|
const auto* parentMesh = moveable.ObjectMeshes[j - 1];
|
|
const auto* parentBone = moveable.LinearizedBones[j - 1];
|
|
|
|
for (int b2 = 0; b2 < parentMesh->Buckets.size(); b2++)
|
|
{
|
|
const auto* parentBucket = &parentMesh->Buckets[b2];
|
|
for (int v2 = 0; v2 < parentBucket->NumVertices; v2++)
|
|
{
|
|
auto* parentVertex = &_moveablesVertices[parentBucket->StartVertex + v2];
|
|
|
|
int x1 = _moveablesVertices[currentBucket.StartVertex + v1].Position.x + currentBone->GlobalTranslation.x;
|
|
int y1 = _moveablesVertices[currentBucket.StartVertex + v1].Position.y + currentBone->GlobalTranslation.y;
|
|
int z1 = _moveablesVertices[currentBucket.StartVertex + v1].Position.z + currentBone->GlobalTranslation.z;
|
|
|
|
int x2 = _moveablesVertices[parentBucket->StartVertex + v2].Position.x + parentBone->GlobalTranslation.x;
|
|
int y2 = _moveablesVertices[parentBucket->StartVertex + v2].Position.y + parentBone->GlobalTranslation.y;
|
|
int z2 = _moveablesVertices[parentBucket->StartVertex + v2].Position.z + parentBone->GlobalTranslation.z;
|
|
|
|
// FIXME: If a tolerance is used, a strange bug occurs where certain vertices don't connect. -- Lwmte, 14.12.2024
|
|
|
|
if (abs(x1 - x2) == 0 && abs(y1 - y2) == 0 && abs(z1 - z2) == 0)
|
|
{
|
|
currentVertex->BoneIndex[0] = j;
|
|
currentVertex->Position = parentVertex->Position;
|
|
currentVertex->Normal = parentVertex->Normal;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_moveablesVertexBuffer = VertexBuffer<Vertex>(_device.Get(), (int)_moveablesVertices.size(), &_moveablesVertices[0]);
|
|
_moveablesIndexBuffer = IndexBuffer(_device.Get(), (int)_moveablesIndices.size(), _moveablesIndices.data());
|
|
|
|
TENLog("Preparing static mesh data...", LogLevel::Info);
|
|
|
|
totalVertices = 0;
|
|
totalIndices = 0;
|
|
for (const auto& staticObj : Statics)
|
|
{
|
|
const auto& mesh = g_Level.Meshes[staticObj.meshNumber];
|
|
for (const auto& bucket : mesh.buckets)
|
|
{
|
|
totalVertices += (bucket.numQuads * 4) + (bucket.numTriangles * 3);
|
|
totalIndices += (bucket.numQuads * 6) + (bucket.numTriangles * 3);
|
|
}
|
|
}
|
|
|
|
_staticsVertices.resize(totalVertices);
|
|
_staticsIndices.resize(totalIndices);
|
|
|
|
lastVertex = 0;
|
|
lastIndex = 0;
|
|
for (const auto& staticObj : Statics)
|
|
{
|
|
auto newStaticObj = RendererObject();
|
|
newStaticObj.Type = 1;
|
|
newStaticObj.Id = staticObj.ObjectNumber;
|
|
|
|
auto& mesh = *GetRendererMeshFromTrMesh(&newStaticObj, &g_Level.Meshes[staticObj.meshNumber], 0, false, false, &lastVertex, &lastIndex);
|
|
|
|
newStaticObj.ObjectMeshes.push_back(&mesh);
|
|
_meshes.push_back(&mesh);
|
|
|
|
_staticObjects.push_back(newStaticObj);
|
|
}
|
|
|
|
_staticsVertexBuffer = VertexBuffer<Vertex>(_device.Get(), (int)_staticsVertices.size(), _staticsVertices.data());
|
|
_staticsIndexBuffer = IndexBuffer(_device.Get(), (int)_staticsIndices.size(), _staticsIndices.data());
|
|
|
|
TENLog("Preparing sprite data...", LogLevel::Info);
|
|
|
|
// Step 5: prepare sprites
|
|
_sprites.resize(g_Level.Sprites.size());
|
|
|
|
for (int i = 0; i < g_Level.Sprites.size(); i++)
|
|
{
|
|
SPRITE *oldSprite = &g_Level.Sprites[i];
|
|
_sprites[i] = RendererSprite();
|
|
RendererSprite &sprite = _sprites[i];
|
|
|
|
sprite.UV[0] = Vector2(oldSprite->x1, oldSprite->y1);
|
|
sprite.UV[1] = Vector2(oldSprite->x2, oldSprite->y2);
|
|
sprite.UV[2] = Vector2(oldSprite->x3, oldSprite->y3);
|
|
sprite.UV[3] = Vector2(oldSprite->x4, oldSprite->y4);
|
|
sprite.Texture = &_spritesTextures[oldSprite->tile];
|
|
sprite.Width = round((oldSprite->x2 - oldSprite->x1) * (float)sprite.Texture->Width + 1.0f);
|
|
sprite.Height = round((oldSprite->y3 - oldSprite->y2) * (float)sprite.Texture->Height + 1.0f);
|
|
sprite.X = oldSprite->x1 * sprite.Texture->Width;
|
|
sprite.Y = oldSprite->y1 * sprite.Texture->Height;
|
|
}
|
|
|
|
for (int i = 0; i < SpriteSequencesIds.size(); i++)
|
|
{
|
|
ObjectInfo *obj = &Objects[SpriteSequencesIds[i]];
|
|
|
|
if (obj->nmeshes < 0)
|
|
{
|
|
short numSprites = abs(obj->nmeshes);
|
|
short baseSprite = obj->meshIndex;
|
|
_spriteSequences[SpriteSequencesIds[i]] = RendererSpriteSequence();
|
|
|
|
// TODO: Why a custom =& operator is needed? It creates everytime new N null sprites
|
|
RendererSpriteSequence &sequence = _spriteSequences[SpriteSequencesIds[i]];
|
|
|
|
sequence.NumSprites = numSprites;
|
|
sequence.SpritesList.resize(numSprites);
|
|
for (int j = baseSprite; j < baseSprite + numSprites; j++)
|
|
{
|
|
sequence.SpritesList[j - baseSprite] = &_sprites[j];
|
|
}
|
|
|
|
_spriteSequences[SpriteSequencesIds[i]] = sequence;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RendererMesh* Renderer::GetRendererMeshFromTrMesh(RendererObject* obj, MESH* meshPtr, short boneIndex, int isJoints, int isHairs, int* lastVertex, int* lastIndex)
|
|
{
|
|
RendererMesh* mesh = new RendererMesh();
|
|
|
|
mesh->Sphere = meshPtr->sphere;
|
|
mesh->LightMode = meshPtr->lightMode;
|
|
|
|
if (meshPtr->positions.empty())
|
|
return mesh;
|
|
|
|
mesh->Positions.resize(meshPtr->positions.size());
|
|
for (int i = 0; i < meshPtr->positions.size(); i++)
|
|
mesh->Positions[i] = meshPtr->positions[i];
|
|
|
|
for (int n = 0; n < meshPtr->buckets.size(); n++)
|
|
{
|
|
BUCKET* levelBucket = &meshPtr->buckets[n];
|
|
RendererBucket bucket{};
|
|
bucket.Animated = levelBucket->animated;
|
|
bucket.Texture = levelBucket->texture;
|
|
bucket.BlendMode = static_cast<BlendMode>(levelBucket->blendMode);
|
|
bucket.StartVertex = *lastVertex;
|
|
bucket.StartIndex = *lastIndex;
|
|
bucket.NumVertices = levelBucket->numQuads * 4 + levelBucket->numTriangles * 3;
|
|
bucket.NumIndices = levelBucket->numQuads * 6 + levelBucket->numTriangles * 3;
|
|
|
|
for (int p = 0; p < (int)levelBucket->polygons.size(); p++)
|
|
{
|
|
POLYGON* poly = &levelBucket->polygons[p];
|
|
RendererPolygon newPoly;
|
|
|
|
newPoly.Shape = poly->shape;
|
|
newPoly.Centre = (
|
|
meshPtr->positions[poly->indices[0]] +
|
|
meshPtr->positions[poly->indices[1]] +
|
|
meshPtr->positions[poly->indices[2]]) / 3.0f;
|
|
|
|
int baseVertices = *lastVertex;
|
|
|
|
for (int k = 0; k < (int)poly->indices.size(); k++)
|
|
{
|
|
Vertex vertex;
|
|
int v = poly->indices[k];
|
|
|
|
vertex.Position.x = meshPtr->positions[v].x;
|
|
vertex.Position.y = meshPtr->positions[v].y;
|
|
vertex.Position.z = meshPtr->positions[v].z;
|
|
|
|
vertex.Normal.x = poly->normals[k].x;
|
|
vertex.Normal.y = poly->normals[k].y;
|
|
vertex.Normal.z = poly->normals[k].z;
|
|
|
|
vertex.Tangent.x = poly->tangents[k].x;
|
|
vertex.Tangent.y = poly->tangents[k].y;
|
|
vertex.Tangent.z = poly->tangents[k].z;
|
|
|
|
vertex.Binormal.x = poly->binormals[k].x;
|
|
vertex.Binormal.y = poly->binormals[k].y;
|
|
vertex.Binormal.z = poly->binormals[k].z;
|
|
|
|
vertex.UV.x = poly->textureCoordinates[k].x;
|
|
vertex.UV.y = poly->textureCoordinates[k].y;
|
|
|
|
vertex.Color.x = meshPtr->colors[v].x;
|
|
vertex.Color.y = meshPtr->colors[v].y;
|
|
vertex.Color.z = meshPtr->colors[v].z;
|
|
vertex.Color.w = 1.0f;
|
|
|
|
vertex.BoneIndex[0] = meshPtr->bones[v];
|
|
vertex.OriginalIndex = v;
|
|
|
|
vertex.Effects = Vector4(meshPtr->effects[v].x, meshPtr->effects[v].y, meshPtr->effects[v].z, poly->shineStrength);
|
|
vertex.Hash = (unsigned int)std::hash<float>{}
|
|
(vertex.Position.x) ^
|
|
(unsigned int)std::hash<float>{}(vertex.Position.y) ^
|
|
(unsigned int)std::hash<float>{}(vertex.Position.z);
|
|
|
|
if (obj->Type == 0)
|
|
_moveablesVertices[*lastVertex] = vertex;
|
|
else
|
|
_staticsVertices[*lastVertex] = vertex;
|
|
|
|
*lastVertex = *lastVertex + 1;
|
|
}
|
|
|
|
if (poly->shape == 0)
|
|
{
|
|
newPoly.BaseIndex = *lastIndex;
|
|
|
|
if (obj->Type == 0)
|
|
{
|
|
_moveablesIndices[newPoly.BaseIndex + 0] = baseVertices + 0;
|
|
_moveablesIndices[newPoly.BaseIndex + 1] = baseVertices + 1;
|
|
_moveablesIndices[newPoly.BaseIndex + 2] = baseVertices + 3;
|
|
_moveablesIndices[newPoly.BaseIndex + 3] = baseVertices + 2;
|
|
_moveablesIndices[newPoly.BaseIndex + 4] = baseVertices + 3;
|
|
_moveablesIndices[newPoly.BaseIndex + 5] = baseVertices + 1;
|
|
}
|
|
else
|
|
{
|
|
_staticsIndices[newPoly.BaseIndex + 0] = baseVertices + 0;
|
|
_staticsIndices[newPoly.BaseIndex + 1] = baseVertices + 1;
|
|
_staticsIndices[newPoly.BaseIndex + 2] = baseVertices + 3;
|
|
_staticsIndices[newPoly.BaseIndex + 3] = baseVertices + 2;
|
|
_staticsIndices[newPoly.BaseIndex + 4] = baseVertices + 3;
|
|
_staticsIndices[newPoly.BaseIndex + 5] = baseVertices + 1;
|
|
}
|
|
|
|
*lastIndex = *lastIndex + 6;
|
|
}
|
|
else
|
|
{
|
|
newPoly.BaseIndex = *lastIndex;
|
|
|
|
if (obj->Type == 0)
|
|
{
|
|
_moveablesIndices[newPoly.BaseIndex + 0] = baseVertices + 0;
|
|
_moveablesIndices[newPoly.BaseIndex + 1] = baseVertices + 1;
|
|
_moveablesIndices[newPoly.BaseIndex + 2] = baseVertices + 2;
|
|
}
|
|
else
|
|
{
|
|
_staticsIndices[newPoly.BaseIndex + 0] = baseVertices + 0;
|
|
_staticsIndices[newPoly.BaseIndex + 1] = baseVertices + 1;
|
|
_staticsIndices[newPoly.BaseIndex + 2] = baseVertices + 2;
|
|
}
|
|
|
|
*lastIndex = *lastIndex + 3;
|
|
}
|
|
|
|
bucket.Polygons.push_back(newPoly);
|
|
}
|
|
|
|
mesh->Buckets.push_back(bucket);
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
}
|