322 lines
12 KiB
C++
322 lines
12 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "TerrainChunk.h"
|
|
#include "Engine/Serialization/Serialization.h"
|
|
#include "TerrainPatch.h"
|
|
#include "Terrain.h"
|
|
#include "TerrainManager.h"
|
|
#include "Engine/Graphics/RenderView.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
#include "Engine/Graphics/Textures/GPUTexture.h"
|
|
#include "Engine/Renderer/RenderList.h"
|
|
#include "Engine/Graphics/RenderTools.h"
|
|
#include "Engine/Core/Math/OrientedBoundingBox.h"
|
|
#include "Engine/Level/Scene/Scene.h"
|
|
#if USE_EDITOR
|
|
#include "Engine/Level/Prefabs/PrefabManager.h"
|
|
#endif
|
|
|
|
TerrainChunk::TerrainChunk(const SpawnParams& params)
|
|
: ScriptingObject(params)
|
|
{
|
|
}
|
|
|
|
void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z)
|
|
{
|
|
// Initialize chunk properties
|
|
_patch = patch;
|
|
_x = x;
|
|
_z = z;
|
|
_yOffset = 0;
|
|
_yHeight = 1;
|
|
_heightmapUVScaleBias = Float4(1.0f, 1.0f, _x, _z) * (1.0f / Terrain::ChunksCountEdge);
|
|
_perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / (float)MAX_uint32);
|
|
OverrideMaterial = nullptr;
|
|
}
|
|
|
|
bool TerrainChunk::PrepareDraw(const RenderContext& renderContext)
|
|
{
|
|
// Calculate LOD
|
|
int32 lod;
|
|
const int32 forcedLod = _patch->_terrain->_forcedLod;
|
|
const int32 lodCount = _patch->Heightmap.Get()->StreamingTexture()->TotalMipLevels();
|
|
const int32 minStreamedLod = lodCount - _patch->Heightmap.Get()->GetTexture()->ResidentMipLevels();
|
|
if (forcedLod >= 0)
|
|
{
|
|
lod = forcedLod;
|
|
}
|
|
else
|
|
{
|
|
const int32 lodBias = _patch->_terrain->_lodBias;
|
|
const float lodDistribution = _patch->_terrain->_lodDistribution;
|
|
const float chunkEdgeSize = (_patch->_terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX);
|
|
|
|
// Calculate chunk distance to view
|
|
const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View);
|
|
const float distance = Float3::Distance(_sphere.Center - lodView->Origin, lodView->Position);
|
|
lod = (int32)Math::Pow(distance / chunkEdgeSize, lodDistribution);
|
|
lod += lodBias;
|
|
|
|
//lod = 0;
|
|
//lod = 10;
|
|
//lod = (_x + _z + Terrain::ChunksCountEdge * (_patch->_x + _patch->_z));
|
|
//lod = (int32)Vector2::Distance(Vector2(2, 2), Vector2(_patch->_x, _patch->_z) * Terrain::ChunksCountEdge + Vector2(_x, _z));
|
|
//lod = (int32)(Vector3::Distance(_bounds.GetCenter(), view.Position) / 10000.0f);
|
|
}
|
|
lod = Math::Clamp(lod, minStreamedLod, lodCount - 1);
|
|
|
|
// Pick a material
|
|
auto material = OverrideMaterial.Get();
|
|
if (!material || !material->IsLoaded())
|
|
{
|
|
material = _patch->_terrain->Material.Get();
|
|
if (!material || !material->IsLoaded())
|
|
material = TerrainManager::GetDefaultTerrainMaterial();
|
|
}
|
|
if (!material || !material->IsReady() || !material->IsTerrain())
|
|
return false;
|
|
|
|
// Cache data
|
|
_cachedDrawLOD = lod;
|
|
_cachedDrawMaterial = material;
|
|
return true;
|
|
}
|
|
|
|
void TerrainChunk::Draw(const RenderContext& renderContext) const
|
|
{
|
|
const int32 lod = _cachedDrawLOD;
|
|
const int32 minLod = Math::Max(lod + 1, 0);
|
|
const int32 chunkSize = _patch->_terrain->GetChunkSize();
|
|
|
|
// Setup draw call
|
|
DrawCall drawCall;
|
|
if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod))
|
|
return;
|
|
if (!_neighbors[0])
|
|
const_cast<TerrainChunk*>(this)->CacheNeighbors();
|
|
drawCall.InstanceCount = 1;
|
|
drawCall.Material = _cachedDrawMaterial;
|
|
renderContext.View.GetWorldMatrix(_transform, drawCall.World);
|
|
drawCall.ObjectPosition = drawCall.World.GetTranslation();
|
|
drawCall.ObjectRadius = (float)_sphere.Radius;
|
|
drawCall.Terrain.Patch = _patch;
|
|
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias;
|
|
drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z));
|
|
drawCall.Terrain.CurrentLOD = (float)lod;
|
|
drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1);
|
|
drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
|
|
// TODO: try using SIMD clamping for 4 chunks at once
|
|
drawCall.Terrain.NeighborLOD.X = (float)Math::Clamp<int32>(_neighbors[0]->_cachedDrawLOD, lod, minLod);
|
|
drawCall.Terrain.NeighborLOD.Y = (float)Math::Clamp<int32>(_neighbors[1]->_cachedDrawLOD, lod, minLod);
|
|
drawCall.Terrain.NeighborLOD.Z = (float)Math::Clamp<int32>(_neighbors[2]->_cachedDrawLOD, lod, minLod);
|
|
drawCall.Terrain.NeighborLOD.W = (float)Math::Clamp<int32>(_neighbors[3]->_cachedDrawLOD, lod, minLod);
|
|
const auto scene = _patch->_terrain->GetScene();
|
|
const auto flags = _patch->_terrain->_staticFlags;
|
|
if ((flags & StaticFlags::Lightmap) != StaticFlags::None && scene)
|
|
{
|
|
drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
|
|
drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea;
|
|
}
|
|
else
|
|
{
|
|
drawCall.Terrain.Lightmap = nullptr;
|
|
drawCall.Terrain.LightmapUVsArea = Rectangle::Empty;
|
|
}
|
|
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
|
|
drawCall.PerInstanceRandom = _perInstanceRandom;
|
|
|
|
// Add half-texel offset for heightmap sampling in vertex shader
|
|
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);
|
|
//const float halfTexelOffset = 0.5f / lodHeightmapSize;
|
|
//drawCall.TerrainData.HeightmapUVScaleBias.Z += halfTexelOffset;
|
|
//drawCall.TerrainData.HeightmapUVScaleBias.W += halfTexelOffset;
|
|
|
|
// Submit draw call
|
|
const DrawPass drawModes = _patch->_terrain->DrawModes & renderContext.View.Pass & drawCall.Material->GetDrawModes();
|
|
if (drawModes != DrawPass::None)
|
|
renderContext.List->AddDrawCall(renderContext, drawModes, flags, drawCall, true);
|
|
}
|
|
|
|
void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex) const
|
|
{
|
|
if (_patch->Heightmap == nullptr || !_patch->Heightmap->IsLoaded())
|
|
return;
|
|
if (!material || !material->IsReady() || !material->IsTerrain())
|
|
return;
|
|
|
|
const int32 lodCount = _patch->Heightmap.Get()->StreamingTexture()->TotalMipLevels();
|
|
const int32 lod = Math::Clamp(lodIndex, 0, lodCount);
|
|
const int32 chunkSize = _patch->_terrain->_chunkSize;
|
|
|
|
// Setup draw call
|
|
DrawCall drawCall;
|
|
if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod))
|
|
return;
|
|
drawCall.InstanceCount = 1;
|
|
drawCall.Material = material;
|
|
renderContext.View.GetWorldMatrix(_transform, drawCall.World);
|
|
drawCall.ObjectPosition = drawCall.World.GetTranslation();
|
|
drawCall.ObjectRadius = (float)_sphere.Radius;
|
|
drawCall.Terrain.Patch = _patch;
|
|
drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias;
|
|
drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z));
|
|
drawCall.Terrain.CurrentLOD = (float)lod;
|
|
drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1);
|
|
drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize;
|
|
drawCall.Terrain.NeighborLOD.X = (float)lod;
|
|
drawCall.Terrain.NeighborLOD.Y = (float)lod;
|
|
drawCall.Terrain.NeighborLOD.Z = (float)lod;
|
|
drawCall.Terrain.NeighborLOD.W = (float)lod;
|
|
const auto scene = _patch->_terrain->GetScene();
|
|
const auto flags = _patch->_terrain->_staticFlags;
|
|
if ((flags & StaticFlags::Lightmap) != StaticFlags::None && scene)
|
|
{
|
|
drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
|
|
drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea;
|
|
}
|
|
else
|
|
{
|
|
drawCall.Terrain.Lightmap = nullptr;
|
|
drawCall.Terrain.LightmapUVsArea = Rectangle::Empty;
|
|
}
|
|
drawCall.WorldDeterminantSign = RenderTools::GetWorldDeterminantSign(drawCall.World);
|
|
drawCall.PerInstanceRandom = _perInstanceRandom;
|
|
|
|
// Add half-texel offset for heightmap sampling in vertex shader
|
|
//const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod);
|
|
//const float halfTexelOffset = 0.5f / lodHeightmapSize;
|
|
//drawCall.TerrainData.HeightmapUVScaleBias.Z += halfTexelOffset;
|
|
//drawCall.TerrainData.HeightmapUVScaleBias.W += halfTexelOffset;
|
|
|
|
// Submit draw call
|
|
const DrawPass drawModes = _patch->_terrain->DrawModes & renderContext.View.Pass & drawCall.Material->GetDrawModes();
|
|
if (drawModes != DrawPass::None)
|
|
renderContext.List->AddDrawCall(renderContext, drawModes, flags, drawCall, true);
|
|
}
|
|
|
|
bool TerrainChunk::Intersects(const Ray& ray, Real& distance)
|
|
{
|
|
return _bounds.Intersects(ray, distance);
|
|
}
|
|
|
|
void TerrainChunk::UpdateBounds()
|
|
{
|
|
const Vector3 boundsExtent = _patch->_terrain->_boundsExtent;
|
|
const float size = (float)_patch->_terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX;
|
|
const Transform terrainTransform = _patch->_terrain->_transform;
|
|
|
|
Transform localTransform;
|
|
localTransform.Translation = _patch->_offset + Vector3(_x * size, _yOffset, _z * size);
|
|
localTransform.Orientation = Quaternion::Identity;
|
|
localTransform.Scale = Vector3(size, _yHeight, size);
|
|
localTransform = terrainTransform.LocalToWorld(localTransform);
|
|
|
|
OrientedBoundingBox obb(Vector3::Zero, Vector3::One);
|
|
obb.Transform(localTransform);
|
|
obb.GetBoundingBox(_bounds);
|
|
BoundingSphere::FromBox(_bounds, _sphere);
|
|
|
|
_bounds.Minimum -= boundsExtent;
|
|
_bounds.Maximum += boundsExtent;
|
|
}
|
|
|
|
void TerrainChunk::UpdateTransform()
|
|
{
|
|
const float size = _patch->_terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX;
|
|
Transform terrainTransform = _patch->_terrain->_transform;
|
|
Transform localTransform;
|
|
localTransform.Translation = _patch->_offset + Vector3(_x * size, _patch->_yOffset, _z * size);
|
|
localTransform.Orientation = Quaternion::Identity;
|
|
localTransform.Scale = Vector3(1.0f, _patch->_yHeight, 1.0f);
|
|
_transform = terrainTransform.LocalToWorld(localTransform);
|
|
}
|
|
|
|
void TerrainChunk::CacheNeighbors()
|
|
{
|
|
// Cache per-chunk neighbors (for morph transition)
|
|
// Fallback to this chunk if none existing on the edge
|
|
|
|
// 0: bottom
|
|
_neighbors[0] = this;
|
|
if (_z > 0)
|
|
{
|
|
_neighbors[0] = &_patch->Chunks[(_z - 1) * Terrain::ChunksCountEdge + _x];
|
|
}
|
|
else
|
|
{
|
|
const auto patch = _patch->_terrain->GetPatch(_patch->_x, _patch->_z - 1);
|
|
if (patch)
|
|
_neighbors[0] = &patch->Chunks[(Terrain::ChunksCountEdge - 1) * Terrain::ChunksCountEdge + _x];
|
|
}
|
|
|
|
// 1: left
|
|
_neighbors[1] = this;
|
|
if (_x > 0)
|
|
{
|
|
_neighbors[1] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x - 1)];
|
|
}
|
|
else
|
|
{
|
|
const auto patch = _patch->_terrain->GetPatch(_patch->_x - 1, _patch->_z);
|
|
if (patch)
|
|
_neighbors[1] = &patch->Chunks[_z * Terrain::ChunksCountEdge + (Terrain::ChunksCountEdge - 1)];
|
|
}
|
|
|
|
// 2: right
|
|
_neighbors[2] = this;
|
|
if (_x < Terrain::ChunksCountEdge - 1)
|
|
{
|
|
_neighbors[2] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x + 1)];
|
|
}
|
|
else
|
|
{
|
|
const auto patch = _patch->_terrain->GetPatch(_patch->_x + 1, _patch->_z);
|
|
if (patch)
|
|
_neighbors[2] = &patch->Chunks[_z * Terrain::ChunksCountEdge];
|
|
}
|
|
|
|
// 3: top
|
|
_neighbors[3] = this;
|
|
if (_z < Terrain::ChunksCountEdge - 1)
|
|
{
|
|
_neighbors[3] = &_patch->Chunks[(_z + 1) * Terrain::ChunksCountEdge + _x];
|
|
}
|
|
else
|
|
{
|
|
const auto patch = _patch->_terrain->GetPatch(_patch->_x, _patch->_z + 1);
|
|
if (patch)
|
|
_neighbors[3] = &patch->Chunks[_x];
|
|
}
|
|
}
|
|
|
|
void TerrainChunk::Serialize(SerializeStream& stream, const void* otherObj)
|
|
{
|
|
SERIALIZE_GET_OTHER_OBJ(TerrainChunk);
|
|
|
|
SERIALIZE_MEMBER(Offset, _yOffset);
|
|
SERIALIZE_MEMBER(Height, _yHeight);
|
|
SERIALIZE_MEMBER(Material, OverrideMaterial);
|
|
|
|
if (HasLightmap()
|
|
#if USE_EDITOR
|
|
&& !PrefabManager::IsCreatingPrefab
|
|
#endif
|
|
)
|
|
{
|
|
stream.JKEY("LightmapIndex");
|
|
stream.Int(Lightmap.TextureIndex);
|
|
|
|
stream.JKEY("LightmapArea");
|
|
stream.Rectangle(Lightmap.UVsArea);
|
|
}
|
|
}
|
|
|
|
void TerrainChunk::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
|
{
|
|
DESERIALIZE_MEMBER(Offset, _yOffset);
|
|
DESERIALIZE_MEMBER(Height, _yHeight);
|
|
DESERIALIZE_MEMBER(Material, OverrideMaterial);
|
|
DESERIALIZE_MEMBER(LightmapIndex, Lightmap.TextureIndex);
|
|
DESERIALIZE_MEMBER(LightmapArea, Lightmap.UVsArea);
|
|
}
|