diff options
Diffstat (limited to 'Runtime/Terrain')
39 files changed, 10230 insertions, 0 deletions
diff --git a/Runtime/Terrain/DetailDatabase.cpp b/Runtime/Terrain/DetailDatabase.cpp new file mode 100644 index 0000000..bc11b30 --- /dev/null +++ b/Runtime/Terrain/DetailDatabase.cpp @@ -0,0 +1,1106 @@ +#include "UnityPrefix.h" +#include "DetailDatabase.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Filters/Mesh/LodMeshFilter.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Geometry/TextureAtlas.h" +#include "PerlinNoise.h" +#include "TerrainData.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Graphics/LightmapSettings.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Scripting/ScriptingUtility.h" + + +const int kClampedVertexCount = 50000; +enum { kResolutionPerPatch = 8 }; + +static void UpdateAtlasTextureColorSpace(Texture2D* atlasTexture, Texture2D** sourceTextures, int sourceTextureCount) +{ + for (int i = 0; i < sourceTextureCount; ++i) + { + if (sourceTextures[i] != NULL && sourceTextures[i]->GetStoredColorSpace() != kTexColorSpaceLinear) + { + atlasTexture->SetStoredColorSpaceNoDirtyNoApply (kTexColorSpaceSRGB); + return; + } + } + atlasTexture->SetStoredColorSpaceNoDirtyNoApply(kTexColorSpaceLinear); +} + +// 8x8 dither table from http://en.wikipedia.org/wiki/Ordered_dithering +const float kDitherTable[] = { + 1, 49, 13, 61, 4, 52, 16, 64, + 33, 17, 45, 29, 36, 20, 48, 32, + 9, 57, 5, 53, 12, 60, 8, 56, + 41, 25, 37, 21, 44, 28, 40, 24, + 3, 51, 15, 63, 2, 50, 14, 62, + 35, 19, 47, 31, 34, 18, 46, 30, + 11, 59, 7, 55, 10, 58, 6, 54, + 43, 27, 39, 23, 42, 26, 38, 22, +}; + +DetailDatabase::DetailDatabase (TerrainData* terrainData, TreeDatabase* database) +{ + m_TerrainData = terrainData; + m_TreeDatabase = database; + m_WavingGrassTint = ColorRGBAf (0.7f, 0.6f, 0.5f, 0.0f); + m_WavingGrassStrength = .5F; + m_WavingGrassAmount = .5F; + m_WavingGrassSpeed = .5F; + m_PatchCount = 0; + m_PatchSamples = kResolutionPerPatch; + m_IsPrototypesDirty = true; + m_AtlasTexture = NULL; +} + +bool DetailDatabase::IsPatchEmpty (int x, int y) const +{ + return GetPatch(x,y).numberOfObjects.empty (); +} +bool DetailDatabase::IsPatchDirty (int x, int y) const +{ + return GetPatch(x,y).dirty; +} + +void DetailDatabase::SetDetailResolution (int resolution, int resolutionPerPatch) +{ + m_PatchCount = clamp<int>(resolution / resolutionPerPatch, 0, 10000); + m_PatchSamples = clamp<int>(resolutionPerPatch, 8, 1000); + + m_Patches.clear(); + m_Patches.resize (m_PatchCount * m_PatchCount); + + SetDirty(); +} + +void DetailDatabase::SetDetailPrototypesDirty () +{ + m_IsPrototypesDirty = true; +} + +void DetailDatabase::ResetDirtyDetails () +{ + for (int i=0;i<m_Patches.size();i++) + m_Patches[i].dirty = false; +} + +int DetailDatabase::AddLayerIndex (int detailIndex, DetailPatch &patch) +{ + for (int i=0;i<patch.layerIndices.size();i++) + { + if (patch.layerIndices[i] == detailIndex) + return i; + } + patch.layerIndices.push_back (detailIndex); + patch.numberOfObjects.resize (patch.numberOfObjects.size() + m_PatchSamples * m_PatchSamples); + return patch.layerIndices.size() - 1; +} + +void DetailDatabase::RemoveLocalLayerIndex (int detailIndex, DetailPatch& patch) +{ + Assert(detailIndex >= 0 || detailIndex < patch.layerIndices.size()); + + // Remove detail index out of numberofObjectsArray + int begin = detailIndex * m_PatchSamples * m_PatchSamples; + patch.numberOfObjects.erase (patch.numberOfObjects.begin() + begin, patch.numberOfObjects.begin() + begin + m_PatchSamples * m_PatchSamples); + + patch.layerIndices.erase(patch.layerIndices.begin() + detailIndex); +} + +int DetailDatabase::GetSupportedLayers (int xBase, int yBase, int totalWidth, int totalHeight, int *buffer) const +{ + if( m_PatchCount <= 0 ) + { + ErrorString ("Terrain has zero detail resolution"); + return 0; + } + + int* enabledLayers; + int prototypeCount = m_DetailPrototypes.size (); + ALLOC_TEMP(enabledLayers, int, prototypeCount); + memset(enabledLayers,0,sizeof (int) * prototypeCount); + + int minPatchX = clamp(xBase / m_PatchSamples, 0, m_PatchCount - 1); + int minPatchY = clamp(yBase / m_PatchSamples, 0, m_PatchCount - 1); + int maxPatchX = clamp((xBase+totalWidth) / m_PatchSamples, 0, m_PatchCount - 1); + int maxPatchY = clamp((yBase+totalHeight) / m_PatchSamples, 0, m_PatchCount - 1); + + for (int patchY=minPatchY;patchY<=maxPatchY;patchY++) + { + for (int patchX=minPatchX;patchX<=maxPatchX;patchX++) + { + int minX = clamp (xBase - patchX * m_PatchSamples, 0, m_PatchSamples - 1); + int minY = clamp (yBase - patchY * m_PatchSamples, 0, m_PatchSamples - 1); + + int maxX = clamp (xBase + totalWidth - patchX * m_PatchSamples, 0, m_PatchSamples); + int maxY = clamp (yBase + totalHeight - patchY * m_PatchSamples, 0, m_PatchSamples); + + int width = maxX - minX; + int height = maxY - minY; + if (width == 0 || height == 0) + continue; + + const DetailPatch& patch = GetPatch(patchX, patchY); + for (int l=0;l<patch.layerIndices.size();l++) + { + int layer = patch.layerIndices[l]; + enabledLayers[layer] = 1; + } + } + } + + int enabledCount = 0; + for (int i=0;i<prototypeCount;i++) + { + if (enabledLayers[i]) + { + if (buffer) + buffer[enabledCount] = i; + enabledCount++; + } + } + + return enabledCount; +} + + +void DetailDatabase::GetLayer (int xBase, int yBase, int totalWidth, int totalHeight, int detailIndex, int *buffer) const +{ + if( m_PatchCount <= 0 ) + { + ErrorString ("Terrain has zero detail resolution"); + return; + } + + int minPatchX = clamp(xBase / m_PatchSamples, 0, m_PatchCount - 1); + int minPatchY = clamp(yBase / m_PatchSamples, 0, m_PatchCount - 1); + int maxPatchX = clamp((xBase+totalWidth) / m_PatchSamples, 0, m_PatchCount - 1); + int maxPatchY = clamp((yBase+totalHeight) / m_PatchSamples, 0, m_PatchCount - 1); + + for (int patchY=minPatchY;patchY<=maxPatchY;patchY++) + { + for (int patchX=minPatchX;patchX<=maxPatchX;patchX++) + { + int minX = clamp(xBase - patchX * m_PatchSamples, 0, m_PatchSamples - 1); + int minY = clamp(yBase - patchY * m_PatchSamples, 0, m_PatchSamples - 1); + + int maxX = clamp(xBase + totalWidth - patchX * m_PatchSamples, 0, m_PatchSamples); + int maxY = clamp(yBase + totalHeight - patchY * m_PatchSamples, 0, m_PatchSamples); + + int width = maxX - minX; + int height = maxY - minY; + if (width == 0 || height == 0) + continue; + + int xOffset = minX + patchX * m_PatchSamples - xBase; + int yOffset = minY + patchY * m_PatchSamples - yBase; + + const DetailPatch &patch = GetPatch(patchX, patchY); + + const UInt8 *numberOfObjects = &patch.numberOfObjects[0]; + for (int l=0;l<patch.layerIndices.size();l++) + { + int layer = patch.layerIndices[l]; + if (layer != detailIndex) + continue; + + for (int y=0;y<height;y++) + { + for (int x=0;x<width;x++) + { + int nbOfObjects = numberOfObjects[GetIndex(minX + x, minY + y, l)]; + buffer[x + xOffset + (y + yOffset) * totalWidth] = nbOfObjects; + } + } + } + } + } +} + +void DetailDatabase::SetLayer (int xBase, int yBase, int totalWidth, int totalHeight, int detailIndex, const int *buffer) +{ + if (detailIndex >= m_DetailPrototypes.size()) + { + ErrorString ("Detail index out of bounds in DetailDatabase.SetLayers"); + return; + } + if (m_PatchCount <= 0) + { + ErrorString ("Terrain has zero detail resolution"); + return; + } + int minPatchX = clamp(xBase / m_PatchSamples, 0, m_PatchCount - 1); + int minPatchY = clamp(yBase / m_PatchSamples, 0, m_PatchCount - 1); + int maxPatchX = clamp((xBase+totalWidth) / m_PatchSamples, 0, m_PatchCount - 1); + int maxPatchY = clamp((yBase+totalHeight) / m_PatchSamples, 0, m_PatchCount - 1); + + for (int patchY=minPatchY;patchY<=maxPatchY;patchY++) + { + for (int patchX=minPatchX;patchX<=maxPatchX;patchX++) + { + int minX = clamp(xBase - patchX * m_PatchSamples, 0, m_PatchSamples - 1); + int minY = clamp(yBase - patchY * m_PatchSamples, 0, m_PatchSamples - 1); + + int maxX = clamp(xBase + totalWidth - patchX * m_PatchSamples, 0, m_PatchSamples); + int maxY = clamp(yBase + totalHeight - patchY * m_PatchSamples, 0, m_PatchSamples); + + int width = maxX - minX; + int height = maxY - minY; + if (width == 0 || height == 0) + continue; + + int xOffset = minX + patchX * m_PatchSamples - xBase; + int yOffset = minY + patchY * m_PatchSamples - yBase; + + DetailPatch& patch = GetPatch(patchX, patchY); + + int localLayerIndex = AddLayerIndex(detailIndex, patch); + UInt8* numberOfObjects = &patch.numberOfObjects[0]; + + for (int y=0;y<height;y++) + { + for (int x=0;x<width;x++) + { + // TODO: Is this the right order? + int nb = clamp(buffer[x + xOffset + (y + yOffset) * totalWidth], 0, 255); + int nbIndex = GetIndex(minX + x, minY + y, localLayerIndex); + if (nb != numberOfObjects[nbIndex]) + { + numberOfObjects[nbIndex] = nb; + patch.dirty = true; + } + } + } + + // Detect if this patch has zero details on this layer + // In that case delete the layer completely to save space + unsigned hasSomething = 0; + int oneLayerSampleCount = m_PatchSamples * m_PatchSamples; + for (int i=0;i<oneLayerSampleCount;i++) + hasSomething += numberOfObjects[localLayerIndex * oneLayerSampleCount + i]; + + if (hasSomething == 0) + RemoveLocalLayerIndex(localLayerIndex, patch); + } + } + SetDirty (); + + // All detail renderers will reload details that have patch.dirty set + // Then reset the patch.dirty = false on all patches. + m_TerrainData->UpdateUsers (TerrainData::kRemoveDirtyDetailsImmediately); + ResetDirtyDetails(); +} + +void DetailDatabase::CleanupPrototype (DetailPrototype &proto, string const& error) +{ + proto.vertices.clear(); + proto.uvs.clear(); + proto.colors.clear(); + proto.triangles.clear(); +} + + +DetailDatabase::~DetailDatabase () +{ + DestroySingleObject(m_AtlasTexture); +} + +namespace DetailDatabase_Static +{ +static SHADERPROP(MainTex); +} // namespace DetailDatabase_Static + +#if UNITY_EDITOR +// For thread loading we need to know the textures. Going through the meshes then textures is not thread safe. +void DetailDatabase::SetupPreloadTextureAtlasData () +{ + m_PreloadTextureAtlasData.resize(m_DetailPrototypes.size()); + + Texture2D** sourceTextures; + ALLOC_TEMP(sourceTextures, Texture2D*, m_DetailPrototypes.size()); + + RefreshPrototypesStep1(sourceTextures); + + for (int i=0;i<m_DetailPrototypes.size();i++) + { + m_PreloadTextureAtlasData[i] = sourceTextures[i]; + if (sourceTextures[i] == NULL) + { + WarningString("Missing detail texture in Terrain, degraded loading performance"); + m_PreloadTextureAtlasData.clear(); + break; + } + } + + SetDetailPrototypesDirty(); +} + +#endif + +void DetailDatabase::GenerateTextureAtlasThreaded () +{ + if (!m_PreloadTextureAtlasData.empty()) + { + AssertIf(m_PreloadTextureAtlasData.size() != m_DetailPrototypes.size()); + + Texture2D** sourceTextures; + ALLOC_TEMP(sourceTextures, Texture2D*, m_PreloadTextureAtlasData.size()); + + int i; + for (i=0;i<m_PreloadTextureAtlasData.size();i++) + { + Texture2D* tex = dynamic_pptr_cast<Texture2D*> (InstanceIDToObjectThreadSafe(m_PreloadTextureAtlasData[i].GetInstanceID())); + if (tex == NULL) + break; + sourceTextures[i] = tex; + } + + if (i == m_PreloadTextureAtlasData.size()) + { + AssertIf (m_AtlasTexture != NULL); + + m_AtlasTexture = NEW_OBJECT_FULL(Texture2D, kCreateObjectFromNonMainThread); + m_AtlasTexture->Reset(); + m_AtlasTexture->AwakeFromLoadThreaded(); + + // ok, just from performance standpoint we don't want to upload texture here + // or get an assert from uninited texture, so, let's cheat + m_AtlasTexture->HackSetAwakeDidLoadThreadedWasCalled(); + + m_PreloadTextureAtlasUVLayout.resize(m_PreloadTextureAtlasData.size()); + + UpdateAtlasTextureColorSpace(m_AtlasTexture, sourceTextures, m_PreloadTextureAtlasData.size()); + PackTextureAtlasSimple (m_AtlasTexture, 2048, m_PreloadTextureAtlasData.size(), sourceTextures, &m_PreloadTextureAtlasUVLayout[0], 0, false, false); + } + } +} + +void DetailDatabase::RefreshPrototypesStep1 (Texture2D** sourceTextures) +{ + using namespace DetailDatabase_Static; + + for (int i=0;i<m_DetailPrototypes.size();i++) + { + DetailPrototype& proto = m_DetailPrototypes[i]; + sourceTextures[i] = NULL; + + GameObject *prototype = proto.prototype; + if (proto.usePrototypeMesh && prototype) + { + Renderer* renderer = prototype->QueryComponent (Renderer); + if (renderer == NULL) + { + CleanupPrototype(proto, Append("Missing renderer ", prototype->GetName())); + continue; + } + + if (renderer->GetMaterialCount() != 1) + { + CleanupPrototype(proto, Append(proto.prototype->GetName(), " must have exactly one material.")); + continue; + } + + Material *sharedMaterial = renderer->GetMaterial (0); + if (sharedMaterial == NULL) + { + CleanupPrototype(proto, Append("Missing material ", proto.prototype->GetName())); + continue; + } + + MeshFilter *filter = prototype->QueryComponent(MeshFilter); + if (filter == NULL) + { + CleanupPrototype(proto, Append("Missing mesh filter ", proto.prototype->GetName())); + continue; + } + + Mesh* mesh = filter->GetSharedMesh(); + if (mesh == NULL) + { + CleanupPrototype(proto, Append ("Missing mesh ", proto.prototype->GetName())); + continue; + } + + proto.vertices.assign (mesh->GetVertexBegin(), mesh->GetVertexEnd()); + if (proto.vertices.empty()) + { + CleanupPrototype(proto, Append ("No vertices available ", prototype->GetName())); + continue; + } + + // Colors and normals are not optional here. Default to something + if (mesh->IsAvailable (kShaderChannelColor)) + { + proto.colors.assign (mesh->GetColorBegin (), mesh->GetColorEnd () ); + } + else + { + proto.colors.clear (); + proto.colors.resize (mesh->GetVertexCount(), ColorRGBA32(0xFFFFFFFF)); + } + + if (mesh->IsAvailable (kShaderChannelNormal)) + { + proto.normals.assign(mesh->GetNormalBegin (), mesh->GetNormalEnd ()); + } + else + { + proto.normals.clear (); + proto.normals.resize (mesh->GetVertexCount(), Vector3f(0,1,0)); + } + + if (mesh->IsAvailable (kShaderChannelTexCoord0)) + { + proto.uvs.assign (mesh->GetUvBegin(0), mesh->GetUvEnd(0)); + } + else + { + CleanupPrototype(proto, Append("No uvs available ", proto.prototype->GetName())); + continue; + } + + Mesh::TemporaryIndexContainer tempBuffer; + mesh->GetTriangles (tempBuffer); + proto.triangles.assign (tempBuffer.begin(), tempBuffer.end()); + + if (proto.triangles.empty()) + { + CleanupPrototype(proto, Append("No triangles available ", proto.prototype->GetName())); + continue; + } + + if (sharedMaterial) + sourceTextures[i] = dynamic_pptr_cast <Texture2D*>(sharedMaterial->GetTexture (kSLPropMainTex)); + } + // We don't have a mesh, but we have a texture: it's grass quads + else if( !proto.usePrototypeMesh && proto.prototypeTexture.IsValid() ) + { + float halfWidth = 0.5F; + float height = 1.0F; + // color modifier at the top of the grass. + // billboard top vertex color = topColor * perlinNoise * 2 + // Was 1.5f before we doublemultiplied + ColorRGBA32 topColor = GfxDevice::ConvertToDeviceVertexColor( ColorRGBA32 (255, 255, 255, 255) ); + ColorRGBA32 bottomColor = GfxDevice::ConvertToDeviceVertexColor( ColorRGBA32 (160,160,160, 0) ); + + Vector3f vertices[] = { + Vector3f (-halfWidth, 0, 0), + Vector3f (-halfWidth, height, 0), + Vector3f (halfWidth, height, 0), + Vector3f (halfWidth, 0, 0), + }; + + ColorRGBA32 colors[] = { + bottomColor, topColor, topColor, bottomColor, + }; + Vector2f uvs[] = { + Vector2f (0, 0), Vector2f (0, 1), Vector2f (1, 1), Vector2f (1, 0), + }; + UInt16 triangles[] = { + 0, 1, 2, 2, 3, 0, + }; + + const int actualVertexCount = 4; + const int actualIndexCount = 6; + + // skip normals creation, since they will be taken from the terrain in GenerateMesh() + + proto.vertices.assign (vertices, vertices + actualVertexCount); + proto.colors.assign (colors, colors + actualVertexCount); + proto.uvs.assign (uvs, uvs + actualVertexCount); + proto.triangles.assign (triangles, triangles + actualIndexCount); + sourceTextures[i] = proto.prototypeTexture; + } + else + { + if (proto.prototype) + CleanupPrototype(proto, Append("Missing prototype ", proto.prototype->GetName())); + else + CleanupPrototype(proto, "Missing prototype"); + continue; + } + } +} + + +void DetailDatabase::RefreshPrototypes () +{ + Texture2D** sourceTextures; + ALLOC_TEMP(sourceTextures, Texture2D*, m_DetailPrototypes.size()); + + RefreshPrototypesStep1(sourceTextures); + + // Normal non-threaded creation mode + if (m_AtlasTexture == NULL || m_AtlasTexture->IsInstanceIDCreated()) + { + // Not created yet + if (m_AtlasTexture == NULL) + { + m_AtlasTexture = CreateObjectFromCode<Texture2D>(); + m_AtlasTexture->SetHideFlags (Object::kHideAndDontSave); + m_AtlasTexture->InitTexture(2, 2, kTexFormatARGB32, Texture2D::kMipmapMask, 1); + m_AtlasTexture->SetWrapMode(kTexWrapClamp); + } + + // TODO: Make 4096 a property & clamp to GFX card, detail settings + Rectf* rects; + ALLOC_TEMP(rects, Rectf, m_DetailPrototypes.size()); + + UpdateAtlasTextureColorSpace(m_AtlasTexture, sourceTextures, m_DetailPrototypes.size()); + PackTextureAtlasSimple (m_AtlasTexture, 2048, m_DetailPrototypes.size(), sourceTextures, rects, 0, true, false); + + for (int i=0;i<m_DetailPrototypes.size();i++) + { + DetailPrototype &proto = m_DetailPrototypes[i]; + Rectf r = rects[i]; + float w = r.Width(); + float h = r.Height(); + for (int v=0;v<proto.uvs.size();v++) + { + proto.uvs[v].x = proto.uvs[v].x * w + r.x; + proto.uvs[v].y = proto.uvs[v].y * h + r.y; + } + } + } + // Generated in loading thread - Just upload + else + { + Object::AllocateAndAssignInstanceID(m_AtlasTexture); + m_AtlasTexture->SetHideFlags (Object::kHideAndDontSave); + m_AtlasTexture->SetWrapMode( kTexWrapClamp ); + + AssertIf (m_PreloadTextureAtlasUVLayout.size() != m_DetailPrototypes.size()); + for (int i=0;i<m_DetailPrototypes.size();i++) + { + DetailPrototype &proto = m_DetailPrototypes[i]; + Rectf r = m_PreloadTextureAtlasUVLayout[i]; + float w = r.Width(); + float h = r.Height(); + for (int v=0;v<proto.uvs.size();v++) + { + proto.uvs[v].x = proto.uvs[v].x * w + r.x; + proto.uvs[v].y = proto.uvs[v].y * h + r.y; + } + } + + m_AtlasTexture->AwakeFromLoad(kDefaultAwakeFromLoad); + } + + m_IsPrototypesDirty = false; +} + + +void DetailDatabase::SetDirty () +{ + m_TerrainData->SetDirty (); +} + +void DetailDatabase::SetDetailPrototypes (const vector<DetailPrototype> & detailPrototypes) +{ + m_DetailPrototypes = detailPrototypes; + RefreshPrototypes (); + SetDirty (); + m_TerrainData->UpdateUsers (TerrainData::kFlushEverythingImmediately); +} + +void DetailDatabase::RemoveDetailPrototype (int index) +{ + #if UNITY_EDITOR + + if( index < 0 || index >= m_DetailPrototypes.size() ) + { + ErrorString("invalid detail prototype index"); + return; + } + + // erase detail prototype + m_DetailPrototypes.erase( m_DetailPrototypes.begin() + index ); + + // update detail patches + for( size_t i = 0; i < m_Patches.size(); ++i ) + { + DetailPatch& patch = m_Patches[i]; + int localIndex = -1; + for( size_t j = 0; j < patch.layerIndices.size(); ++j ) + { + if( patch.layerIndices[j] == index ) + localIndex = j; + else if( patch.layerIndices[j] > index ) + --patch.layerIndices[j]; + } + if( localIndex == -1 ) + continue; + + AssertIf( patch.numberOfObjects.size() != patch.layerIndices.size() * m_PatchSamples * m_PatchSamples ); + + patch.layerIndices.erase( patch.layerIndices.begin() + localIndex ); + patch.numberOfObjects.erase( + patch.numberOfObjects.begin() + localIndex * m_PatchSamples * m_PatchSamples, + patch.numberOfObjects.begin() + (localIndex+1) * m_PatchSamples * m_PatchSamples ); + } + + RefreshPrototypes (); + SetDirty(); + m_TerrainData->UpdateUsers (TerrainData::kFlushEverythingImmediately); + + #else + ErrorString("only implemented in editor"); + #endif +} + +static void CopyVertex (Vector3f *src, Vector3f *dst, const Matrix4x4f &transform, int offset, int count) +{ + for (int i=0;i<count;i++) + dst[i+offset] = transform.MultiplyPoint3(src[i]); +} + +static void CopyVertex (Vector3f pos, Vector3f *dst, int offset, int count) +{ + for (int i=0;i<count;i++) + dst[i+offset] = pos; +} + +static void CopyNormal (Vector3f* src, Vector3f* dst, Quaternionf rot, int offset, int count) +{ + for (int i=0;i<count;i++) + dst[i+offset] = RotateVectorByQuat (rot, src[i]); +} + +static void CopyNormalFromTerrain (const Heightmap& heightmap, float normalizedX, float normalizedZ, Vector3f* dst, int offset, int count) +{ + Vector3f terrainNormal = heightmap.GetInterpolatedNormal(normalizedX, normalizedZ); + + for (int i = 0; i < count; i++) + dst[i+offset] = terrainNormal; +} + +static void CopyUV (Vector2f* src, Vector2f *dst, int offset, int count) +{ + for (int i=0;i<count;i++) + dst[i+offset] = src[i]; +} + +static void CopyTangents (Vector2f* src, Vector4f *dst, int offset, int count) +{ + for (int i = 0; i < count; i++) + { + Vector2f srcVector = src[i]; + Vector4f& dstVector = dst[i+offset]; + + dstVector.x = srcVector.x; + dstVector.y = srcVector.y; + } +} + +static void CopyUVFromTerrain (float normalizedX, float normalizedZ, Vector2f *dst, int offset, int count) +{ + Vector2f lightmapUV(normalizedX, normalizedZ); + for (int i = 0; i < count; i++) + dst[i+offset] = lightmapUV; +} + +inline ColorRGBA32 MultiplyDouble (const ColorRGBA32 &inC0, const ColorRGBA32 &inC1) +{ + return ColorRGBA32 ( + std::min (((int)inC0.r * (int)inC1.r) / 128, 255), + std::min (((int)inC0.g * (int)inC1.g) / 128, 255), + std::min (((int)inC0.b * (int)inC1.b) / 128, 255), + std::min (((int)inC0.a * (int)inC1.a) / 128, 255) + ); +} + +static void CopyColor (ColorRGBA32* src, ColorRGBA32* dst, ColorRGBA32 scale, int offset, int count) +{ + for (int i=0;i<count;i++) + dst[i+offset] = src[i] * scale; +} + +/*Rectf DetailDatabase::GetNormalizedArea (int x, int y) +{ + float fx = (float)x / m_PatchCount; + float fy = (float)y / m_PatchCount; + float size = 1.0F / m_PatchCount; + return Rectf (fx, fy, fx+size, fy+size); +} +*/ +/* +void DetailDatabase::GenerateBounds (DetailPatch &patch, int patchX, int patchY) +{ + if (patch.numberOfObjects.size() != 0) + { + Mesh mesh = new Mesh (); + + GenerateMesh (mesh, patch, patchX, patchY, m_Heightmap.size, false, DetailRenderMode.Grass); + patch.bounds = mesh.bounds; +// Debug.Log(patch.bounds.min); +// Debug.Log(patch.bounds.max); + DestroyImmediate (mesh); + } +} +*/ + +PROFILER_INFORMATION(gBuildDetailMesh, "Terrain.Details.BuildPatchMesh", kProfilerRender) +PROFILER_INFORMATION(gExtractLightmap, "DetailMesh.ExtractLightmap", kProfilerRender); +PROFILER_INFORMATION(gSetup, "DetailMesh.Setup", kProfilerRender); +PROFILER_INFORMATION(gBuildData, "DetailMesh.BuildData", kProfilerRender); +PROFILER_INFORMATION(gAssignToMesh, "DetailMesh.AssignToMesh", kProfilerRender); + +Mesh* DetailDatabase::BuildMesh (int patchX, int patchY, Vector3f size, int lightmapIndex, DetailRenderMode renderMode, float density) +{ + int totalTriangleCount, totalVertexCount; + + PROFILER_AUTO(gBuildDetailMesh, NULL) + + DetailPatch &patch = GetPatch (patchX, patchY); + ComputeVertexAndTriangleCount(patch, renderMode, density, &totalVertexCount, &totalTriangleCount); + if (totalTriangleCount == 0 || totalVertexCount == 0) + return NULL; + else + { + Mesh* mesh = NEW_OBJECT(Mesh); + mesh->Reset(); + mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + mesh->SetHideFlags(Object::kHideAndDontSave); + GenerateMesh (*mesh, patchX, patchY, size, lightmapIndex, renderMode, density, totalVertexCount, totalTriangleCount); + return mesh; + } +} + +// Fixes bounds of the patch of billboards. Since each quad making a billboard +// has its vertices collapsed in one point, the bounding box does not take +// the height nor the width of the billboard into account. +inline void ExpandDetailBillboardBounds(Mesh& mesh, float detailMaxHalfWidth, float detailMaxHeight) +{ + // The origin of the billboard is in the middle of the bottom edge. + AABB aabb = mesh.GetBounds(); + + // The billboard always faces the camera, so when looking from the top + // it's the height of the billboard that extends in the XZ plane. + float maxHalfWidth = std::max(detailMaxHalfWidth, detailMaxHeight); + + aabb.m_Extent += Vector3f(maxHalfWidth, 0.5f * detailMaxHeight, maxHalfWidth); + aabb.m_Center += Vector3f(0, 0.5f * detailMaxHeight, 0); + mesh.SetBounds(aabb); +} + +void DetailDatabase::GenerateMesh (Mesh& mesh, int patchX, int patchY, Vector3f size, int lightmapIndex, DetailRenderMode renderMode, float density, int totalVertexCount, int totalTriangleCount) +{ + PROFILER_BEGIN_INTERNAL(gSetup, NULL); + DetailPatch &patch = GetPatch (patchX, patchY); + Vector3f* vertices; + ALLOC_TEMP(vertices, Vector3f, totalVertexCount); + + Vector2f* uvs; + ALLOC_TEMP(uvs, Vector2f, totalVertexCount); + + int uv2Count = totalVertexCount; + + Vector2f* uvs2 = NULL; + ALLOC_TEMP(uvs2, Vector2f, uv2Count); + + int tangentCount = 0; + if (renderMode == kDetailBillboard) + tangentCount = totalVertexCount; + + Vector4f* tangents = NULL; + ALLOC_TEMP(tangents, Vector4f, tangentCount); + + ColorRGBA32* colors; + ALLOC_TEMP(colors, ColorRGBA32, totalVertexCount); + + int normalCount = totalVertexCount; + Vector3f* normals = NULL; + ALLOC_TEMP (normals, Vector3f, normalCount); + + UInt16* triangles; + ALLOC_TEMP (triangles, UInt16, totalTriangleCount); + + int triangleCount = 0; + int vertexCount = 0; + float randomResolutionSize = 1.0F / GetResolution(); + int res = m_PatchSamples; + + Heightmap* heightmap = &m_TerrainData->GetHeightmap(); +/// int samplesPerHeixel = m_SamplesPerHeixel; +/// int heixels = m_SamplesPerHeixel; + +// int xBaseHeightmap = patchX * m_PatchSamples / m_SamplesPerHeixel; +// int yBaseHeightmap = patchY * m_PatchSamples / m_SamplesPerHeixel; + + PROFILER_END_INTERNAL; + + PROFILER_BEGIN_INTERNAL(gBuildData, NULL); + + float detailMaxHalfWidth = 0.0f; + float detailMaxHeight = 0.0f; + + for (int i=0;i<patch.layerIndices.size();i++) + { + DetailPrototype& prototype = m_DetailPrototypes[patch.layerIndices[i]]; + + if (prototype.renderMode != renderMode) + continue; + + Vector3f* prototypeVertices = prototype.vertices.size() > 0 ? &prototype.vertices[0] : NULL; + Vector3f* prototypeNormals = prototype.normals.size() > 0 ? &prototype.normals[0] : NULL; + Vector2f* prototypeUvs = prototype.uvs.size() > 0 ? &prototype.uvs[0] : NULL; + ColorRGBA32* prototypeColors = prototype.colors.size() > 0 ? &prototype.colors[0] : NULL; + UInt16* prototypeTris = prototype.triangles.size() > 0 ? &prototype.triangles[0] : NULL; + float noiseSpread = prototype.noiseSpread; + ColorRGBAf dry = prototype.dryColor; + ColorRGBAf healthy = prototype.healthyColor; + + float halfGrassWidth = prototype.minWidth * 0.5F; + float halfGrassWidthDelta = (prototype.maxWidth - prototype.minWidth) * .5F; + float grassHeight = prototype.minHeight; + float grassHeightDelta = prototype.maxHeight - prototype.minHeight; + int prototypeTrisSize = prototype.triangles.size(); + int prototypeVerticesSize = prototype.vertices.size(); + + if (prototypeVerticesSize == 0) + continue; + + for (int y=0;y<res;y++) + { + for (int x=0;x<res;x++) + { + int nbIndex = y * res + x + i * res * res; + int origCount = patch.numberOfObjects[nbIndex]; + if (origCount == 0) + continue; + + float nx = (float)patchX / m_PatchCount + (float)x / (res * m_PatchCount); + float ny = (float)patchY / m_PatchCount + (float)y / (res * m_PatchCount); + m_Random.SetSeed (nbIndex + (patchX * m_PatchCount + patchY) * 1013); + + // Clamp the number of genrated details to not generate more than kClampedVertex vertices + int maxCount = (kClampedVertexCount - vertexCount) / prototypeVerticesSize; + origCount = std::min(maxCount, origCount); + + int newCount = (int)(origCount * density + (kDitherTable[(x&7)*8+(y&7)] - 0.5f) / 64.0f); + for (int k=0;k<newCount;k++) + { + // Generate position & rotation + + float normalizedX = nx + m_Random.GetFloat() * randomResolutionSize; + float normalizedZ = ny + m_Random.GetFloat() * randomResolutionSize; + + // normalizedX = nx + 0.5F * randomSize; + // normalzedZ = ny + 0.5F * randomSize; + Vector3f pos; + pos.y = heightmap->GetInterpolatedHeight (normalizedX, normalizedZ); + pos.x = normalizedX * size.x; + pos.z = normalizedZ * size.z; + + float noise = PerlinNoise::NoiseNormalized(pos.x * noiseSpread, pos.z * noiseSpread); + ColorRGBA32 healthyDryColor = Lerp (dry, healthy, noise); + healthyDryColor = GfxDevice::ConvertToDeviceVertexColor (healthyDryColor); + + // set second UVs to point to the fragment of the terrain lightmap underneath the detail mesh + CopyUVFromTerrain (normalizedX, normalizedZ, uvs2, vertexCount, prototypeVerticesSize); + + if (renderMode == kDetailBillboard) + { + DebugAssertIf (prototypeVerticesSize != 4); + DebugAssertIf (prototypeTrisSize != 6); + + float grassX = halfGrassWidth + halfGrassWidthDelta * noise; + float grassY = grassHeight + grassHeightDelta * noise; + + detailMaxHalfWidth = std::max(detailMaxHalfWidth, grassX); + detailMaxHeight = std::max(detailMaxHeight, grassY); + + Vector2f billboardSize[] = + { + Vector2f (-grassX, 0), + Vector2f (-grassX, grassY), + Vector2f (grassX, grassY), + Vector2f (grassX, 0) + }; + + CopyVertex (pos, vertices, vertexCount, prototypeVerticesSize); + CopyUV (prototypeUvs, uvs, vertexCount, prototypeVerticesSize); + + // used for offsetting vertices in the vertex shader + CopyTangents (billboardSize, tangents, vertexCount, prototypeVerticesSize); + + CopyColor (prototypeColors, colors, healthyDryColor, vertexCount, prototypeVerticesSize); + + CopyNormalFromTerrain(*heightmap, normalizedX, normalizedZ, normals, vertexCount, prototypeVerticesSize); + + for (int t=0;t<prototypeTrisSize;t++) + triangles[t+triangleCount] = prototypeTris[t] + vertexCount; + + triangleCount += prototypeTrisSize; + vertexCount += prototypeVerticesSize; + } + else + { + Quaternionf rot = AxisAngleToQuaternion (Vector3f (0,1,0), m_Random.GetFloat() * 6.2831852f); + float scaleX = Lerp (prototype.minWidth, prototype.maxWidth, noise); + float scaleZ = Lerp (prototype.minHeight, prototype.maxHeight, noise); + Vector3f scale = Vector3f (scaleX, scaleZ, scaleX); + Matrix4x4f transform; + transform.SetTRS (pos, rot, scale); + + CopyVertex (prototypeVertices, vertices, transform, vertexCount, prototypeVerticesSize); + CopyUV (prototypeUvs, uvs, vertexCount, prototypeVerticesSize); + CopyColor (prototypeColors, colors, healthyDryColor, vertexCount, prototypeVerticesSize); + + if (renderMode == kDetailMeshGrass) + { + CopyNormalFromTerrain(*heightmap, normalizedX, normalizedZ, normals, vertexCount, prototypeVerticesSize); + } + else if (normals != NULL) + { + CopyNormal (prototypeNormals, normals, rot, vertexCount, prototypeVerticesSize); + } + + for (int t=0;t<prototypeTrisSize;t++) + triangles[t+triangleCount] = prototypeTris[t] + vertexCount; + + triangleCount += prototypeTrisSize; + vertexCount += prototypeVerticesSize; + } + } + } + } + } + PROFILER_END_INTERNAL; + + PROFILER_BEGIN_INTERNAL(gAssignToMesh, NULL); + + AssertIf(triangleCount != totalTriangleCount); + AssertIf(vertexCount != totalVertexCount); + + // Assign the mesh + mesh.Clear(true); + unsigned meshFormat = VERTEX_FORMAT5 (Vertex, Normal, Color, TexCoord0, TexCoord1); + if (renderMode == kDetailBillboard) + meshFormat |= VERTEX_FORMAT1 (Tangent); + + mesh.ResizeVertices (vertexCount, meshFormat); + strided_copy (vertices, vertices + vertexCount, mesh.GetVertexBegin ()); + strided_copy (colors, colors + vertexCount, mesh.GetColorBegin ()); + strided_copy (normals, normals + vertexCount, mesh.GetNormalBegin ()); + strided_copy (uvs, uvs + vertexCount, mesh.GetUvBegin (0)); + strided_copy (uvs2, uvs2 + vertexCount, mesh.GetUvBegin (1)); + if (renderMode == kDetailBillboard) + strided_copy (tangents, tangents + vertexCount, mesh.GetTangentBegin ()); + + mesh.SetIndicesComplex (triangles, triangleCount, 0, kPrimitiveTriangles, Mesh::k16BitIndices); + mesh.SetChannelsDirty (mesh.GetAvailableChannels (), true); + mesh.RecalculateBounds (); + + if (renderMode == kDetailBillboard) + ExpandDetailBillboardBounds(mesh, detailMaxHalfWidth, detailMaxHeight); + + PROFILER_END_INTERNAL; +} + +void DetailDatabase::ComputeVertexAndTriangleCount(DetailPatch &patch, DetailRenderMode renderMode, float density, int* vertexCount, int* triangleCount) +{ + *triangleCount = 0; + *vertexCount = 0; + int res = m_PatchSamples; + + for (int i=0;i<patch.layerIndices.size();i++) + { + DetailPrototype &prototype = m_DetailPrototypes[patch.layerIndices[i]]; + if (prototype.renderMode != renderMode) + continue; + + if (prototype.vertices.empty()) + continue; + + int count = 0; + for (int y=0;y<res;y++) + { + for (int x=0;x<res;x++) + { + int nbIndex = y * res + x + i * res * res; + int origCount = patch.numberOfObjects[nbIndex]; + if (!origCount) + continue; + int newCount = (int)(origCount * density + (kDitherTable[(x&7)*8+(y&7)] - 0.5f) / 64.0f); + count += newCount; + } + } + + + + // Clamp the number of genrated details to not generate more than kClampedVertex vertices + int maxCount = (kClampedVertexCount - *vertexCount) / prototype.vertices.size(); + count = std::min(maxCount, count); + + *triangleCount += prototype.triangles.size() * count; + *vertexCount += prototype.vertices.size() * count; + } +} + + +void DetailDatabase::UpdateDetailPrototypesIfDirty () +{ + if (m_IsPrototypesDirty) + RefreshPrototypes(); +} + +#if UNITY_EDITOR +/* +AssetDatabase::RegisterPostprocessCallback (Postprocess); +static void Postprocess (const std::set<UnityGUID>& refreshed, const std::set<UnityGUID>& removed, const std::set<UnityGUID>& moved); +{ + for (std::set<UnityGUID>::iterator i=refresh.begin();i != refreshed.end();i++) + { + PPtr<Texture> tex = dynamic_pptr_cast<Texture*> (GetMainAsset()); + if (tex) + { + vector<TerrainData*> data; + FindObjectsOfType(data); + data.GetDetailDatabase().RefreshPrototypes(); + } + } + +} +*/ +#endif + + + +void DetailPrototypeToMono (const DetailPrototype &src, MonoDetailPrototype &dest) { + dest.prototype = Scripting::ScriptingWrapperFor (src.prototype); + dest.prototypeTexture = Scripting::ScriptingWrapperFor (src.prototypeTexture); + dest.healthyColor = src.healthyColor; + dest.dryColor = src.dryColor; + dest.minWidth = src.minWidth; + dest.maxWidth = src.maxWidth; + dest.minHeight = src.minHeight; + dest.maxHeight = src.maxHeight; + dest.noiseSpread = src.noiseSpread; + dest.bendFactor = src.bendFactor; + dest.renderMode = src.renderMode; + dest.usePrototypeMesh = src.usePrototypeMesh; + +} +void DetailPrototypeToCpp (MonoDetailPrototype &src, DetailPrototype &dest) { + dest.prototype = ScriptingObjectToObject<GameObject> (src.prototype); + dest.prototypeTexture = ScriptingObjectToObject<Texture2D> (src.prototypeTexture); + dest.healthyColor = src.healthyColor; + dest.dryColor = src.dryColor; + dest.minWidth = src.minWidth; + dest.maxWidth = src.maxWidth; + dest.minHeight = src.minHeight; + dest.maxHeight = src.maxHeight; + dest.noiseSpread = src.noiseSpread; + dest.bendFactor = src.bendFactor; + dest.renderMode = src.renderMode; + dest.usePrototypeMesh = src.usePrototypeMesh; +} + + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/DetailDatabase.h b/Runtime/Terrain/DetailDatabase.h new file mode 100644 index 0000000..52cfc7e --- /dev/null +++ b/Runtime/Terrain/DetailDatabase.h @@ -0,0 +1,270 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Geometry/AABB.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Graphics/Texture2D.h" +#include "Runtime/Math/Random/rand.h" +#include "Runtime/Math/Rect.h" +#include <vector> +#include "TreeDatabase.h" + +class TerrainData; +class Heightmap; +class Mesh; + +using std::vector; + +struct DetailPatch +{ + public: + DECLARE_SERIALIZE (DetailPatch) + AABB bounds; + + bool dirty; + + vector<UInt8> layerIndices; + vector<UInt8> numberOfObjects; + + DetailPatch () { dirty = false; } +}; + +template<class TransferFunc> +void DetailPatch::Transfer (TransferFunc& transfer) +{ + TRANSFER (bounds); + TRANSFER (layerIndices); + TRANSFER (numberOfObjects); +} + +enum DetailRenderMode +{ + kDetailBillboard = 0, // billboard + kDetailMeshLit, // just a mesh, lit like everything else + kDetailMeshGrass, // mesh (user supplied or generated grass crosses), waves in the wind + kDetailRenderModeCount +}; + +struct DetailPrototype +{ + DECLARE_SERIALIZE (DetailPrototype) + PPtr<GameObject> prototype; + PPtr<Texture2D> prototypeTexture; + + float minWidth, maxWidth; ///< Width of the grass billboards (if renderMode is grassBillboard) + float minHeight, maxHeight; ///< Height of the grass billboards (if renderMode is grassBillboard) + float noiseSpread; + float bendFactor; + ColorRGBAf healthyColor; + ColorRGBAf dryColor; + float lightmapFactor; + int renderMode; + int usePrototypeMesh; + + vector<Vector3f> vertices; + vector<Vector3f> normals; + vector<Vector2f> uvs; + vector<ColorRGBA32> colors; + vector<UInt16> triangles; + + DetailPrototype () : + healthyColor (67/255.0F, 249/255.0F, 42/255.0F, 1 ), + dryColor(205/255.0F, 188/255.0F, 26/255.0F, 1.0F ) + { + minWidth = 1.0F; + maxWidth = 2.0F; + minHeight = 1.0F; + maxHeight = 2.0F; + noiseSpread = 10.0F; + bendFactor = 1.0F; + lightmapFactor = 1.0F; + renderMode = kDetailMeshGrass; + usePrototypeMesh = false; + } +}; + +template<class TransferFunc> +void DetailPrototype::Transfer (TransferFunc& transfer) +{ + transfer.SetVersion(2); + + TRANSFER (prototype); + TRANSFER (prototypeTexture); + TRANSFER (minWidth); + TRANSFER (maxWidth); + TRANSFER (minHeight); + TRANSFER (maxHeight); + TRANSFER (noiseSpread); + TRANSFER (bendFactor); + TRANSFER (healthyColor); + TRANSFER (dryColor); + TRANSFER (lightmapFactor); + TRANSFER (renderMode); + TRANSFER (usePrototypeMesh); + + if (transfer.IsOldVersion(1)) + { + if (prototype) + usePrototypeMesh = 1; + else + usePrototypeMesh = 0; + } +} + + +class DetailDatabase +{ + public: + DetailDatabase (TerrainData* terrainData, TreeDatabase* treeDatabase); + ~DetailDatabase (); + + DECLARE_SERIALIZE (DetailDatabase) + + Texture2D* GetAtlasTexture () { return m_AtlasTexture; } + GET_SET (ColorRGBAf, WavingGrassTint, m_WavingGrassTint); + GET_SET (float, WavingGrassStrength, m_WavingGrassStrength); + GET_SET (float, WavingGrassAmount, m_WavingGrassAmount); + GET_SET (float, WavingGrassSpeed, m_WavingGrassSpeed); + + int GetPatchCount () const { return m_PatchCount; } + + const vector<DetailPrototype> &GetDetailPrototypes () const { return m_DetailPrototypes; } + void SetDetailPrototypes (const vector<DetailPrototype> &treePrototypes ); + void RemoveDetailPrototype (int index); + + void SetDetailResolution (int resolution, int resolutionPerPatch); + + void ResetDirtyDetails (); + void SetDetailPrototypesDirty (); + void UpdateDetailPrototypesIfDirty (); + void GenerateTextureAtlasThreaded (); + + void SetDirty (); + + int GetWidth () const { return GetResolution (); } + int GetHeight () const { return GetResolution (); } + int GetResolution () const { return m_PatchSamples * m_PatchCount; } + int GetResolutionPerPatch () const { return m_PatchSamples; } + + int GetSupportedLayers (int xBase, int yBase, int totalWidth, int totalHeight, int *buffer) const; + void GetLayer (int xBase, int yBase, int totalWidth, int totalHeight, int detailIndex, int *buffer) const; + void SetLayer (int xBase, int yBase, int totalWidth, int totalHeight, int detailIndex, const int *buffer); + + int GetIndex (int x, int y, int l) const + { + int res = m_PatchSamples; + int nbIndex = y * res + x + l * res * res; + return nbIndex; + } + + void RefreshPrototypes (); + + Rectf GetNormalizedArea (int x, int y) const + { + float fx = (float)x / m_PatchCount; + float fy = (float)y / m_PatchCount; + float size = 1.0F / m_PatchCount; + return Rectf (fx, fy, size, size); + } + +// void GenerateBounds (DetailPatch &patch, int patchX, int patchY); + + Mesh* BuildMesh (int patchX, int patchY, Vector3f size, int lightmapIndex, DetailRenderMode renderMode, float density); + bool IsPatchEmpty (int x, int y) const; + bool IsPatchDirty (int x, int y) const; + + + +private: + void GenerateMesh (Mesh& mesh, int patchX, int patchY, Vector3f size, int lightmapIndex, DetailRenderMode renderMode, float density, int totalVertexCount, int totalTriangleCount); + + #if UNITY_EDITOR + void SetupPreloadTextureAtlasData (); + #endif + void RefreshPrototypesStep1 (Texture2D** sourceTextures); + + void CleanupPrototype (DetailPrototype &proto, string const& error); + void ComputeVertexAndTriangleCount(DetailPatch &patch, DetailRenderMode renderMode, float density, int* vertexCount, int* triangleCount); + static int GetActualResolution (int resolution, int heightmapResolution); + int AddLayerIndex (int detailIndex, DetailPatch &patch); + void RemoveLocalLayerIndex (int detailIndex, DetailPatch& patch); + + const DetailPatch& GetPatch (int x, int y) const { return m_Patches[y * m_PatchCount + x]; } + DetailPatch& GetPatch (int x, int y) { return m_Patches[y * m_PatchCount + x]; } + + bool m_IsPrototypesDirty; + vector<DetailPatch> m_Patches; + vector<DetailPrototype> m_DetailPrototypes; + TerrainData* m_TerrainData; + TreeDatabase* m_TreeDatabase; + int m_PatchCount; + int m_PatchSamples; + vector<Vector3f> m_RandomRotations; + Texture2D* m_AtlasTexture; + + ColorRGBAf m_WavingGrassTint; + float m_WavingGrassStrength; + float m_WavingGrassAmount; + float m_WavingGrassSpeed; + + Rand m_Random; + + vector<PPtr<Texture2D> > m_PreloadTextureAtlasData; + vector<Rectf> m_PreloadTextureAtlasUVLayout; +}; + + +template<class TransferFunc> +void DetailDatabase::Transfer (TransferFunc& transfer) +{ + TRANSFER (m_Patches); + TRANSFER (m_DetailPrototypes); + TRANSFER (m_PatchCount); + TRANSFER (m_PatchSamples); + TRANSFER (m_RandomRotations); + transfer.Transfer( m_WavingGrassTint, "WavingGrassTint" ); + TRANSFER (m_WavingGrassStrength); + TRANSFER (m_WavingGrassAmount); + TRANSFER (m_WavingGrassSpeed); + transfer.Transfer (m_TreeDatabase->GetInstances(), "m_TreeInstances"); + transfer.Transfer (m_TreeDatabase->GetTreePrototypes(), "m_TreePrototypes"); + + #if UNITY_EDITOR + if (transfer.IsBuildingTargetPlatform(kBuildAnyPlayerData)) + { + SetupPreloadTextureAtlasData(); + TRANSFER (m_PreloadTextureAtlasData); + m_PreloadTextureAtlasData.clear(); + } + else + #endif + { + TRANSFER (m_PreloadTextureAtlasData); + } +} + + +struct MonoDetailPrototype { + ScriptingObjectPtr prototype; + ScriptingObjectPtr prototypeTexture; + + ColorRGBAf healthyColor; + ColorRGBAf dryColor; + + float minWidth, maxWidth; + float minHeight, maxHeight; + float noiseSpread; + float bendFactor; + int renderMode; + int usePrototypeMesh; +}; + + +void DetailPrototypeToMono (const DetailPrototype &src, MonoDetailPrototype &dest); +void DetailPrototypeToCpp (MonoDetailPrototype &src, DetailPrototype &dest) ; + + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/DetailRenderer.cpp b/Runtime/Terrain/DetailRenderer.cpp new file mode 100644 index 0000000..b591fdc --- /dev/null +++ b/Runtime/Terrain/DetailRenderer.cpp @@ -0,0 +1,303 @@ +#include "UnityPrefix.h" +#include "DetailRenderer.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Input/TimeManager.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Geometry/Plane.h" +#include "Runtime/Camera/CameraUtil.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Geometry/Intersection.h" +#include "Runtime/Shaders/MaterialProperties.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/Shaders/ShaderNameRegistry.h" +#include "Runtime/Camera/IntermediateRenderer.h" + +namespace DetailRenderer_Static +{ +static SHADERPROP(Cutoff); +static SHADERPROP(MainTex); +static SHADERPROP(maxDistanceSqr); +static SHADERPROP(CameraPosition); +static SHADERPROP(WaveAndDistance); +static SHADERPROP(WavingTint); +static SHADERPROP(CameraRight); +static SHADERPROP(CameraUp); +} // namespace SplatMaterials_Static + +DetailRenderer::DetailRenderer (PPtr<TerrainData> terrain, Vector3f position, int lightmapIndex) +{ + using namespace DetailRenderer_Static; + + m_Database = terrain; + m_Position = position; + m_LightmapIndex = lightmapIndex; + + const char* shaders[] = { + "Hidden/TerrainEngine/Details/BillboardWavingDoublePass", + "Hidden/TerrainEngine/Details/Vertexlit", + "Hidden/TerrainEngine/Details/WavingDoublePass" + }; + + ScriptMapper& sm = GetScriptMapper (); + bool shaderNotFound = false; + for (int i = 0; i < kDetailRenderModeCount; i++) + { + Shader* shader = sm.FindShader(shaders[i]); + if (shader == NULL) + { + shaderNotFound = true; + shader = sm.FindShader("Diffuse"); + } + + m_Materials[i] = Material::CreateMaterial (*shader, Object::kHideAndDontSave); + m_Materials[i]->SetFloat (kSLPropCutoff, .5f * .75f); + } + + if (shaderNotFound) + { + ErrorString("Unable to find shaders used for the terrain engine. Please include Nature/Terrain/Diffuse shader in Graphics settings."); + } + + m_RenderCount = 0; + m_LastTime = 0; +} + +DetailPatchRender& DetailRenderer::GrabCachedPatch (int x, int y, int lightmapIndex, DetailRenderMode mode, float density) +{ + DetailList &patches = m_Patches[mode]; + UInt32 index = x + y*m_Database->GetDetailDatabase().GetPatchCount(); + + DetailPatchRender& render = patches[index]; + if(!render.inited) + { + render.mesh = m_Database->GetDetailDatabase().BuildMesh(x, y, m_TerrainSize, lightmapIndex, mode, density); + render.isMeshNull = render.mesh == NULL; + render.x = x; + render.y = y; + render.inited = true; + } + render.lastUsed = m_RenderCount; + return render; +} + +void DetailRenderer::Render (Camera* camera, float viewDistance, int layer, float detailDensity) +{ + using namespace DetailRenderer_Static; + + detailDensity = clamp01(detailDensity); + + DetailDatabase& detail = m_Database->GetDetailDatabase(); + m_LastTime += (IsWorldPlaying() ? GetDeltaTime() * detail.GetWavingGrassStrength() * .05f : 0); + m_RenderCount++; + + + m_TerrainSize = m_Database->GetHeightmap().GetSize(); + int patchCount = detail.GetPatchCount(); + if (patchCount == 0) + return; + + detail.UpdateDetailPrototypesIfDirty(); + + for (int i=0;i<kDetailRenderModeCount;i++) + { + if (m_Materials[i]->HasProperty(kSLPropMainTex)) + m_Materials[i]->SetTexture (kSLPropMainTex, detail.GetAtlasTexture()); + } + + Transform* camT = camera->QueryComponent (Transform); + + Vector3f position = camT->GetPosition() - m_Position; + int centerX = RoundfToInt(position.x * patchCount / m_TerrainSize.x); + int centerY = RoundfToInt(position.z * patchCount / m_TerrainSize.z); + + int halfWidth = int(Ceilf(patchCount * viewDistance / m_TerrainSize.x) + 1); + int halfHeight = int(Ceilf(patchCount * viewDistance / m_TerrainSize.z) + 1); + + int minx = centerX - halfWidth; + if(minx < 0) minx = 0; + if(minx > patchCount - 1) minx = patchCount - 1; + + int miny = centerY - halfHeight; + if(miny < 0) miny = 0; + if(miny > patchCount - 1) miny = patchCount - 1; + + int maxx = centerX + halfWidth; + if(maxx < 0) maxx = 0; + if(maxx > patchCount - 1) maxx = patchCount - 1; + + int maxy = centerY + halfHeight; + if(maxy < 0) maxy = 0; + if(maxy > patchCount - 1) maxy = patchCount - 1; + + float sqrViewDistance = viewDistance * viewDistance; + + Plane planes[6]; + ExtractProjectionPlanes( camera->GetWorldToClipMatrix(), planes); + +// DetailList *newPatches = new DetailList[3]; + int totalVisible = 0; + int total = 0; + + bool supportsBillboards = m_Materials[0]->GetShader()->IsSupported(); + + // Find and cull all visible patches + for (int y=miny;y<=maxy;y++) + { + for (int x=minx;x<=maxx;x++) + { + if (detail.IsPatchEmpty (x,y)) + continue; + + for (int i=0;i<kDetailRenderModeCount;i++) + { + // Skip billboard rendering if not supported + if( i == 0 && !supportsBillboards ) + continue; + + // Grab the cached patch + DetailPatchRender &render = GrabCachedPatch (x, y, m_LightmapIndex, (DetailRenderMode)i, detailDensity); + + if (render.isMeshNull) + { + render.isCulledVisible = false; + } + else + { + AABB bounds = render.mesh->GetBounds(); + + render.isCulledVisible = true; + if (CalculateSqrDistance(position,bounds) > sqrViewDistance) + { + render.isCulledVisible = false; + } + else + { + bounds.GetCenter() += m_Position; + if (!IntersectAABBFrustumFull(bounds, planes)) + render.isCulledVisible = false; + } + + if(render.isCulledVisible) + totalVisible ++; + } + total++; + } + } + } + + Vector3f up = camT->InverseTransformDirection (Vector3f(0.0f,1.0f,0.0f)); + up.z = 0; + up = camT->TransformDirection(up); + up = NormalizeSafe(up); + + MaterialPropertyBlock props; + + Vector3f right = Cross (camT->TransformDirection(Vector3f(0.0f,0.0f,-1.0f)), up); + right = NormalizeSafe(right); + + Matrix4x4f matrix; + matrix.SetTranslate( m_Position ); + + for (int r=0;r<kDetailRenderModeCount;r++) + { + Material *material = m_Materials[r]; + + props.Clear(); + props.AddPropertyFloat(kSLPropmaxDistanceSqr, sqrViewDistance); + Vector4f prop; + prop[0] = position.x; + prop[1] = position.y; + prop[2] = position.z; + prop[3] = 1.0f / sqrViewDistance; + props.AddPropertyVector(kSLPropCameraPosition, prop); + prop[0] = m_LastTime; + prop[1] = detail.GetWavingGrassSpeed() * .4f; + // cancel wind on mesh lit + prop[2] = r == kDetailMeshLit ? 0 : detail.GetWavingGrassAmount() * 6.0f; + prop[3] = sqrViewDistance; + props.AddPropertyVector(kSLPropWaveAndDistance, prop); + ColorRGBAf color = detail.GetWavingGrassTint(); + props.AddPropertyColor(kSLPropWavingTint, color); + prop[0] = right.x; + prop[1] = right.y; + prop[2] = right.z; + prop[3] = 0.0f; + props.AddPropertyVector(kSLPropCameraRight, prop); + prop[0] = up.x; + prop[1] = up.y; + prop[2] = up.z; + prop[3] = 0.0f; + props.AddPropertyVector(kSLPropCameraUp, prop); + + + DetailList &curPatches = m_Patches[r]; + for (DetailList::iterator i = curPatches.begin(); i != curPatches.end(); i++) + { + if (i->second.isCulledVisible) + { + IntermediateRenderer* r = AddMeshIntermediateRenderer( matrix, i->second.mesh, material, layer, false, true, 0, camera ); + r->SetPropertyBlock( props ); + r->SetLightmapIndexIntNoDirty(m_LightmapIndex); + } + } + } + + for (int r=0;r<kDetailRenderModeCount;r++) + { + DetailList &curPatches = m_Patches[r]; + DetailList::iterator next; + for (DetailList::iterator render = curPatches.begin(); render != curPatches.end(); render=next) + { + next = render; + next++; + if(render->second.lastUsed < m_RenderCount) + curPatches.erase(render); + } + } +} + +/// Cleanup all the cached render patches +void DetailRenderer::Cleanup () +{ + for(int i=0;i<kDetailRenderModeCount;i++) + { + DestroySingleObject (m_Materials[i]); + DetailList &curPatches = m_Patches[i]; + + for (DetailList::iterator render = curPatches.begin(); render != curPatches.end(); ++render) + { + render->second.inited = false; + DestroySingleObject(render->second.mesh); + render->second.mesh = NULL; + } + } +} + +void DetailRenderer::ReloadAllDetails () +{ + for (int i=0;i<kDetailRenderModeCount;i++) + { + m_Patches[i].clear(); + } +} + +void DetailRenderer::ReloadDirtyDetails () +{ + DetailDatabase& detail = m_Database->GetDetailDatabase(); + for (int i=0;i<kDetailRenderModeCount;i++) + { + DetailList &curPatches = m_Patches[i]; + DetailList::iterator next; + for (DetailList::iterator render = curPatches.begin(); render != curPatches.end(); render=next) + { + next = render; + next++; + if (detail.IsPatchDirty (render->second.x, render->second.y)) + curPatches.erase(render); + } + } +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/DetailRenderer.h b/Runtime/Terrain/DetailRenderer.h new file mode 100644 index 0000000..2e11fe2 --- /dev/null +++ b/Runtime/Terrain/DetailRenderer.h @@ -0,0 +1,56 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "TerrainData.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Utilities/MemoryPool.h" + +class DetailPatchRender +{ +public: + Mesh *mesh; + bool isCulledVisible; + bool isMeshNull; + bool inited; + int lastUsed; + int x; + int y; + + DetailPatchRender() { inited = false; mesh = NULL; } + ~DetailPatchRender() { DestroySingleObject (mesh); } +}; + +class DetailRenderer +{ +public: + Material *m_Materials[kDetailRenderModeCount]; + + DetailRenderer (PPtr<TerrainData> terrain, Vector3f position, int lightmapIndex); + void Render (Camera *camera, float viewDistance, int layer, float detailDensity); + void Cleanup (); + void ReloadAllDetails(); + void ReloadDirtyDetails(); + + int GetLightmapIndex() { return m_LightmapIndex; } + void SetLightmapIndex(int value) { m_LightmapIndex = value; } + +private: + typedef std::map<UInt32,DetailPatchRender, std::less<UInt32> ,memory_pool<std::pair<const UInt32, DetailPatchRender> > > DetailList; + + PPtr<TerrainData> m_Database; + Vector3f m_TerrainSize; + UInt8 m_LightmapIndex; + + DetailList m_Patches[kDetailRenderModeCount]; + Vector3f m_Position; + int m_RenderCount; + float m_LastTime; + + DetailPatchRender& GrabCachedPatch (int x, int y, int lightmapIndex, DetailRenderMode mode, float density); +}; + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/Heightmap.cpp b/Runtime/Terrain/Heightmap.cpp new file mode 100644 index 0000000..cf3bf15 --- /dev/null +++ b/Runtime/Terrain/Heightmap.cpp @@ -0,0 +1,892 @@ +#include "UnityPrefix.h" +#include "Heightmap.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Terrain/TerrainRenderer.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Utilities/Utility.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxHeightField.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxHeightFieldDesc.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxHeightFieldShape.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxHeightFieldShapeDesc.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxHeightFieldSample.h" +#include "Runtime/Dynamics/PhysicsManager.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxScene.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxActor.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxActorDesc.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxCapsuleShape.h" +#include "External/PhysX/builds/SDKs/Physics/include/NxCapsuleShapeDesc.h" +#include "Runtime/Terrain/DetailDatabase.h" +#include "Runtime/Dynamics/NxWrapperUtility.h" +#include "math.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Terrain/TerrainData.h" +#include "Runtime/BaseClasses/SupportedMessageOptimization.h" +#include "Runtime/Utilities/BitUtility.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Terrain/TerrainIndexGenerator.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Dynamics/TerrainCollider.h" +#include "Runtime/Interfaces/IPhysics.h" + +enum { kMaxHeight = 32766 }; + +using namespace std; + +inline UInt32 GetLowMaterialIndex (UInt32 index) +{ + return index & 0x7F; +} + +inline UInt32 GetHighMaterialIndex (UInt32 index) +{ + return index >> 7; +} + +static void UpdatePatchMeshInternal ( + const Heightmap& heightmap, + const StrideIterator<Vector3f>& vertices, + const StrideIterator<Vector3f>& normals, + const StrideIterator<Vector2f>& uvs, + int xPatch, int yPatch, int mipLevel, int edgeMask, TerrainRenderer *renderer) +{ + Vector3f hmScale = heightmap.GetScale(); + + float skipScale = 1 << mipLevel; + float scale = hmScale.y / (float)(kMaxHeight); + + Vector2f uvscale; + uvscale.x = 1.0F / (heightmap.GetWidth() - 1) * skipScale; + uvscale.y = 1.0F / (heightmap.GetHeight() - 1) * skipScale; + + Vector2f uvoffset; + uvoffset.x = xPatch * (1 << mipLevel) * (kPatchSize -1); + uvoffset.x /= (heightmap.GetWidth() - 1); + uvoffset.y = yPatch * (1 << mipLevel) * (kPatchSize -1); + uvoffset.y /= (heightmap.GetHeight() - 1); + + int skip = 1 << mipLevel; + int xBase = xPatch * (kPatchSize -1); + int yBase = yPatch * (kPatchSize -1); + + StrideIterator<Vector3f> itVertices = vertices; + StrideIterator<Vector3f> itNormals = normals; + StrideIterator<Vector2f> itUVs = uvs; + + for (int x=0;x<kPatchSize;x++) + { + for (int y=0;y<kPatchSize;y++) + { + int sampleIndex = (y + yBase) + (x + xBase) * heightmap.GetHeight(); + sampleIndex *= skip; + float height = heightmap.GetRawHeight(sampleIndex); + height *= scale; + + int index = y + x * kPatchSize; + + // Vertex + itVertices[index].x = (x + xBase) * hmScale.x * skipScale; + itVertices[index].y = height; + itVertices[index].z = (y + yBase) * hmScale.z * skipScale; + + // UV + itUVs[index].x = (float)x * uvscale.x + uvoffset.x; + itUVs[index].y = (float)y * uvscale.y + uvoffset.y; + + // Normal + Vector3f normal = heightmap.CalculateNormalSobelRespectingNeighbors ((x + xBase) * skip, (y + yBase) * skip, renderer); + itNormals[index] = normal; + } + } +} + +Heightmap::Heightmap (TerrainData *owner) +: m_Scale(1.0f,1.0f,1.0f) +{ + m_TerrainData = owner; +#if ENABLE_PHYSICS + m_NxHeightField = NULL; +#endif +} + +Heightmap::~Heightmap () +{ +#if ENABLE_PHYSICS + CleanupNx (); +#endif +} + +/// Calculates the index of the patch given it's level and x, y index +int Heightmap::GetPatchIndex (int x, int y, int level) const +{ + int index = 0; + for (int i=0;i<level;i++) + { + int size = 1 << (m_Levels - i); + index += size * size; + } + + int width = 1 << (m_Levels - level); + index += width * y; + index += x; + return index; +} + +float Heightmap::InterpolatePatchHeight (float* data, float fx, float fy) const +{ + int lx = (int)(fx * kPatchSize); + int ly = (int)(fy * kPatchSize); + + int hx = lx + 1; + if (hx >= kPatchSize) + hx = kPatchSize - 1; + int hy = ly + 1; + if (hy >= kPatchSize) + hy = kPatchSize - 1; + + float s00 = GetPatchHeight (data, lx, ly); + float s01 = GetPatchHeight (data, lx, hy); + float s10 = GetPatchHeight (data, hx, ly); + float s11 = GetPatchHeight (data, hx, hy); + + float dx = fx * kPatchSize - lx; + float dy = fy * kPatchSize - ly; + + float x = Lerp(s00, s10, dx); + float y = Lerp(s01, s11, dx); + float value = Lerp(x, y, dy); + + return value; +} + + +Vector3f Heightmap::CalculateNormalSobelRespectingNeighbors (int x, int y, const TerrainRenderer *renderer) const +{ + Vector3f normal; + float dY, dX; + // Do X sobel filter + dX = GetHeightRespectingNeighbors (x-1, y-1, renderer) * -1.0F; + dX += GetHeightRespectingNeighbors (x-1, y , renderer) * -2.0F; + dX += GetHeightRespectingNeighbors (x-1, y+1, renderer) * -1.0F; + dX += GetHeightRespectingNeighbors (x+1, y-1, renderer) * 1.0F; + dX += GetHeightRespectingNeighbors (x+1, y , renderer) * 2.0F; + dX += GetHeightRespectingNeighbors (x+1, y+1, renderer) * 1.0F; + + dX /= m_Scale.x; + + // Do Y sobel filter + dY = GetHeightRespectingNeighbors (x-1, y-1, renderer) * -1.0F; + dY += GetHeightRespectingNeighbors (x , y-1, renderer) * -2.0F; + dY += GetHeightRespectingNeighbors (x+1, y-1, renderer) * -1.0F; + dY += GetHeightRespectingNeighbors (x-1, y+1, renderer) * 1.0F; + dY += GetHeightRespectingNeighbors (x , y+1, renderer) * 2.0F; + dY += GetHeightRespectingNeighbors (x+1, y+1, renderer) * 1.0F; + dY /= m_Scale.z; + + // Cross Product of components of gradient reduces to + normal.x = -dX; + if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_3_a1)) + normal.y = 8; + else + // 5 here is just wrong! + normal.y = 5; + normal.z = -dY; + normal = NormalizeFast (normal); + + return normal; +} + +Vector3f Heightmap::CalculateNormalSobel (int x, int y) const +{ + Vector3f normal; + float dY, dX; + // Do X sobel filter + dX = GetHeight (x-1, y-1) * -1.0F; + dX += GetHeight (x-1, y ) * -2.0F; + dX += GetHeight (x-1, y+1) * -1.0F; + dX += GetHeight (x+1, y-1) * 1.0F; + dX += GetHeight (x+1, y ) * 2.0F; + dX += GetHeight (x+1, y+1) * 1.0F; + + dX /= m_Scale.x; + + // Do Y sobel filter + dY = GetHeight (x-1, y-1) * -1.0F; + dY += GetHeight (x , y-1) * -2.0F; + dY += GetHeight (x+1, y-1) * -1.0F; + dY += GetHeight (x-1, y+1) * 1.0F; + dY += GetHeight (x , y+1) * 2.0F; + dY += GetHeight (x+1, y+1) * 1.0F; + dY /= m_Scale.z; + + // Cross Product of components of gradient reduces to + normal.x = -dX; + if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_3_a1)) + normal.y = 8; + else + // 5 here is just wrong! + normal.y = 5; + normal.z = -dY; + normal = NormalizeFast (normal); + + return normal; +} + +float Heightmap::ComputeMaximumHeightError (int xPatch, int yPatch, int level) const +{ + // Lod zero never has error + if (level == 0) + return 0.0F; + + float* data = new float[kPatchSize * kPatchSize]; + GetPatchData (xPatch, yPatch, level, data); + float deltaMax = 0.0F; + + int skip = 1 << level; + int xBase = xPatch * (kPatchSize - 1) * skip; + int yBase = yPatch * (kPatchSize - 1) * skip; + + int size = (kPatchSize - 1) * skip + 1; + float normalizeScale = 1.0F / (kPatchSize * skip); + + for (int x=0;x<size;x++) + { + for (int y=0;y<size;y++) + { + float fx = (float)x * normalizeScale; + float fy = (float)y * normalizeScale; + float interpolatedHeight = InterpolatePatchHeight (data, fx, fy); + float realHeight = GetHeight (x + xBase, y + yBase); + float delta = Abs(realHeight - interpolatedHeight); + deltaMax = max(deltaMax, delta); + } + } + + delete[] data; + return deltaMax; +} + + +Vector3f Heightmap::GetSize () const +{ + return Vector3f (m_Scale.x * (m_Width - 1), m_Scale.y, m_Scale.z * (m_Height - 1)); +} + +void Heightmap::SetSize (const Vector3f& size) +{ + m_Scale.x = size.x / (m_Width - 1); + m_Scale.y = size.y; + m_Scale.z = size.z / (m_Height - 1); + + PrecomputeError(0, 0, m_Width, m_Height, false); + +#if ENABLE_PHYSICS + UpdateNx (); + RecreateShapes(); +#endif + + m_TerrainData->SetDirty(); + m_TerrainData->UpdateUsers (TerrainData::kHeightmap); +} + +void Heightmap::AwakeFromLoad () +{ +#if ENABLE_PHYSICS + CreateNx (); + RecreateShapes (); +#endif +} + +#if ENABLE_PHYSICS +void Heightmap::RecreateShapes () +{ + for (TerrainColliderList::iterator i=m_TerrainColliders.begin();i != m_TerrainColliders.end();) + { + TerrainCollider& col = **i; + i++; + col.Create(NULL); + } +} +#endif + +// Precompute error only on a part of the heightmap +// if forceHighestLod is enabled we simply set the error to infinity +// This casues the heightmap to be rendered at full res (Used while editing) +void Heightmap::PrecomputeError (int minX, int minY, int width, int height, bool forceHighestLod) +{ + for (int level=0;level <= m_Levels;level++) + { + for (int y=0;y<GetPatchCountY(level);y++) + { + for (int x=0;x<GetPatchCountX(level);x++) + { + int skip = 1 << level; + int curXBase = x * (kPatchSize - 1) * skip; + int curYBase = y * (kPatchSize - 1) * skip; + int curPatchSize = kPatchSize * skip; + + // Are we in the bounds horizontally + if (curXBase + curPatchSize < minX) + continue; + if (curXBase > minX + width) + continue; + + // Are we in the bounds vertically + if (curYBase + curPatchSize < minY) + continue; + if (curYBase > minY + height) + continue; + + if (forceHighestLod) + { + m_PrecomputedError[GetPatchIndex(x, y, level)] = std::numeric_limits<float>::infinity(); + } + else + { + float error = ComputeMaximumHeightError(x, y, level); + m_PrecomputedError[GetPatchIndex(x, y, level)] = error; + } + RecalculateMinMaxHeight(x, y, level); + } + } + } + m_TerrainData->SetDirty(); +} + +/// After editing is complete we need to recompute the error for the modified patches +/// We also update the min&max height of each patch +void Heightmap::RecomputeInvalidPatches(UNITY_TEMP_VECTOR(int)& recomputedPatches) +{ + recomputedPatches.clear(); + for (int level=0;level <= m_Levels;level++) + { + for (int y=0;y<GetPatchCountY(level);y++) + { + for (int x=0;x<GetPatchCountX(level);x++) + { + int patchIndex = GetPatchIndex(x, y, level); + if (m_PrecomputedError[patchIndex] == std::numeric_limits<float>::infinity()) + { + float error = ComputeMaximumHeightError(x, y, level); + m_PrecomputedError[patchIndex] = error; + RecalculateMinMaxHeight(x, y, level); + + recomputedPatches.push_back(patchIndex); + } + } + } + } + if (!recomputedPatches.empty()) + m_TerrainData->SetDirty(); +} + +float Heightmap::GetMaximumHeightError (int x, int y, int level) const +{ + return m_PrecomputedError[GetPatchIndex(x, y, level)]; +} + + +float Heightmap::Bilerp (const float* corners, float u, float v) +{ + // Corners are laid out like this + /// 0 1 + /// 2 3 + if (u > v) + { + float z00 = corners[0]; + float z01 = corners[1]; + float z11 = corners[3]; + return z00 + (z01-z00) * u + (z11 - z01) * v; + } + else + { + float z00 = corners[0]; + float z10 = corners[2]; + float z11 = corners[3]; + return z00 + (z11-z10) * u + (z10 - z00) * v; + } +} + +/// The scaled interpolated height at the normalized coordinate x, y [0...1] +/// Out of bounds x and y will be clamped +float Heightmap::GetInterpolatedHeight (float x, float y) const +{ + float fx = x * (m_Width - 1); + float fy = y * (m_Height - 1); + int lx = (int)fx; + int ly = (int)fy; + + float u = fx - lx; + float v = fy - ly; + + if (u > v) + { + float z00 = GetHeight (lx+0, ly+0); + float z01 = GetHeight (lx+1, ly+0); + float z11 = GetHeight (lx+1, ly+1); + return z00 + (z01-z00) * u + (z11 - z01) * v; + } + else + { + float z00 = GetHeight (lx+0, ly+0); + float z10 = GetHeight (lx+0, ly+1); + float z11 = GetHeight (lx+1, ly+1); + return z00 + (z11-z10) * u + (z10 - z00) * v; + } +} + +// Gets the interpolated normal of the terrain at a +float Heightmap::GetSteepness (float x, float y) const +{ + float steepness = Dot(GetInterpolatedNormal(x, y), Vector3f(0.0F, 1.0F, 0.0F)); + steepness = Rad2Deg(acos (steepness)); + return steepness; +} + +// Gets the interpolated normal of the terrain at a +Vector3f Heightmap::GetInterpolatedNormal (float x, float y) const +{ + float fx = x * (m_Width - 1); + float fy = y * (m_Height - 1); + int lx = (int)fx; + int ly = (int)fy; + + Vector3f n00 = CalculateNormalSobel (lx+0, ly+0); + Vector3f n10 = CalculateNormalSobel (lx+1, ly+0); + Vector3f n01 = CalculateNormalSobel (lx+0, ly+1); + Vector3f n11 = CalculateNormalSobel (lx+1, ly+1); + + float u = fx - lx; + float v = fy - ly; + + Vector3f s = Lerp(n00, n10, u); + Vector3f t = Lerp(n01, n11, u); + Vector3f value = Lerp(s, t, v); + value = NormalizeFast(value); + + return value; +} + +void Heightmap::GetHeights (int xBase, int yBase, int width, int height, float* heights) const +{ + float toNormalize = 1.0F / kMaxHeight; + for (int x=0;x<width;x++) + { + for (int y=0;y<height;y++) + { + NxHeightFieldSample sample = reinterpret_cast<const NxHeightFieldSample&> (m_Heights[(y + yBase) + (x + xBase) * m_Height]); + float height = sample.height; + height *= toNormalize; + heights[y * width + x] = height; + } + } +} + +#define SUPPORT_PHYSX_UPDATE_BLOCKS 1 + +void Heightmap::SetHeights (int xBase, int yBase, int width, int height, const float* heights, bool delayLodComputation) +{ + UInt32 min = 0; + float normalizedTo16 = kMaxHeight; + + #if ENABLE_PHYSICS && SUPPORT_PHYSX_UPDATE_BLOCKS + NxHeightFieldSample* nxSamples = new NxHeightFieldSample[width * height]; + int materialIndex = GetLowMaterialIndex(GetMaterialIndex()); + #endif + + for (int x=0;x<width;x++) + { + for (int y=0;y<height;y++) + { + float nHeight = heights[y * width + x] * normalizedTo16; + SInt32 iheight = RoundfToInt(nHeight); + iheight = clamp<SInt32> (iheight, min, kMaxHeight); + + // Update height value + m_Heights[(y + yBase) + (x + xBase) * m_Height] = iheight; + + #if ENABLE_PHYSICS && SUPPORT_PHYSX_UPDATE_BLOCKS + // Build update buffer for novodex + NxHeightFieldSample sample; + sample.height = iheight; + sample.materialIndex0 = materialIndex; + sample.materialIndex1 = materialIndex; + sample.tessFlag = 1; + sample.unused = 0; + nxSamples[height * x + y] = sample; + #endif + } + } + + #if ENABLE_PHYSICS + #if SUPPORT_PHYSX_UPDATE_BLOCKS + if (m_NxHeightField) + { + m_NxHeightField->updateBlock(xBase, yBase, width, height, height * sizeof(NxHeightFieldSample), nxSamples); + RecreateShapes(); + } + + delete[] nxSamples; + #else + UpdateNx (); + RecreateShapes(); + #endif + #endif + + PrecomputeError(xBase, yBase, width, height, delayLodComputation); + + m_TerrainData->SetDirty(); + + m_TerrainData->UpdateUsers (delayLodComputation ? TerrainData::kDelayedHeightmapUpdate : TerrainData::kHeightmap); +} + +int Heightmap::GetAdjustedSize (int size) const +{ + int levels = HighestBit( NextPowerOfTwo( size / kPatchSize ) ); + levels = max<int>(1, levels); + return (1 << levels) * (kPatchSize - 1) + 1; +} + +#if ENABLE_PHYSICS +void Heightmap::SetPhysicMaterial(PPtr<PhysicMaterial> mat) +{ + m_DefaultPhysicMaterial = mat; + UpdateNx (); + RecreateShapes(); +} +#endif + +void Heightmap::SetResolution (int resolution) +{ + m_Levels = HighestBit( NextPowerOfTwo( resolution / kPatchSize ) ); + m_Levels = max<int>(1, m_Levels); + m_Width = (1 << m_Levels) * (kPatchSize - 1) + 1; + m_Height = (1 << m_Levels) * (kPatchSize - 1) + 1; + + UInt32 materialIndex = GetLowMaterialIndex(GetMaterialIndex());// + + NxHeightFieldSample sample; + sample.height = 0; + sample.materialIndex0 = materialIndex; + sample.materialIndex1 = materialIndex; + sample.tessFlag = 1; + sample.unused = 0; + + m_Heights.clear(); + m_Heights.resize(m_Width * m_Height, reinterpret_cast<UInt32&> (sample)); + + m_PrecomputedError.clear(); + m_PrecomputedError.resize(GetTotalPatchCount()); + + m_MinMaxPatchHeights.clear(); + m_MinMaxPatchHeights.resize(GetTotalPatchCount() * 2); + +#if ENABLE_PHYSICS + UpdateNx (); + RecreateShapes(); +#endif + + m_TerrainData->SetDirty(); + m_TerrainData->UpdateUsers (TerrainData::kHeightmap); +} + +float Heightmap::GetHeight (int x, int y) const +{ + x = clamp<int>(x, 0, m_Width-1); + y = clamp<int>(y, 0, m_Height-1); + float scale = m_Scale.y / (float)(kMaxHeight); + + NxHeightFieldSample sample = reinterpret_cast<const NxHeightFieldSample&> (m_Heights[y + x * m_Height]); + return sample.height * scale; +} + +float Heightmap::GetHeightRespectingNeighbors (int x, int y, const TerrainRenderer *renderer) const +{ + const Heightmap *lookup = this; + if(x<0) + { + if(renderer && renderer->m_LeftNeighbor && renderer->m_LeftNeighbor->GetTerrainData()) + { + renderer = renderer->m_LeftNeighbor; + lookup = &(renderer->GetTerrainData()->GetHeightmap()); + x += lookup->m_Width - 1; + } + } + if(x >= lookup->m_Width) + { + if(renderer && renderer->m_RightNeighbor && renderer->m_RightNeighbor->GetTerrainData()) + { + x -= lookup->m_Width - 1; + renderer = renderer->m_RightNeighbor; + lookup = &(renderer->GetTerrainData()->GetHeightmap()); + } + } + if(y<0) + { + if(renderer && renderer->m_BottomNeighbor && renderer->m_BottomNeighbor->GetTerrainData()) + { + renderer = renderer->m_BottomNeighbor; + lookup = &(renderer->GetTerrainData()->GetHeightmap()); + y += lookup->m_Height - 1; + } + } + if(y >= lookup->m_Height) + { + if(renderer && renderer->m_TopNeighbor && renderer->m_TopNeighbor->GetTerrainData()) + { + y -= lookup->m_Height - 1; + renderer = renderer->m_TopNeighbor; + lookup = &(renderer->GetTerrainData()->GetHeightmap()); + } + } + return lookup->GetHeight(x,y); +} + +void Heightmap::UpdatePatchIndices (Mesh& mesh, int xPatch, int yPatch, int level, int edgeMask) +{ + unsigned int count; + + unsigned short *tris = TerrainIndexGenerator::GetOptimizedIndexStrip(edgeMask, count); + mesh.SetIndicesComplex (tris, count, 0, kPrimitiveTriangleStripDeprecated, Mesh::k16BitIndices|Mesh::kDontSupportSubMeshVertexRanges); +} + +void Heightmap::UpdatePatchMesh (Mesh& mesh, int xPatch, int yPatch, int mipLevel, int edgeMask, TerrainRenderer *renderer) +{ + int vertexCount = kPatchSize * kPatchSize; + mesh.ResizeVertices (vertexCount, mesh.GetAvailableChannels() | VERTEX_FORMAT3(Vertex, TexCoord0, Normal)); + + UpdatePatchMeshInternal(*this, + mesh.GetVertexBegin(), + mesh.GetNormalBegin(), + mesh.GetUvBegin(), + xPatch, yPatch, mipLevel, edgeMask, renderer); + + mesh.SetBounds(GetBounds(xPatch, yPatch, mipLevel)); + mesh.SetChannelsDirty (mesh.GetAvailableChannels(), false); +} + +void Heightmap::UpdatePatchIndices (VBO& vbo, SubMesh& subMesh, int xPatch, int yPatch, int level, int edgeMask) +{ + unsigned int count; + + unsigned short *tris = TerrainIndexGenerator::GetOptimizedIndexStrip(edgeMask, count); + IndexBufferData ibd = { + tris, + count, + 1 << kPrimitiveTriangleStripDeprecated + }; + vbo.UpdateIndexData(ibd); + subMesh.indexCount = count; + subMesh.topology = kPrimitiveTriangleStripDeprecated; +} + +void Heightmap::UpdatePatchMesh (VBO& vbo, SubMesh& subMesh, int xPatch, int yPatch, int mipLevel, int edgeMask, TerrainRenderer *renderer) +{ + const VertexChannelsLayout& format = VertexDataInfo::kVertexChannelsDefault; + UInt8 normalOffset = format.channels[kShaderChannelVertex].dimension * GetChannelFormatSize(format.channels[kShaderChannelVertex].format); + UInt8 uvOffset = format.channels[kShaderChannelNormal].dimension + * GetChannelFormatSize(format.channels[kShaderChannelNormal].format) + + normalOffset; + UInt8 stride = format.channels[kShaderChannelTexCoord0].dimension + * GetChannelFormatSize(format.channels[kShaderChannelTexCoord0].format) + + uvOffset; + + VertexBufferData vbd; + + vbd.channels[kShaderChannelVertex].format = format.channels[kShaderChannelVertex].format; + vbd.channels[kShaderChannelVertex].dimension = format.channels[kShaderChannelVertex].dimension; + vbd.channels[kShaderChannelNormal].format = format.channels[kShaderChannelNormal].format; + vbd.channels[kShaderChannelNormal].dimension = format.channels[kShaderChannelNormal].dimension; + vbd.channels[kShaderChannelNormal].offset = normalOffset; + vbd.channels[kShaderChannelTexCoord0].format = format.channels[kShaderChannelTexCoord0].format; + vbd.channels[kShaderChannelTexCoord0].dimension = format.channels[kShaderChannelTexCoord0].dimension; + vbd.channels[kShaderChannelTexCoord0].offset = uvOffset; + + // UV1 is used for lightmapping and it shares with UV0 + vbd.channels[kShaderChannelTexCoord1] = vbd.channels[kShaderChannelTexCoord0]; + + vbd.streams[0].channelMask = VERTEX_FORMAT4(Vertex, Normal, TexCoord0, TexCoord1); + vbd.streams[0].stride = stride; + + vbd.vertexCount = kPatchSize * kPatchSize; + + vbd.bufferSize = VertexDataInfo::AlignStreamSize(vbd.vertexCount * stride); + UInt8* buffer; + ALLOC_TEMP_ALIGNED(buffer, UInt8, vbd.bufferSize, VertexDataInfo::kVertexDataAlign); + vbd.buffer = buffer; + + UpdatePatchMeshInternal(*this, + StrideIterator<Vector3f>(vbd.buffer, stride), + StrideIterator<Vector3f>(vbd.buffer + normalOffset, stride), + StrideIterator<Vector2f>(vbd.buffer + uvOffset, stride), + xPatch, yPatch, mipLevel, edgeMask, renderer); + + vbo.UpdateVertexData(vbd); + subMesh.localAABB = GetBounds(xPatch, yPatch, mipLevel); + subMesh.vertexCount = vbd.vertexCount; +} + +void Heightmap::GetPatchData (int xPatch, int yPatch, int mipLevel, float* heights) const +{ + int skip = 1 << mipLevel; + int xBase = xPatch * (kPatchSize -1); + int yBase = yPatch * (kPatchSize -1); + + float scale = m_Scale.y / (float)(kMaxHeight); + + for (int x=0;x<kPatchSize;x++) + { + for (int y=0;y<kPatchSize;y++) + { + int index = (y + yBase) + (x + xBase) * m_Height; + index *= skip; + float height = m_Heights[index]; + height *= scale; + heights[y + x * kPatchSize] = height; + } + } +} + +void Heightmap::RecalculateMinMaxHeight (int xPatch, int yPatch, int mipLevel) +{ + int totalSamples = kPatchSize * kPatchSize; + float* heights = new float[totalSamples]; + GetPatchData (xPatch, yPatch, mipLevel, heights); + float minHeight = std::numeric_limits<float>::infinity(); + float maxHeight = -std::numeric_limits<float>::infinity(); + for (int i=0;i<totalSamples;i++) + { + minHeight = min(minHeight, heights[i]); + maxHeight = max(maxHeight, heights[i]); + } + + int patchIndex = GetPatchIndex(xPatch, yPatch, mipLevel); + m_MinMaxPatchHeights[patchIndex * 2 + 0] = minHeight / m_Scale.y ; + m_MinMaxPatchHeights[patchIndex * 2 + 1] = maxHeight / m_Scale.y; + + delete[] heights; +} + +AABB Heightmap::GetBounds () const +{ + return GetBounds(0, 0, m_Levels); +} + + +AABB Heightmap::GetBounds (int xPatch, int yPatch, int mipLevel) const +{ + int patchIndex = GetPatchIndex(xPatch, yPatch, mipLevel); + + float minHeight = m_MinMaxPatchHeights[patchIndex * 2 + 0]; + float maxHeight = m_MinMaxPatchHeights[patchIndex * 2 + 1]; + + Vector3f min; + min.x = (xPatch) * (1 << mipLevel) * (kPatchSize -1) * m_Scale.x; + min.z = (yPatch) * (1 << mipLevel) * (kPatchSize -1) * m_Scale.z; + min.y = minHeight * m_Scale.y; + + Vector3f max; + max.x = (xPatch + 1) * (1 << mipLevel) * (kPatchSize -1) * m_Scale.x; + max.z = (yPatch + 1) * (1 << mipLevel) * (kPatchSize -1) * m_Scale.z; + max.y = maxHeight * m_Scale.y; + + MinMaxAABB bounds = MinMaxAABB (min, max); + return bounds; +} + + +int Heightmap::GetMaterialIndex () const +{ +#if ENABLE_PHYSICS + const PhysicMaterial* material = m_DefaultPhysicMaterial; + if (material) + return material->GetMaterialIndex (); + else +#endif + return 0; +} + +#if ENABLE_PHYSICS +NxHeightField* Heightmap::GetNxHeightField () +{ + if (m_NxHeightField == NULL) + CreateNx(); + return m_NxHeightField; +} + +void Heightmap::CreateNx () +{ + IPhysics* physicsModule = GetIPhysics(); + + if (!physicsModule) + return; + + if (m_NxHeightField == NULL) + { + NxHeightFieldDesc desc; + BuildDesc(desc); + + m_NxHeightField = physicsModule->CreateNxHeightField(desc); + + free(desc.samples); + } + else + UpdateNx (); +} + +// builds an nsdesc of the heightmap (you need to deallocate the samples array yourself) +void Heightmap::BuildDesc (NxHeightFieldDesc& desc) +{ + NxHeightFieldSample* nxSamples = (NxHeightFieldSample*)malloc (sizeof(NxHeightFieldSample) * m_Width * m_Height); + AssertIf(!nxSamples); + desc.nbRows = m_Width; + desc.nbColumns = m_Height; + desc.samples = nxSamples; + desc.sampleStride = 4; + // the threshold seems to be the difference between two samples in 16 height space + // Thus a value of less than 1 doesn't make a difference + desc.convexEdgeThreshold = 4; + int materialIndex = GetLowMaterialIndex(GetMaterialIndex()); + + for (int x=0;x<m_Width;x++) + { + for (int y=0;y<m_Height;y++) + { + // Build update buffer for novodex + NxHeightFieldSample sample; + sample.height = m_Heights[m_Height * x + y]; + sample.materialIndex0 = materialIndex; + sample.materialIndex1 = materialIndex; + sample.tessFlag = 1; + sample.unused = 0; + nxSamples[m_Height * x + y] = sample; + } + } +} + +void Heightmap::UpdateNx () +{ + if (!m_NxHeightField) + return; + + NxHeightFieldDesc desc; + BuildDesc(desc); + + + m_NxHeightField->loadFromDesc(desc); + free(desc.samples); +} + +void Heightmap::CleanupNx () +{ + if (m_NxHeightField) + { + GetIPhysics()->ReleaseHeightField(*m_NxHeightField); + m_NxHeightField = NULL; + } +} + +#endif +#endif diff --git a/Runtime/Terrain/Heightmap.h b/Runtime/Terrain/Heightmap.h new file mode 100644 index 0000000..cc294f4 --- /dev/null +++ b/Runtime/Terrain/Heightmap.h @@ -0,0 +1,199 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Math/Vector3.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Utilities/Utility.h" +#include "Runtime/Dynamics/Collider.h" +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Dynamics/PhysicMaterial.h" + +class Mesh; +class NxHeightField; +class NxHeightFieldDesc; +struct SubMesh; +class TerrainData; +class TerrainCollider; +class TerrainRenderer; +class VBO; + +enum +{ + kDirectionLeft = 0, kDirectionRight = 1, kDirectionUp = 2, kDirectionDown = 3, + kDirectionLeftUp = 0, kDirectionRightUp = 1, kDirectionLeftDown = 2, kDirectionRightDown = 3, +}; + +enum +{ + kDirectionLeftFlag = (1<<kDirectionLeft), + kDirectionRightFlag = (1<<kDirectionRight), + kDirectionUpFlag = (1<<kDirectionUp), + kDirectionDownFlag = (1<<kDirectionDown), + kDirectionDirectNeighbourMask = (kDirectionLeftFlag|kDirectionRightFlag|kDirectionUpFlag|kDirectionDownFlag), +}; + +//made this value constant. It is not currently being modified anyways, and this way, +//cached triangles strips can be used across several terrains. +//Must be an odd number. +#define kPatchSize 17 + +class Heightmap +{ + public: + enum { kMaxHeight = 32766 }; + + DECLARE_SERIALIZE(Heightmap) + + Heightmap (TerrainData* owner); + virtual ~Heightmap (); + + int GetWidth () const { return m_Width; } + int GetHeight () const { return m_Height; } + int GetMipLevels () const { return m_Levels; } + + const Vector3f& GetScale () const { return m_Scale; } + + Vector3f GetSize () const; + + void SetSize (const Vector3f& size); + + virtual void AwakeFromLoad(AwakeFromLoadMode mode){} + + /// After editing is complete we need to recompute the error for the modified patches + /// We also update the min&max height of each patch + void RecomputeInvalidPatches (UNITY_TEMP_VECTOR(int)& recomputedPatches); + + float GetMaximumHeightError (int x, int y, int level) const; + + /// The scaled interpolated height at the normalized coordinate x, y [0...1] + /// Out of bounds x and y will be clamped + float GetInterpolatedHeight (float x, float y) const; + + /// Interpolates 4 height values just like GetInterpolated height + /// Used for optimized heightmap lookups when you know the corners. + // Corners are laid out like this + /// 0 1 + /// 2 3 + static float Bilerp (const float* corners, float u, float v); + + /// The scaled height at height map pixel x, y. + /// Out of bounds x and y will be clamped + float GetHeight (int x, int y) const; + + SInt16 GetRawHeight(int sampleIndex) const { return m_Heights[sampleIndex]; } + + float GetHeightRespectingNeighbors (int x, int y, const TerrainRenderer *renderer) const; + + float GetSteepness (float x, float y) const; + + Vector3f GetInterpolatedNormal (float x, float y) const; + + void GetHeights (int xBase, int yBase, int width, int height, float* heights) const; + void SetHeights (int xBase, int yBase, int width, int height, const float* heights, bool delay); + + int GetAdjustedSize (int size) const; + + void SetResolution (int resolution); + int GetResolution () const { return m_Width; } + + int GetTotalPatchCount () const { return GetPatchIndex(0, 0, m_Levels) + 1; } + + int GetMaterialIndex () const; + + void GetPatchData (int xPatch, int yPatch, int mipLevel, float* heights) const; + + void UpdatePatchIndices (Mesh& mesh, int xPatch, int yPatch, int level, int edgeMask); + void UpdatePatchMesh (Mesh& mesh, int xPatch, int yPatch, int mipLevel, int edgeMask, TerrainRenderer *renderer); + void UpdatePatchIndices (VBO& vbo, SubMesh& subMesh, int xPatch, int yPatch, int level, int edgeMask); + void UpdatePatchMesh (VBO& vbo, SubMesh& subMesh, int xPatch, int yPatch, int mipLevel, int edgeMask, TerrainRenderer *renderer); + AABB GetBounds (int xPatch, int yPatch, int mipLevel) const; + + NxHeightField* GetNxHeightField (); + + typedef List< ListNode<TerrainCollider> > TerrainColliderList; + TerrainColliderList& GetTerrainColliders () { return m_TerrainColliders; } + + ///@TODO: THIS IS STILL INCORRECT + Vector3f CalculateNormalSobel (int x, int y) const; + Vector3f CalculateNormalSobelRespectingNeighbors (int x, int y, const TerrainRenderer *renderer) const; + + AABB GetBounds () const; + + void AwakeFromLoad (); + +#if ENABLE_PHYSICS + PPtr<PhysicMaterial> GetPhysicMaterial() const { return m_DefaultPhysicMaterial; } + void SetPhysicMaterial(PPtr<PhysicMaterial> mat); +#endif + +private: + + TerrainData* m_TerrainData; + /// Raw heightmap + std::vector<SInt16> m_Heights; + + // Precomputed error of every patch + std::vector<float> m_PrecomputedError; + // Precomputed min&max value of each terrain patch + std::vector<float> m_MinMaxPatchHeights; + + TerrainColliderList m_TerrainColliders; + + int m_Width; + int m_Height; + int m_Levels; + Vector3f m_Scale; + + int GetPatchCountX (int level) const { return 1 << (m_Levels - level); } + int GetPatchCountY (int level) const { return 1 << (m_Levels - level); } + + inline float GetPatchHeight (float* data, int x, int y) const + { + return data[y + x * kPatchSize]; + } + + /// Calculates the index of the patch given it's level and x, y index + int GetPatchIndex (int x, int y, int level) const; + + float InterpolatePatchHeight (float* data, float fx, float fy) const; + + float ComputeMaximumHeightError (int xPatch, int yPatch, int level) const; + + void RecalculateMinMaxHeight (int xPatch, int yPatch, int mipLevel); + // Precompute error only on a part of the heightmap + // if forceHighestLod is enabled we simply set the error to infinity + // This casues the heightmap to be rendered at full res (Used while editing) + void PrecomputeError (int minX, int minY, int width, int height, bool forceHighestLod); + +#if ENABLE_PHYSICS + PPtr<PhysicMaterial> m_DefaultPhysicMaterial; + NxHeightField* m_NxHeightField; + + void CleanupNx (); + void CreateNx (); + void UpdateNx (); + void BuildDesc (NxHeightFieldDesc& desc); + + void RecreateShapes (); +#endif +}; + +template<class TransferFunc> +void Heightmap::Transfer (TransferFunc& transfer) +{ + TRANSFER(m_Heights); + transfer.Align(); + TRANSFER(m_PrecomputedError); + TRANSFER(m_MinMaxPatchHeights); +#if ENABLE_PHYSICS + TRANSFER(m_DefaultPhysicMaterial); +#endif + TRANSFER(m_Width); + TRANSFER(m_Height); + TRANSFER(m_Levels); + TRANSFER(m_Scale); +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/ImposterRenderTexture.cpp b/Runtime/Terrain/ImposterRenderTexture.cpp new file mode 100644 index 0000000..7fc737b --- /dev/null +++ b/Runtime/Terrain/ImposterRenderTexture.cpp @@ -0,0 +1,229 @@ +#include "UnityPrefix.h" +#include "ImposterRenderTexture.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Graphics/RenderTexture.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "Runtime/Utilities/BitUtility.h" +#include "Runtime/Misc/GameObjectUtility.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Graphics/DrawUtil.h" +#include "External/shaderlab/Library/FastPropertyName.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Camera/Renderqueue.h" + + +#if UNITY_PS3 +# include "Runtime/GfxDevice/ps3/GfxDevicePS3.h" +#endif + +const float kMinimumAngleDelta = 0.1f * kDeg2Rad; +const float kImpostorPadding = 1.0f; + +ImposterRenderTexture::ImposterRenderTexture (const TreeDatabase& treeDB) +: m_TreeDatabase(treeDB) +, m_AngleX(std::numeric_limits<float>::infinity()) +, m_AngleY(std::numeric_limits<float>::infinity()) +, m_ImposterHeight(256) +, m_MaxImposterSize(2048) +, m_CameraOrientationMatrix(Matrix4x4f::identity) +{ + // if render textures or vertex shaders are not supported, there shall be + // no impostors. + RenderTexture::SetTemporarilyAllowIndieRenderTexture (true); + if (!RenderTexture::IsEnabled()) + { + RenderTexture::SetTemporarilyAllowIndieRenderTexture (false); + m_Supported = false; + m_Texture = NULL; + m_Camera = NULL; + return; + } + m_Supported = true; + + const std::vector<TreeDatabase::Prototype>& prototypes = m_TreeDatabase.GetPrototypes(); + + m_Areas.resize(prototypes.size()); + // Calculate how many pixel width we need! + float totalPixelWidth = 0.0F; + for (int i = 0; i < prototypes.size(); ++i) + { + totalPixelWidth += m_ImposterHeight * prototypes[i].getBillboardAspect() + kImpostorPadding; + } + + int textureWidth = std::min<int>(m_MaxImposterSize, ClosestPowerOfTwo(RoundfToIntPos(totalPixelWidth))); + + float uOffset = kImpostorPadding / (float)textureWidth; + + // Calculate areas + float runOffset = 0.0F; + for (int i = 0; i < prototypes.size(); ++i) + { + float width = (m_ImposterHeight * prototypes[i].getBillboardAspect()) / totalPixelWidth; + m_Areas[i].Set(runOffset + uOffset, 0.0F, width - uOffset - uOffset, 1.0F); + runOffset += width; + } + + // Setup Render texture + m_Texture = NEW_OBJECT (RenderTexture); + m_Texture->Reset(); + + m_Texture->SetHideFlags(Object::kDontSave); + m_Texture->SetWidth(textureWidth); + m_Texture->SetHeight(m_ImposterHeight); + m_Texture->SetColorFormat(kRTFormatARGB32); + m_Texture->SetDepthFormat(kDepthFormat16); + m_Texture->SetName("Tree Imposter Texture"); + m_Texture->SetMipMap(true); + m_Texture->SetMipMapBias(-1); + + m_Texture->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + // Setup camera + GameObject* cameraGO = &CreateGameObjectWithHideFlags ("Imposter Camera", true, Object::kHideAndDontSave, "Camera", NULL); + m_Camera = &cameraGO->GetComponent(Camera); + m_Camera->SetTargetTexture(m_Texture); + m_Camera->SetClearFlags(Camera::kSolidColor); + m_Camera->SetBackgroundColor(ColorRGBAf(.2f,.2f,.2f,0)); + m_Camera->SetOrthographic(true); + m_Camera->SetCullingMask(0); + m_Camera->SetEnabled(false); + +#if UNITY_PS3 + ((GfxDevicePS3&)GetGfxDevice()).RegisterSurfaceLostCallback(&InvalidateRenderTexture, this); +#endif + + RenderTexture::SetTemporarilyAllowIndieRenderTexture (false); +} + +ImposterRenderTexture::~ImposterRenderTexture() +{ +#if UNITY_PS3 + ((GfxDevicePS3&)GetGfxDevice()).UnregisterSurfaceLostCallback(&InvalidateRenderTexture, this); +#endif + if (m_Camera != NULL) + DestroyObjectHighLevel(&m_Camera->GetGameObject()); + if (m_Texture != NULL) + DestroyObjectHighLevel(m_Texture); +} + +void ImposterRenderTexture::UpdateImposters (const Camera& mainCamera) +{ + // Only update if we really have to because the viewing angle is very different + const Transform& transform = mainCamera.GetComponent (Transform); + const Vector3f& cameraEuler = QuaternionToEuler(transform.GetRotation()); + if (m_Texture->IsCreated()) + { + if (m_AngleX != std::numeric_limits<float>::infinity() && std::abs(DeltaAngleRad (m_AngleX, cameraEuler.x)) < kMinimumAngleDelta && + m_AngleY != std::numeric_limits<float>::infinity() && std::abs(DeltaAngleRad (m_AngleY, cameraEuler.y)) < kMinimumAngleDelta) + return; + } + + m_AngleX = cameraEuler.x; + m_AngleY = cameraEuler.y; + + m_Camera->GetComponent (Transform).SetLocalEulerAngles(Vector3f (cameraEuler.x, cameraEuler.y, 0) * Rad2Deg(1)); + m_CameraOrientationMatrix = m_Camera->GetCameraToWorldMatrix(); + + Camera& oldCamera = GetCurrentCamera(); + + // Clear the whole render texture + m_Camera->SetNormalizedViewportRect(Rectf (0, 0, 1, 1)); + m_Camera->SetClearFlags(Camera::kSolidColor); + + m_Camera->StandaloneRender(Camera::kRenderFlagDontRestoreRenderState | Camera::kRenderFlagSetRenderTarget, NULL, ""); + + const std::vector<TreeDatabase::Prototype>& prototypes = m_TreeDatabase.GetPrototypes(); + for (int i = 0; i < prototypes.size(); ++i) + { + const Rectf& rect = m_Areas[i];//new Rect (offset , 0.0F, width, 1.0F); + UpdateImposter(rect, prototypes[i]); + } + + oldCamera.StandaloneSetup(); +} + +// angleFactor - used for: +// 1) non-uniform scale compensation +// 2) blending between vertical (viewing from front) and horizontal (viewing from above/below) billboard modes +// offsetFactor - used for offsetting billboard from the ground when billboard is in horizontal mode +// +// See TerrainBillboardTree in TerrainEngine.cginc for more detailed explanation +void ImposterRenderTexture::GetBillboardParams(float& angleFactor, float& offsetFactor) const +{ + float angles = Rad2Deg(m_AngleX); + angles = angles <= 90 ? angles : angles - 360; + + AssertMsg(angles >= -90 && angles <= 90, "Invalid angle: %f", angles); + + angleFactor = 1 - cos(Deg2Rad(angles)); + + { + // calculate modeFactor + const float kLimitAngle = 40; + + float factor = fabsf(angles); + factor = factor < kLimitAngle ? 0 : SmoothStep(0, 1, (factor - kLimitAngle) / (90 - kLimitAngle)); + + // we never want to use bottom-center mode completely, because it can cause intersection + // of billboard with terrain, so we just raise it a bit all the time + offsetFactor = std::max(0.1f, factor); + } +} + +void ImposterRenderTexture::UpdateImposter (const Rectf& rect, const TreeDatabase::Prototype& prototype) +{ + if (prototype.imposterMaterials.empty() + || !prototype.mesh.IsValid()) + { + return; + } + + { + Transform& transform = m_Camera->GetComponent (Transform); + // Setup camera location + transform.SetPosition(Vector3f (0, prototype.getCenterOffset(), 0)); + // Just move far away enough to get the whole tree into the view. (How far doesnt matter since it is orthographic projection) + transform.SetPosition(transform.GetPosition() + transform.TransformDirection (-Vector3f::zAxis * (prototype.treeHeight + prototype.treeWidth) * 2)); + } + + // Setup camera rect + m_Camera->SetClearFlags(Camera::kDontClear); + m_Camera->SetNormalizedViewportRect(rect); + + m_Camera->SetAspect(prototype.getBillboardAspect()); + m_Camera->SetOrthographicSize(prototype.getBillboardHeight() * 0.5F); + + // Setup render target + m_Camera->StandaloneRender(Camera::kRenderFlagDontRestoreRenderState | Camera::kRenderFlagSetRenderTarget, NULL, ""); + + const ShaderLab::FastPropertyName colorProperty = ShaderLab::Property("_Color"); + const ShaderLab::FastPropertyName halfOverCutoffProperty = ShaderLab::Property("_HalfOverCutoff"); + const ShaderLab::FastPropertyName terrainEngineBendTreeProperty = ShaderLab::Property("_TerrainEngineBendTree"); + + for (int m=0; m<prototype.imposterMaterials.size(); ++m) + { + const ColorRGBAf& color = prototype.originalMaterialColors[m]; + float cutoff = prototype.inverseAlphaCutoff[m]; + Material* mat = prototype.imposterMaterials[m]; + mat->SetColor(colorProperty, color); + mat->SetFloat(halfOverCutoffProperty, cutoff); + mat->SetMatrix(terrainEngineBendTreeProperty, Matrix4x4f::identity); + for (int p=0; p<mat->GetPassCount(); ++p) + { + if (CheckShouldRenderPass (p, *mat)) + { + const ChannelAssigns* channels = mat->SetPass(p); + if (channels) + { + DrawUtil::DrawMesh (*channels, *prototype.mesh, Vector3f::zero, Quaternionf::identity(), m); + } + } + } + } +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/ImposterRenderTexture.h b/Runtime/Terrain/ImposterRenderTexture.h new file mode 100644 index 0000000..de7714f --- /dev/null +++ b/Runtime/Terrain/ImposterRenderTexture.h @@ -0,0 +1,65 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "TreeDatabase.h" +#include "Runtime/Math/Rect.h" +#include <vector> + + +class RenderTexture; +class Camera; + + +class ImposterRenderTexture +{ +public: + ImposterRenderTexture(const TreeDatabase& treeDB); + ~ImposterRenderTexture(); + + const Rectf& GetArea(int index) const { + return m_Areas[index]; + } + + RenderTexture* GetTexture() const { return m_Texture; } + bool GetSupported() const { return m_Supported; } + + void UpdateImposters(const Camera& mainCamera); + + void InvalidateAngles () + { + m_AngleX = std::numeric_limits<float>::infinity(); + m_AngleY = std::numeric_limits<float>::infinity(); + } + + const Matrix4x4f& getCameraOrientation() const { return m_CameraOrientationMatrix; } + + void GetBillboardParams(float& angleFactor, float& offsetFactor) const; +private: + void UpdateImposter(const Rectf& rect, const TreeDatabase::Prototype& prototype); + +private: + const TreeDatabase& m_TreeDatabase; + + std::vector<Rectf> m_Areas; + Camera* m_Camera; + RenderTexture* m_Texture; + bool m_Supported; + + float m_AngleX; + float m_AngleY; + + int m_ImposterHeight; + int m_MaxImposterSize; + + Matrix4x4f m_CameraOrientationMatrix; + + static void InvalidateRenderTexture(void* userData) { + ((ImposterRenderTexture*)userData)->InvalidateAngles(); + } +}; + +#endif // ENABLE_TERRAIN + + diff --git a/Runtime/Terrain/PerlinNoise.cpp b/Runtime/Terrain/PerlinNoise.cpp new file mode 100644 index 0000000..6507f03 --- /dev/null +++ b/Runtime/Terrain/PerlinNoise.cpp @@ -0,0 +1,73 @@ +#include "UnityPrefix.h" +#include "PerlinNoise.h" + +inline static float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } +inline static float lerp(float t, float a, float b) { return a + t * (b - a); } +inline static float grad(int hash, float x, float y, float z) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + float u = h<8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. + v = h<4 ? y : h==12||h==14 ? x : z; + return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); +} + +inline static float grad2(int hash, float x, float y) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. + v = h < 4 ? y : h==12 || h==14 ? x : 0; + return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); +} + +static int p[] = +{ + 151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180, + 151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 +}; + + + +float PerlinNoise::Noise(float x, float y) { + x = Abs(x); + y = Abs(y); + + int floorX = int(x); + int floorY = int(y); + + int X = floorX & 255; // FIND UNIT CUBE THAT + int Y = floorY & 255; // CONTAINS POINT. + x -= floorX; // FIND RELATIVE X,Y,Z + y -= floorY; // OF POINT IN CUBE. + float u = fade(std::min(x, 1.0f)); // COMPUTE FADE CURVES + float v = fade(std::min(y, 1.0f)); // FOR EACH OF X,Y,Z. + int A = p[X ]+Y, AA = p[A], AB = p[A+1], // HASH COORDINATES OF + B = p[X+1]+Y, BA = p[B], BB = p[B+1]; // THE 8 CUBE CORNERS, + + float res = lerp(v, lerp(u, grad2(p[AA ], x , y ), // AND ADD + grad2(p[BA ], x-1, y )), // BLENDED + lerp(u, grad2(p[AB ], x , y-1 ), // RESULTS + grad2(p[BB ], x-1, y-1 )));// FROM 8 + return res; +} diff --git a/Runtime/Terrain/PerlinNoise.h b/Runtime/Terrain/PerlinNoise.h new file mode 100644 index 0000000..7a9a978 --- /dev/null +++ b/Runtime/Terrain/PerlinNoise.h @@ -0,0 +1,16 @@ +#ifndef PERLINNOISE_H +#define PERLINNOISE_H +class PerlinNoise { +public: + static float Noise(float x, float y); + // Returns noise between 0 - 1 + inline static float NoiseNormalized(float x, float y) + { + //-0.697 - 0.795 + 0.697 + float value = Noise(x, y); + value = (value + 0.69F) / (0.793F + 0.69F); + return value; + } +}; + +#endif diff --git a/Runtime/Terrain/QuadTreeNodeRenderer.cpp b/Runtime/Terrain/QuadTreeNodeRenderer.cpp new file mode 100644 index 0000000..c2eca30 --- /dev/null +++ b/Runtime/Terrain/QuadTreeNodeRenderer.cpp @@ -0,0 +1,124 @@ +#include "UnityPrefix.h" +#include "QuadTreeNodeRenderer.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Terrain/TerrainRenderer.h" +#include "Runtime/Utilities/InitializeAndCleanup.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Camera/UnityScene.h" +#if UNITY_PS3 +# include "Runtime/GfxDevice/ps3/GfxGCMVBO.h" +#endif + +DEFINE_POOLED_ALLOC(QuadTreeNodeRenderer, 64 * 1024); + +void QuadTreeNodeRenderer::StaticInitialize() +{ + STATIC_INITIALIZE_POOL(QuadTreeNodeRenderer); +} + +void QuadTreeNodeRenderer::StaticDestroy() +{ + STATIC_DESTROY_POOL(QuadTreeNodeRenderer); +} + +static RegisterRuntimeInitializeAndCleanup s_QuadTreeNodeRendererCallbacks(QuadTreeNodeRenderer::StaticInitialize, QuadTreeNodeRenderer::StaticDestroy); + +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + +QuadTreeNodeRenderer::QuadTreeNodeRenderer() +: m_TerrainRenderer(NULL) +, m_QuadTreeNode(NULL) +{ +} + +void QuadTreeNodeRenderer::Initialize(const Matrix4x4f& matrix, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows) +{ + MeshIntermediateRenderer::Initialize(matrix, NULL, localAABB, material, layer, castShadows, receiveShadows, 0); +} + +QuadTreeNodeRenderer::~QuadTreeNodeRenderer() +{ +} + +void QuadTreeNodeRenderer::Setup(TerrainRenderer* terrainRenderer, QuadTreeNode* quadTreeNode) +{ + AssertIf(terrainRenderer == NULL || quadTreeNode == NULL); + + m_TerrainRenderer = terrainRenderer; + m_QuadTreeNode = quadTreeNode; +} + + +void QuadTreeNodeRenderer::Render( int /*subsetIndex*/, const ChannelAssigns& channels ) +{ + if (m_TerrainRenderer == NULL || m_QuadTreeNode == NULL) + { + return; + } + + if (m_QuadTreeNode->vbo == NULL) + { + m_QuadTreeNode->vbo = m_TerrainRenderer->CreateVBO(); + + // these two flags shall always be set if a new VBO is requested + AssertIf(!m_QuadTreeNode->updateMesh || !m_QuadTreeNode->updateIndices); + } + + if (m_QuadTreeNode->updateMesh) + { + m_TerrainRenderer->GetTerrainData()->GetHeightmap().UpdatePatchMesh( + *m_QuadTreeNode->vbo, + m_QuadTreeNode->subMesh, + m_QuadTreeNode->x, + m_QuadTreeNode->y, + m_QuadTreeNode->level, + m_QuadTreeNode->edgeMask, m_TerrainRenderer); + m_QuadTreeNode->updateMesh = false; + } + + if (m_QuadTreeNode->updateIndices) + { + m_TerrainRenderer->GetTerrainData()->GetHeightmap().UpdatePatchIndices( + *m_QuadTreeNode->vbo, + m_QuadTreeNode->subMesh, + m_QuadTreeNode->x, + m_QuadTreeNode->y, + m_QuadTreeNode->level, + m_QuadTreeNode->edgeMask); + m_QuadTreeNode->updateIndices = false; + } + + if (m_CustomProperties) + { + GetGfxDevice().SetMaterialProperties (*m_CustomProperties); + } + + SubMesh& subMesh = m_QuadTreeNode->subMesh; + + //PROFILER_AUTO(gDrawMeshVBOProfile, &mesh) +#if UNITY_PS3 + GfxGCMVBO* gcmVBO = static_cast<GfxGCMVBO*>(m_QuadTreeNode->vbo); + gcmVBO->DrawSubmesh(channels, 0, &subMesh); +#else + m_QuadTreeNode->vbo->DrawVBO(channels, subMesh.firstByte, subMesh.indexCount, subMesh.topology, subMesh.firstVertex, subMesh.vertexCount); +#endif + GPU_TIMESTAMP(); +} + +QuadTreeNodeRenderer* AddQuadTreeNodeRenderer( const Matrix4x4f& matrix, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, Camera* camera ) +{ + QuadTreeNodeRenderer* renderer = new QuadTreeNodeRenderer (); + renderer->Initialize(matrix, localAABB, material, layer, castShadows, receiveShadows); + + IntermediateRenderers* renderers; + if (camera != NULL) + renderers = &camera->GetIntermediateRenderers(); + else + renderers = &GetScene().GetIntermediateRenderers(); + renderers->Add(renderer, layer); + + return renderer; +} + +#endif diff --git a/Runtime/Terrain/QuadTreeNodeRenderer.h b/Runtime/Terrain/QuadTreeNodeRenderer.h new file mode 100644 index 0000000..7f70ee7 --- /dev/null +++ b/Runtime/Terrain/QuadTreeNodeRenderer.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Runtime/Camera/IntermediateRenderer.h" + +struct QuadTreeNode; +class Camera; +class TerrainRenderer; + +class QuadTreeNodeRenderer : public MeshIntermediateRenderer +{ +public: + QuadTreeNodeRenderer (); + + void Initialize (const Matrix4x4f& matrix, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows); + + virtual ~QuadTreeNodeRenderer(); + + void Setup(TerrainRenderer* terrainRenderer, QuadTreeNode* node); + + virtual void Render( int materialIndex, const ChannelAssigns& channels ); + + static void StaticInitialize (); + static void StaticDestroy (); + +protected: + + +private: + // Note: not using per-frame linear allocator, because in the editor + // it can render multiple frames using single player loop run (e.g. when editor is paused). + // Clearing per-frame data and then trying to use it later leads to Bad Things. + DECLARE_POOLED_ALLOC(QuadTreeNodeRenderer); + + TerrainRenderer* m_TerrainRenderer; // It's safe to store raw pointer to the TerrainRender object here + // because TerrainRenderers are deleted before Render() happens + // and the life-time of TerrainVBORenderer object is only one frame. + QuadTreeNode* m_QuadTreeNode; +}; + +QuadTreeNodeRenderer* AddQuadTreeNodeRenderer( const Matrix4x4f& matrix, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, Camera* camera ); diff --git a/Runtime/Terrain/ScriptBindings/TerrainDataBindings.txt b/Runtime/Terrain/ScriptBindings/TerrainDataBindings.txt new file mode 100644 index 0000000..c14d84a --- /dev/null +++ b/Runtime/Terrain/ScriptBindings/TerrainDataBindings.txt @@ -0,0 +1,406 @@ +C++RAW +#include "UnityPrefix.h" +#include "Runtime/Terrain/Heightmap.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Terrain/DetailDatabase.h" +#include "Runtime/Terrain/SplatDatabase.h" +#include "Runtime/Terrain/TerrainData.h" +#include "Runtime/Terrain/TerrainInstance.h" +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Terrain/TerrainRenderer.h" +#include "Runtime/Camera/Light.h" +#include "Runtime/Terrain/DetailRenderer.h" +#include "Runtime/Terrain/ImposterRenderTexture.h" +#include "Runtime/Terrain/TreeRenderer.h" +#include "Runtime/Terrain/Wind.h" +#include "Runtime/Terrain/Tree.h" +#include "Runtime/Scripting/Scripting.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingExportUtility.h" + +using namespace Unity; +using namespace std; + +CSRAW + +#if ENABLE_TERRAIN + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Collections; +using System.Collections.Generic; + +namespace UnityEngine +{ + +CSRAW [StructLayout (LayoutKind.Sequential)] +CONDITIONAL ENABLE_TERRAIN +CLASS TreePrototype + CSRAW internal GameObject m_Prefab; + CSRAW internal float m_BendFactor; + + CSRAW public GameObject prefab { get { return m_Prefab; } set { m_Prefab = value; } } + + CSRAW public float bendFactor { get { return m_BendFactor; } set { m_BendFactor = value; } } +END + +ENUM DetailRenderMode + GrassBillboard = 0, + VertexLit = 1, + Grass = 2 +END + +CSRAW [StructLayout (LayoutKind.Sequential)] +CONDITIONAL ENABLE_TERRAIN +CLASS DetailPrototype + CSRAW GameObject m_Prototype = null; + CSRAW Texture2D m_PrototypeTexture = null; + CSRAW Color m_HealthyColor = new Color (67/255F, 249/255F, 42/255F, 1 ); + CSRAW Color m_DryColor = new Color (205/255.0F, 188/255.0F, 26/255.0F, 1.0F ) ; + CSRAW float m_MinWidth = 1.0F; + CSRAW float m_MaxWidth = 2.0F; + CSRAW float m_MinHeight = 1F; + CSRAW float m_MaxHeight = 2F; + CSRAW float m_NoiseSpread = 0.1F; + CSRAW float m_BendFactor = 0.1F; + CSRAW int m_RenderMode = 2; + CSRAW int m_UsePrototypeMesh = 0; + + CSRAW public GameObject prototype { get { return m_Prototype; } set { m_Prototype = value; } } + + CSRAW public Texture2D prototypeTexture { get { return m_PrototypeTexture; } set { m_PrototypeTexture = value; } } + + CSRAW public float minWidth { get { return m_MinWidth; } set { m_MinWidth = value; } } + + CSRAW public float maxWidth { get { return m_MaxWidth; } set { m_MaxWidth = value; } } + + CSRAW public float minHeight { get { return m_MinHeight; } set { m_MinHeight = value; } } + + CSRAW public float maxHeight { get { return m_MaxHeight; } set { m_MaxHeight = value; } } + + CSRAW public float noiseSpread { get { return m_NoiseSpread; } set { m_NoiseSpread = value; } } + + CSRAW public float bendFactor { get { return m_BendFactor; } set { m_BendFactor = value; } } + + CSRAW public Color healthyColor { get { return m_HealthyColor; } set { m_HealthyColor = value; } } + + CSRAW public Color dryColor { get { return m_DryColor; } set { m_DryColor = value; } } + + CSRAW public DetailRenderMode renderMode { get { return (DetailRenderMode)m_RenderMode; } set { m_RenderMode = (int)value; } } + + CSRAW public bool usePrototypeMesh { get { return m_UsePrototypeMesh != 0; } set { m_UsePrototypeMesh = value ? 1 : 0; } } +END + +CSRAW [StructLayout (LayoutKind.Sequential)] +CONDITIONAL ENABLE_TERRAIN +CLASS SplatPrototype + CSRAW Texture2D m_Texture; + CSRAW Texture2D m_NormalMap; + CSRAW Vector2 m_TileSize = new Vector2 (15,15); + CSRAW Vector2 m_TileOffset = new Vector2 (0, 0); + + CSRAW public Texture2D texture { get { return m_Texture; } set { m_Texture = value; } } + + CSRAW public Texture2D normalMap { get { return m_NormalMap; } set { m_NormalMap = value; } } + + CSRAW public Vector2 tileSize { get { return m_TileSize; } set { m_TileSize = value; } } + + CSRAW public Vector2 tileOffset { get { return m_TileOffset; } set { m_TileOffset = value; } } +END + + +CONDITIONAL ENABLE_TERRAIN +STRUCT TreeInstance + CSRAW Vector3 m_Position; + CSRAW float m_WidthScale; + CSRAW float m_HeightScale; + CSRAW Color32 m_Color; + CSRAW Color32 m_LightmapColor; + CSRAW int m_Index; + CSRAW float m_TemporaryDistance; + + public Vector3 position { get { return m_Position; } set { m_Position = value; } } + + public float widthScale { get { return m_WidthScale; } set { m_WidthScale = value; } } + + public float heightScale { get { return m_HeightScale; } set { m_HeightScale = value; } } + + public Color color { get { return m_Color; } set { m_Color = value; } } + + public Color lightmapColor { get { return m_LightmapColor; } set { m_LightmapColor = value; } } + + public int prototypeIndex { get { return m_Index; } set { m_Index = value; } } + + internal float temporaryDistance { get { return m_TemporaryDistance; } set { m_TemporaryDistance = value; } } +END + +CONDITIONAL ENABLE_TERRAIN +CLASS TerrainData : Object + CSRAW public TerrainData () + { + Internal_Create(this); + } + + CUSTOM internal void Internal_Create ([Writable]TerrainData terrainData) + { + TerrainData* td = NEW_OBJECT (TerrainData); + td->Reset(); + + //this is only for ensuring, that HeightMap initialized properly before someone uses TerrainData + if (td) + td->GetHeightmap().SetResolution(0); + + Scripting::ConnectScriptingWrapperToObject (terrainData.GetScriptingObject(), td); + td->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + } + + CUSTOM internal bool HasUser (GameObject user) { return self->HasUser (user); } + CUSTOM internal void AddUser (GameObject user) { self->AddUser (user); } + CUSTOM internal void RemoveUser (GameObject user) { self->RemoveUser (user); } + + // HEIGHTMAP INTERFACE + C++RAW #define GETHEIGHT (&(self->GetHeightmap())) + + // ============================================= + C++RAW #define GETDETAIL (&(self->GetDetailDatabase())) + + C++RAW #define GETTREEDATABASE (&(self->GetTreeDatabase())) + + // PhysicMaterial the terrain. + CONDITIONAL ENABLE_PHYSICS + CUSTOM_PROP PhysicMaterial physicMaterial { return Scripting::ScriptingWrapperFor(GETHEIGHT->GetPhysicMaterial ()); } { GETHEIGHT->SetPhysicMaterial (value); } + CUSTOM_PROP int heightmapWidth { return GETHEIGHT->GetWidth (); } + CUSTOM_PROP int heightmapHeight { return GETHEIGHT->GetHeight (); } + + CUSTOM_PROP int heightmapResolution { return GETHEIGHT->GetResolution (); } { GETHEIGHT->SetResolution (value); } + + CUSTOM_PROP Vector3 heightmapScale { return GETHEIGHT->GetScale (); } + + CUSTOM_PROP Vector3 size { return GETHEIGHT->GetSize (); } { GETHEIGHT->SetSize (value); } + + CUSTOM float GetHeight (int x, int y) { return GETHEIGHT->GetHeight (x,y); } + CUSTOM float GetInterpolatedHeight (float x, float y) { return GETHEIGHT->GetInterpolatedHeight (x,y); } + + CUSTOM public float[,] GetHeights (int xBase, int yBase, int width, int height) { + + if(xBase < 0 || yBase < 0 || xBase+width > GETHEIGHT->GetWidth() || yBase+height > GETHEIGHT->GetHeight ()) + { + Scripting::RaiseMonoException ("Trying to access out-of-bounds terrain height information."); + return SCRIPTING_NULL; + } + + ScriptingArrayPtr map = CreateScriptingArray2D<float> (MONO_COMMON.floatSingle, height, width); + GETHEIGHT->GetHeights (xBase, yBase, width, height, &Scripting::GetScriptingArrayElement<float>(map, 0)); + return map; + } + + CSRAW public void SetHeights (int xBase, int yBase, float[,] heights) { + if (heights == null) + { + throw new System.NullReferenceException (); + } + if (xBase+heights.GetLength(1) > heightmapWidth || xBase < 0 || yBase < 0 || yBase+heights.GetLength(0) > heightmapHeight) + { + throw new System.Exception (UnityString.Format ("X or Y base out of bounds. Setting up to {0}x{1} while map size is {2}x{3}", xBase+heights.GetLength(1), yBase+heights.GetLength(0), heightmapWidth, heightmapHeight)); + } + + Internal_SetHeights (xBase, yBase, heights.GetLength(1), heights.GetLength(0), heights); + } + + CUSTOM private void Internal_SetHeights (int xBase, int yBase, int width, int height, float[,] heights) + { + GETHEIGHT->SetHeights(xBase, yBase, width, height, &Scripting::GetScriptingArrayElement<float>(heights, 0), false); + GETTREEDATABASE->RecalculateTreePositions(); + } + + CUSTOM private void Internal_SetHeightsDelayLOD (int xBase, int yBase, int width, int height, float[,] heights) + { + GETHEIGHT->SetHeights(xBase, yBase, width, height, &Scripting::GetScriptingArrayElement<float>(heights, 0), true); + } + + CSRAW internal void SetHeightsDelayLOD (int xBase, int yBase, float[,] heights) + { + Internal_SetHeightsDelayLOD (xBase, yBase, heights.GetLength(1), heights.GetLength(0), heights); + } + + CUSTOM float GetSteepness (float x, float y) { return GETHEIGHT->GetSteepness (x,y); } + + CUSTOM Vector3 GetInterpolatedNormal (float x, float y) { return GETHEIGHT->GetInterpolatedNormal (x,y); } + + + CUSTOM internal int GetAdjustedSize (int size) { return GETHEIGHT->GetAdjustedSize (size); } + + C++RAW #undef GETHEIGHT + + CUSTOM_PROP float wavingGrassStrength { return GETDETAIL->GetWavingGrassStrength(); } { GETDETAIL->SetWavingGrassStrength (value); self->SetDirty(); } + CUSTOM_PROP float wavingGrassAmount { return GETDETAIL->GetWavingGrassAmount(); } { GETDETAIL-> SetWavingGrassAmount (value); self->SetDirty(); } + CUSTOM_PROP float wavingGrassSpeed { return GETDETAIL->GetWavingGrassSpeed(); } { GETDETAIL-> SetWavingGrassSpeed (value); self->SetDirty(); } + CUSTOM_PROP Color wavingGrassTint { return GETDETAIL->GetWavingGrassTint(); } { GETDETAIL-> SetWavingGrassTint (value); self->SetDirty(); } + + CUSTOM_PROP int detailWidth { return GETDETAIL->GetWidth (); } + + CUSTOM_PROP int detailHeight { return GETDETAIL->GetHeight (); } + + CUSTOM void SetDetailResolution (int detailResolution, int resolutionPerPatch) + { + GETDETAIL->SetDetailResolution(detailResolution, resolutionPerPatch); + } + + CUSTOM_PROP int detailResolution { return GETDETAIL->GetResolution (); } + + CUSTOM_PROP internal int detailResolutionPerPatch { return GETDETAIL->GetResolutionPerPatch (); } + + CUSTOM internal void ResetDirtyDetails () { GETDETAIL->ResetDirtyDetails (); } + + CUSTOM void RefreshPrototypes () + { + GETDETAIL->RefreshPrototypes (); + GETTREEDATABASE->RefreshPrototypes (); + } + + CUSTOM_PROP DetailPrototype[] detailPrototypes + { + return VectorToScriptingClassArray<DetailPrototype, MonoDetailPrototype> (GETDETAIL->GetDetailPrototypes(), MONO_COMMON.detailPrototype, DetailPrototypeToMono); + } + { + GETDETAIL->SetDetailPrototypes (ScriptingClassArrayToVector<DetailPrototype, MonoDetailPrototype> (value, DetailPrototypeToCpp)); + } + + CUSTOM int[] GetSupportedLayers (int xBase, int yBase, int totalWidth, int totalHeight) { + int size = GETDETAIL->GetSupportedLayers (xBase, yBase, totalWidth, totalHeight, NULL); // Get the count of layers + ScriptingArrayPtr arr = CreateScriptingArray<int>(MONO_COMMON.int_32, size); + GETDETAIL->GetSupportedLayers (xBase, yBase, totalWidth, totalHeight, &Scripting::GetScriptingArrayElement<int> (arr, 0)); + return arr; + } + + CUSTOM int[,] GetDetailLayer (int xBase, int yBase, int width, int height, int layer) { + ScriptingArrayPtr map = CreateScriptingArray2D<int> (MONO_COMMON.int_32, height, width); + GETDETAIL->GetLayer (xBase, yBase, width, height, layer, &Scripting::GetScriptingArrayElement<int> (map, 0)); + return map; + } + + CSRAW public void SetDetailLayer (int xBase, int yBase, int layer, int[,] details) { + Internal_SetDetailLayer (xBase, yBase, details.GetLength(1), details.GetLength(0), layer, details); + } + + CUSTOM private void Internal_SetDetailLayer (int xBase, int yBase, int totalWidth, int totalHeight, int detailIndex, int[,] data) + { + GETDETAIL->SetLayer (xBase, yBase, totalWidth, totalHeight, detailIndex, &Scripting::GetScriptingArrayElement<int> (data, 0)); + } + + CUSTOM_PROP TreeInstance[] treeInstances + { + return CreateScriptingArray(&GETTREEDATABASE->GetInstances()[0], GETTREEDATABASE->GetInstances().size(), MONO_COMMON.treeInstance); + } + { + Scripting::RaiseIfNull((ScriptingObjectPtr)value); + TreeInstance *first = &Scripting::GetScriptingArrayElement<TreeInstance> (value, 0); + GETTREEDATABASE->GetInstances().assign (first, first + GetScriptingArraySize(value)); + GETTREEDATABASE->UpdateTreeInstances(); + } + CUSTOM_PROP TreePrototype[] treePrototypes + { return VectorToScriptingClassArray<TreePrototype, MonoTreePrototype> (GETTREEDATABASE->GetTreePrototypes(), MONO_COMMON.treePrototype, TreePrototypeToMono); } + { GETTREEDATABASE->SetTreePrototypes (ScriptingClassArrayToVector<TreePrototype, MonoTreePrototype> (value, TreePrototypeToCpp)); } + + CUSTOM internal void RemoveTreePrototype (int index) + { + GETTREEDATABASE->RemoveTreePrototype (index); + } + + CUSTOM internal void RecalculateTreePositions () + { + GETTREEDATABASE->RecalculateTreePositions (); + } + + CUSTOM internal void RemoveDetailPrototype (int index) + { + GETDETAIL->RemoveDetailPrototype (index); + } + + C++RAW #undef GETDETAIL + + + // OLD SPLAT DATABASE + // ============================================= + C++RAW #define GETSPLAT (&(self->GetSplatDatabase())) + + CUSTOM_PROP int alphamapLayers { return GETSPLAT->GetDepth(); } + CUSTOM public float[,,] GetAlphamaps (int x, int y, int width, int height) { + ScriptingArrayPtr map = CreateScriptingArray3D<float> (MONO_COMMON.floatSingle, height, width, GETSPLAT->GetDepth ()); + GETSPLAT->GetAlphamaps (x, y, width, height, &Scripting::GetScriptingArrayElement<float>(map, 0)); + return map; + } + + CUSTOM_PROP int alphamapResolution { return GETSPLAT->GetAlphamapResolution(); } { return GETSPLAT->SetAlphamapResolution(value); } + CUSTOM_PROP int alphamapWidth { return GETSPLAT->GetAlphamapResolution(); } + CUSTOM_PROP int alphamapHeight { return GETSPLAT->GetAlphamapResolution(); } + CUSTOM_PROP int baseMapResolution { return GETSPLAT->GetBaseMapResolution(); } { return GETSPLAT->SetBaseMapResolution(value); } + + CSRAW public void SetAlphamaps (int x, int y, float[,,] map) + { + if (map.GetLength(2) != alphamapLayers) { + throw new System.Exception (UnityString.Format ("Float array size wrong (layers should be {0})", alphamapLayers)); + } + // TODO: crop the map or throw if outside, + + Internal_SetAlphamaps (x,y, map.GetLength(1), map.GetLength(0), map); + } + CUSTOM private void Internal_SetAlphamaps (int x, int y, int width, int height, float[,,] map) + { + GETSPLAT->SetAlphamaps (x, y, width, height, &Scripting::GetScriptingArrayElement<float>(map, 0)); + } + + CUSTOM internal void RecalculateBasemapIfDirty() + { + GETSPLAT->RecalculateBasemapIfDirty(); + } + + CUSTOM internal void SetBasemapDirty(bool dirty) + { + GETSPLAT->SetBasemapDirty(dirty); + } + + CUSTOM private Texture2D GetAlphamapTexture(int index) { + return Scripting::ScriptingWrapperFor (GETSPLAT->GetAlphaTexture(index)); + } + + CUSTOM_PROP private int alphamapTextureCount { + return GETSPLAT->GetAlphaTextureCount(); + } + + CSRAW internal Texture2D[] alphamapTextures + { + get { + Texture2D[] splatTextures = new Texture2D[alphamapTextureCount]; + for (int i=0;i<splatTextures.Length;i++) + splatTextures[i] = GetAlphamapTexture(i); + return splatTextures; + } + } + + CUSTOM_PROP SplatPrototype[] splatPrototypes + { return VectorToScriptingClassArray<SplatPrototype, MonoSplatPrototype> (GETSPLAT->GetSplatPrototypes(), MONO_COMMON.splatPrototype, SplatPrototypeToMono); } + { GETSPLAT->SetSplatPrototypes (ScriptingClassArrayToVector<SplatPrototype, MonoSplatPrototype> (value, SplatPrototypeToCpp)); } + C++RAW #undef GET + + CUSTOM internal bool HasTreeInstances () + { + return !self->GetTreeDatabase().GetInstances().empty(); + } + + CUSTOM internal void AddTree (out TreeInstance tree) + { + self->GetTreeDatabase().AddTree (*tree); + } + + CUSTOM internal int RemoveTrees (Vector2 position, float radius, int prototypeIndex) + { + return self->GetTreeDatabase().RemoveTrees (position, radius, prototypeIndex); + } +END + +CSRAW +} +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/ScriptBindings/Terrains.txt b/Runtime/Terrain/ScriptBindings/Terrains.txt new file mode 100644 index 0000000..4335595 --- /dev/null +++ b/Runtime/Terrain/ScriptBindings/Terrains.txt @@ -0,0 +1,675 @@ +C++RAW +#include "UnityPrefix.h" +#include "Runtime/Terrain/Heightmap.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Terrain/DetailDatabase.h" +#include "Runtime/Terrain/SplatDatabase.h" +#include "Runtime/Terrain/TerrainData.h" +#include "Runtime/Terrain/TerrainInstance.h" +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Terrain/TerrainRenderer.h" +#include "Runtime/Camera/Light.h" +#include "Runtime/Terrain/DetailRenderer.h" +#include "Runtime/Terrain/ImposterRenderTexture.h" +#include "Runtime/Terrain/TreeRenderer.h" +#include "Runtime/Terrain/Wind.h" +#include "Runtime/Terrain/Tree.h" +#include "Runtime/Scripting/GetComponent.h" +#include "Runtime/Scripting/Scripting.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingExportUtility.h" +#include "Runtime/Scripting/Backend/ScriptingInvocation.h" +#include "Runtime/Scripting/Backend/ScriptingTypeRegistry.h" +#include "Runtime/Interfaces/ITerrainManager.h" + +using namespace Unity; +using namespace std; + +CSRAW + +#if ENABLE_TERRAIN + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Collections; +using System.Collections.Generic; + +namespace UnityEngine +{ + +// List of changes done to the terrain for OnTerrainChanged +// OnTerrainChanged is called with a bitfield of these items telling it what was changed. +CSRAW +[Flags] +internal enum TerrainChangedFlags +{ + NoChange = 0, + Heightmap = 1, + TreeInstances = 2, + DelayedHeightmapUpdate = 4, + FlushEverythingImmediately = 8, + RemoveDirtyDetailsImmediately = 16, + WillBeDestroyed = 256, +} + +ENUM TerrainRenderFlags + heightmap = 1, + trees = 2, + details = 4, + all = heightmap | trees | details +END + +CSRAW +CONDITIONAL ENABLE_TERRAIN +[AddComponentMenu("")] +[ExecuteInEditMode] +CLASS Terrain : MonoBehaviour + CSRAW + [SerializeField] + private TerrainData m_TerrainData; + [SerializeField] + float m_TreeDistance = 5000.0F; + [SerializeField] + float m_TreeBillboardDistance = 50.0F; + [SerializeField] + float m_TreeCrossFadeLength = 5.0F; + [SerializeField] + int m_TreeMaximumFullLODCount = 50; + [SerializeField] + float m_DetailObjectDistance = 80.0F; + [SerializeField] + float m_DetailObjectDensity = 1.0f; + [SerializeField] + float m_HeightmapPixelError = 5.0F; + [SerializeField] + float m_SplatMapDistance = 1000.0F; + [SerializeField] + int m_HeightmapMaximumLOD = 0; + [SerializeField] + bool m_CastShadows = true; + [SerializeField] + int m_LightmapIndex = -1; + [SerializeField] + int m_LightmapSize = 1024; + [SerializeField] + bool m_DrawTreesAndFoliage = true; + [SerializeField] + Material m_MaterialTemplate; + + [System.NonSerialized] + IntPtr m_TerrainInstance; + + // Since the terrain object can be disabled on being loaded, there is + // no way to reliably initialize the TerrainInstance after loaded. + // So initialization is moved here. + private IntPtr InstanceObject + { + get + { + if (m_TerrainInstance == IntPtr.Zero) + { + m_TerrainInstance = Construct(); + Internal_SetTerrainData(m_TerrainInstance, m_TerrainData); + Internal_SetTreeDistance(m_TerrainInstance, m_TreeDistance); + Internal_SetTreeBillboardDistance(m_TerrainInstance, m_TreeBillboardDistance); + Internal_SetTreeCrossFadeLength(m_TerrainInstance, m_TreeCrossFadeLength); + Internal_SetTreeMaximumFullLODCount(m_TerrainInstance, m_TreeMaximumFullLODCount); + Internal_SetDetailObjectDistance(m_TerrainInstance, m_DetailObjectDistance); + Internal_SetDetailObjectDensity(m_TerrainInstance, m_DetailObjectDensity); + Internal_SetHeightmapPixelError(m_TerrainInstance, m_HeightmapPixelError); + Internal_SetBasemapDistance(m_TerrainInstance, m_SplatMapDistance); + Internal_SetHeightmapMaximumLOD(m_TerrainInstance, m_HeightmapMaximumLOD); + Internal_SetCastShadows(m_TerrainInstance, m_CastShadows); + Internal_SetLightmapIndex(m_TerrainInstance, m_LightmapIndex); + Internal_SetLightmapSize(m_TerrainInstance, m_LightmapSize); + Internal_SetDrawTreesAndFoliage(m_TerrainInstance, m_DrawTreesAndFoliage); + Internal_SetMaterialTemplate(m_TerrainInstance, m_MaterialTemplate); + } + return m_TerrainInstance; + } + set + { + m_TerrainInstance = value; + } + } + + private void OnDestroy() + { + OnDisable(); + + // Using InstanceObject potentially creates the object then immediately destroy it + Cleanup(m_TerrainInstance); + } + + CUSTOM private void Cleanup(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + UNITY_DELETE(ti, kMemTerrain); + } + + CSRAW public TerrainRenderFlags editorRenderFlags + { + get { return (TerrainRenderFlags)GetEditorRenderFlags(InstanceObject); } + set { SetEditorRenderFlags(InstanceObject, (int)value); } + } + CUSTOM private int GetEditorRenderFlags(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetEditorRenderFlags(); + } + CUSTOM private void SetEditorRenderFlags(IntPtr terrainInstance, int flags) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetEditorRenderFlags((TerrainInstance::RenderFlags)flags); + } + + CSRAW public TerrainData terrainData + { + get { m_TerrainData = Internal_GetTerrainData(InstanceObject); return m_TerrainData; } + set { m_TerrainData = value; Internal_SetTerrainData(InstanceObject, value); } + } + CUSTOM private TerrainData Internal_GetTerrainData(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return Scripting::ScriptingWrapperFor(ti->GetTerrainData()); + } + CUSTOM private void Internal_SetTerrainData(IntPtr terrainInstance, TerrainData value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetTerrainData(value); + } + + CSRAW public float treeDistance + { + get { m_TreeDistance = Internal_GetTreeDistance(InstanceObject); return m_TreeDistance; } + set { m_TreeDistance = value; Internal_SetTreeDistance(InstanceObject, value); } + } + CUSTOM private float Internal_GetTreeDistance(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetTreeDistance(); + } + CUSTOM private void Internal_SetTreeDistance(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetTreeDistance(value); + } + + CSRAW public float treeBillboardDistance + { + get { m_TreeBillboardDistance = Internal_GetTreeBillboardDistance(InstanceObject); return m_TreeBillboardDistance; } + set { m_TreeBillboardDistance = value; Internal_SetTreeBillboardDistance(InstanceObject, value); } + } + CUSTOM private float Internal_GetTreeBillboardDistance(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetTreeBillboardDistance(); + } + CUSTOM private void Internal_SetTreeBillboardDistance(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetTreeBillboardDistance(value); + } + + CSRAW public float treeCrossFadeLength + { + get { m_TreeCrossFadeLength = Internal_GetTreeCrossFadeLength(InstanceObject); return m_TreeCrossFadeLength; } + set { m_TreeCrossFadeLength = value; Internal_SetTreeCrossFadeLength(InstanceObject, value); } + } + CUSTOM private float Internal_GetTreeCrossFadeLength(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetTreeCrossFadeLength(); + } + CUSTOM private void Internal_SetTreeCrossFadeLength(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetTreeCrossFadeLength(value); + } + + CSRAW public int treeMaximumFullLODCount + { + get { m_TreeMaximumFullLODCount = Internal_GetTreeMaximumFullLODCount(InstanceObject); return m_TreeMaximumFullLODCount; } + set { m_TreeMaximumFullLODCount = value; Internal_SetTreeMaximumFullLODCount(InstanceObject, value); } + } + CUSTOM private int Internal_GetTreeMaximumFullLODCount(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetTreeMaximumFullLODCount(); + } + CUSTOM private void Internal_SetTreeMaximumFullLODCount(IntPtr terrainInstance, int value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetTreeMaximumFullLODCount(value); + } + + CSRAW public float detailObjectDistance + { + get { m_DetailObjectDistance = Internal_GetDetailObjectDistance(InstanceObject); return m_DetailObjectDistance; } + set { m_DetailObjectDistance = value; Internal_SetDetailObjectDistance(InstanceObject, value); } + } + CUSTOM private float Internal_GetDetailObjectDistance(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetDetailObjectDistance(); + } + CUSTOM private void Internal_SetDetailObjectDistance(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetDetailObjectDistance(value); + } + + CSRAW public float detailObjectDensity + { + get { m_DetailObjectDensity = Internal_GetDetailObjectDensity(InstanceObject); return m_DetailObjectDensity; } + set { m_DetailObjectDensity = value; Internal_SetDetailObjectDensity(InstanceObject, value); } + } + CUSTOM private float Internal_GetDetailObjectDensity(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetDetailObjectDensity(); + } + CUSTOM private void Internal_SetDetailObjectDensity(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetDetailObjectDensity(value); + } + + CSRAW public float heightmapPixelError + { + get { m_HeightmapPixelError = Internal_GetHeightmapPixelError(InstanceObject); return m_HeightmapPixelError; } + set { m_HeightmapPixelError = value; Internal_SetHeightmapPixelError(InstanceObject, value); } + } + CUSTOM private float Internal_GetHeightmapPixelError(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetHeightmapPixelError(); + } + CUSTOM private void Internal_SetHeightmapPixelError(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetHeightmapPixelError(value); + } + + CSRAW public int heightmapMaximumLOD + { + get { m_HeightmapMaximumLOD = Internal_GetHeightmapMaximumLOD(InstanceObject); return m_HeightmapMaximumLOD; } + set { m_HeightmapMaximumLOD = value; Internal_SetHeightmapMaximumLOD(InstanceObject, value); } + } + CUSTOM private int Internal_GetHeightmapMaximumLOD(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetHeightmapMaximumLOD(); + } + CUSTOM private void Internal_SetHeightmapMaximumLOD(IntPtr terrainInstance, int value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetHeightmapMaximumLOD(value); + } + + CSRAW public float basemapDistance + { + get { m_SplatMapDistance = Internal_GetBasemapDistance(InstanceObject); return m_SplatMapDistance; } + set { m_SplatMapDistance = value; Internal_SetBasemapDistance(InstanceObject, value); } + } + CUSTOM private float Internal_GetBasemapDistance(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetBasemapDistance(); + } + CUSTOM private void Internal_SetBasemapDistance(IntPtr terrainInstance, float value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetBasemapDistance(value); + } + + OBSOLETE error use basemapDistance + CSRAW public float splatmapDistance + { + get { return basemapDistance; } + set { basemapDistance = value; } + } + + CSRAW public int lightmapIndex + { + get { m_LightmapIndex = Internal_GetLightmapIndex(InstanceObject); return m_LightmapIndex; } + set { m_LightmapIndex = value; Internal_SetLightmapIndex(InstanceObject, value); } + } + CSRAW private void SetLightmapIndex(int value) + { + lightmapIndex = value; + } + CSRAW private void ShiftLightmapIndex(int offset) + { + lightmapIndex += offset; + } + CUSTOM private int Internal_GetLightmapIndex(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetLightmapIndex(); + } + CUSTOM private void Internal_SetLightmapIndex(IntPtr terrainInstance, int value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetLightmapIndex(value); + } + + CSRAW internal int lightmapSize + { + get { m_LightmapSize = Internal_GetLightmapSize(InstanceObject); return m_LightmapSize; } + set { m_LightmapSize = value; Internal_SetLightmapSize(InstanceObject, value); } + } + CUSTOM private int Internal_GetLightmapSize(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetLightmapSize(); + } + CUSTOM private void Internal_SetLightmapSize(IntPtr terrainInstance, int value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetLightmapSize(value); + } + + CSRAW public bool castShadows + { + get { m_CastShadows = Internal_GetCastShadows(InstanceObject); return m_CastShadows; } + set { m_CastShadows = value; Internal_SetCastShadows(InstanceObject, value); } + } + CUSTOM private bool Internal_GetCastShadows(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetCastShadows(); + } + CUSTOM private void Internal_SetCastShadows(IntPtr terrainInstance, bool value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetCastShadows(value); + } + + CSRAW public Material materialTemplate + { + get { m_MaterialTemplate = Internal_GetMaterialTemplate(InstanceObject); return m_MaterialTemplate; } + set { m_MaterialTemplate = value; Internal_SetMaterialTemplate(InstanceObject, value); } + } + CUSTOM private Material Internal_GetMaterialTemplate(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return Scripting::ScriptingWrapperFor(const_cast<Material*>(ti->GetMaterialTemplate())); + } + CUSTOM private void Internal_SetMaterialTemplate(IntPtr terrainInstance, Material value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetMaterialTemplate(value); + } + + CSRAW internal bool drawTreesAndFoliage + { + get { m_DrawTreesAndFoliage = Internal_GetDrawTreesAndFoliage(InstanceObject); return m_DrawTreesAndFoliage; } + set { m_DrawTreesAndFoliage = value; Internal_SetDrawTreesAndFoliage(InstanceObject, value); } + } + CUSTOM private bool Internal_GetDrawTreesAndFoliage(IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetDrawTreesAndFoliage(); + } + CUSTOM private void Internal_SetDrawTreesAndFoliage(IntPtr terrainInstance, bool value) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->SetDrawTreesAndFoliage(value); + } + + CSRAW public float SampleHeight(Vector3 worldPosition) + { + return Internal_SampleHeight(InstanceObject, worldPosition); + } + CUSTOM private float Internal_SampleHeight (IntPtr terrainInstance, Vector3 worldPosition) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->SampleHeight(worldPosition); + } + + CSRAW internal void ApplyDelayedHeightmapModification() + { + Internal_ApplyDelayedHeightmapModification(InstanceObject); + } + CUSTOM internal void Internal_ApplyDelayedHeightmapModification (IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->ApplyDelayedHeightmapModification(); + } + + CSRAW public void AddTreeInstance(TreeInstance instance) + { + Internal_AddTreeInstance(InstanceObject, instance); + } + CUSTOM private void Internal_AddTreeInstance (IntPtr terrainInstance, TreeInstance instance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->AddTreeInstance(instance); + } + + CSRAW public void SetNeighbors (Terrain left, Terrain top, Terrain right, Terrain bottom) + { + Internal_SetNeighbors(InstanceObject, + left != null ? left.InstanceObject : IntPtr.Zero, + top != null ? top.InstanceObject : IntPtr.Zero, + right != null ? right.InstanceObject : IntPtr.Zero, + bottom != null ? bottom.InstanceObject : IntPtr.Zero); + } + CUSTOM private void Internal_SetNeighbors (IntPtr terrainInstance, IntPtr left, IntPtr top, IntPtr right, IntPtr bottom) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + TerrainInstance* leftInstance = static_cast<TerrainInstance*>(left); + TerrainInstance* topInstance = static_cast<TerrainInstance*>(top); + TerrainInstance* rightInstance = static_cast<TerrainInstance*>(right); + TerrainInstance* bottomInstance = static_cast<TerrainInstance*>(bottom); + ti->SetNeighbors(leftInstance, topInstance, rightInstance, bottomInstance); + } + + CSRAW public Vector3 GetPosition () + { + return Internal_GetPosition(InstanceObject); + } + CUSTOM private Vector3 Internal_GetPosition (IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + return ti->GetPosition(); + } + + CSRAW public void Flush () + { + Internal_Flush(InstanceObject); + } + CUSTOM private void Internal_Flush (IntPtr terrainInstance) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->Flush(); + } + + CSRAW internal void RemoveTrees (Vector2 position, float radius, int prototypeIndex) + { + Internal_RemoveTrees(InstanceObject, position, radius, prototypeIndex); + } + CUSTOM private void Internal_RemoveTrees (IntPtr terrainInstance, Vector2 position, float radius, int prototypeIndex) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->RemoveTrees(position, radius, prototypeIndex); + } + + CSRAW private void OnTerrainChanged (TerrainChangedFlags flags) + { + Internal_OnTerrainChanged(InstanceObject, flags); + } + + CUSTOM private void Internal_OnTerrainChanged (IntPtr terrainInstance, TerrainChangedFlags flags) + { + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + Assert(ti != NULL); + ti->OnTerrainChanged(flags); + } + + CUSTOM private IntPtr Construct () + { + SET_ALLOC_OWNER(self->GetGameObjectPtr()); + return UNITY_NEW(TerrainInstance, kMemTerrain)(self->GetGameObjectPtr()); + } + + CSRAW + internal void OnEnable () + { + Internal_OnEnable(InstanceObject); + } + + CUSTOM private void Internal_OnEnable (IntPtr terrainInstance) + { + Assert(terrainInstance != NULL); + + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + ti->OnEnable(); + GetITerrainManager()->AddTerrainAndSetActive(ti); + } + + CSRAW + internal void OnDisable() + { + Internal_OnDisable(InstanceObject); + } + + CUSTOM private void Internal_OnDisable (IntPtr terrainInstance) + { + Assert(terrainInstance != NULL); + + TerrainInstance* ti = static_cast<TerrainInstance*>(terrainInstance); + GetITerrainManager()->RemoveTerrain(ti); + ti->OnDisable(); + ti->Flush(); + } + + C++RAW + ScriptingObjectPtr TerrainInstanceToMonoBehaviour(TerrainInstance* ti) + { + if (!ti) + return SCRIPTING_NULL; + + GameObject* go = ti->GetGameObject(); + Assert(go != NULL); + return ScriptingGetComponentOfType(*go, MONO_COMMON.terrain); + } + + CUSTOM_PROP static Terrain activeTerrain + { + TerrainInstance* ti = GetITerrainManager()->GetActiveTerrain(); + return TerrainInstanceToMonoBehaviour(ti); + } + + CUSTOM_PROP static Terrain[] activeTerrains + { + TerrainList tl = GetITerrainManager()->GetActiveTerrains(); + + ScriptingClassPtr terrainClass = MONO_COMMON.terrain; + ScriptingArrayPtr array = CreateScriptingArray<ScriptingObjectPtr>(terrainClass, tl.size()); + + int index = 0; + for (TerrainList::iterator i = tl.begin(); i != tl.end(); ++i, index++) + Scripting::SetScriptingArrayElement(array, index, TerrainInstanceToMonoBehaviour (*i)); + return array; + } + + CSRAW + public static GameObject CreateTerrainGameObject (TerrainData assignTerrain) + { + // Also create the renderer game object + #if ENABLE_PHYSICS + GameObject go = new GameObject("Terrain", typeof(Terrain), typeof(TerrainCollider)); + #else + GameObject go = new GameObject("Terrain", typeof(Terrain)); + #endif + go.isStatic = true; + Terrain terrain = go.GetComponent(typeof(Terrain)) as Terrain; + #if ENABLE_PHYSICS + TerrainCollider collider = go.GetComponent(typeof(TerrainCollider)) as TerrainCollider; + collider.terrainData = assignTerrain; + #endif + terrain.terrainData = assignTerrain; + + // The terrain already got an OnEnable, but the terrain data had not been set up correctly. + terrain.OnEnable (); + + return go; + } + + // This method is used internally by the engine to reconnect Terrain objects to TerrainData. + private static void ReconnectTerrainData() + { + List<Terrain> activeTerrains = new List<Terrain>(Terrain.activeTerrains); + foreach (Terrain terrain in activeTerrains) + { + // we could delete asset directly - remove it here (we are calling this function on StopAssetEditing + if (terrain.terrainData == null ) + { + terrain.OnDisable(); + continue; + } + + // Check if connection to m_TerrainData has been lost + if (!terrain.terrainData.HasUser(terrain.gameObject)) + { + // destroy and recreate data + terrain.OnDisable(); + terrain.OnEnable(); + } + } + } +END + +CONDITIONAL ENABLE_TERRAIN +CLASS Tree : Component + + AUTO_PTR_PROP ScriptableObject data GetTreeData SetTreeData + +END + +CSRAW +} +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/ScriptBindings/WindZoneBindings.txt b/Runtime/Terrain/ScriptBindings/WindZoneBindings.txt new file mode 100644 index 0000000..b9e98ea --- /dev/null +++ b/Runtime/Terrain/ScriptBindings/WindZoneBindings.txt @@ -0,0 +1,183 @@ +C++RAW +#include "UnityPrefix.h" +#include "Runtime/Terrain/Heightmap.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Terrain/DetailDatabase.h" +#include "Runtime/Terrain/SplatDatabase.h" +#include "Runtime/Terrain/TerrainData.h" +#include "Runtime/Terrain/TerrainInstance.h" +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Terrain/TerrainRenderer.h" +#include "Runtime/Camera/Light.h" +#include "Runtime/Terrain/DetailRenderer.h" +#include "Runtime/Terrain/ImposterRenderTexture.h" +#include "Runtime/Terrain/TreeRenderer.h" +#include "Runtime/Terrain/Wind.h" +#include "Runtime/Terrain/Tree.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingExportUtility.h" + +using namespace Unity; +using namespace std; + +CSRAW + +#if ENABLE_TERRAIN + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Collections; +using System.Collections.Generic; + +namespace UnityEngine +{ + +/// Modes a Wind Zone can have, either Spherical or Directional +/// You can have more than one Spherical Wind Zone in a scene, but it does not make much +/// sense to have more than one Directional Wind Zone in your scene as it affects +/// the whole scene. This Wind Zone Mode is used by the WindZone.mode member. +CONDITIONAL ENABLE_TERRAIN +ENUM internal WindZoneMode + /// Wind zone only has an effect inside the radius, and has a falloff from the center towards the edge. + CONVERTEXAMPLE + BEGIN EX + // Creates a Directional Wind Zone that blows wind up. + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Directional; + // transform.rotation = Quaternion.LookRotation(Vector3.up); + } + END EX + /// + Directional = 0, + /// Wind zone affects the entire scene in one direction. + CONVERTEXAMPLE + BEGIN EX + // Creates a Spherical Wind Zone. + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Spherical; + } + END EX + /// + Spherical = 1 +END + +/// Wind Zones add realism to the trees you create by making them wave their branches and leaves as if blown by the wind. +/// +/// __Note:__ This only works with trees created by the tree creator. +CSRAW +CONDITIONAL ENABLE_TERRAIN +CLASS internal WindZone : Component + + /// Defines the type of wind zone to be used (Spherical or Directional). + CONVERTEXAMPLE + BEGIN EX + // Creates a Directional Wind Zone. + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Directional; + } + END EX + /// + AUTO_PROP WindZoneMode mode GetMode SetMode + + /// Radius of the Spherical Wind Zone (only active if the WindZoneMode is set to Spherical). + CONVERTEXAMPLE + BEGIN EX + // Creates a Spherical Wind Zone and sets its radius to 10. + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Spherical; + // wind.radius = 10; + } + END EX + /// + AUTO_PROP float radius GetRadius SetRadius + + /// The primary wind force. + /// It produces a softly changing wind Pressure. + CONVERTEXAMPLE + BEGIN EX + // Creates a wind zone with the effect of a helicopter passing by + // Just place this into an empty game object and move it over a tree + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Spherical; + // wind.radius = 10.0; + // wind.windMain = 3.0; + // wind.windTurbulence = 0.5; + // wind.windPulseMagnitude = 2.0; + // wind.windPulseFrequency = 0.01; + } + END EX + /// + AUTO_PROP float windMain GetWindMain SetWindMain + + /// The turbulence wind force. + /// Produces a rapidly changing wind pressure. + CONVERTEXAMPLE + BEGIN EX + // Creates a wind zone to produce a softly changing general wind + // Just place this into an empty game object + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Directional; + // wind.windMain = 0.70; + // wind.windTurbulence = 0.1; + // wind.windPulseMagnitude = 2.0; + // wind.windPulseFrequency = 0.25; + } + END EX + /// + AUTO_PROP float windTurbulence GetWindTurbulence SetWindTurbulence + + /// Defines ow much the wind changes over time. + CONVERTEXAMPLE + BEGIN EX + // Creates a wind zone with the effect of a helicopter passing by + // Just place this into an empty game object and move it over a tree + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Spherical; + // wind.radius = 10.0; + // wind.windMain = 3.0; + // wind.windTurbulence = 0.5; + // wind.windPulseMagnitude = 2.0; + // wind.windPulseFrequency = 0.01; + } + END EX + /// + AUTO_PROP float windPulseMagnitude GetWindPulseMagnitude SetWindPulseMagnitude + + /// Defines the frequency of the wind changes. + CONVERTEXAMPLE + BEGIN EX + // Creates a wind zone to produce a softly changing general wind + // Just place this into an empty game object + + function Start() { + // var wind : WindZone = gameObject.AddComponent(WindZone); + // wind.mode = WindZoneMode.Directional; + // wind.windMain = 0.70; + // wind.windTurbulence = 0.1; + // wind.windPulseMagnitude = 2.0; + // wind.windPulseFrequency = 0.25; + } + END EX + /// + AUTO_PROP float windPulseFrequency GetWindPulseFrequency SetWindPulseFrequency +END + +CSRAW +} +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/SplatDatabase.cpp b/Runtime/Terrain/SplatDatabase.cpp new file mode 100644 index 0000000..41cdf7b --- /dev/null +++ b/Runtime/Terrain/SplatDatabase.cpp @@ -0,0 +1,600 @@ +#include "UnityPrefix.h" +#include "SplatDatabase.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Graphics/Texture2D.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Graphics/Image.h" +#include "TerrainData.h" +#include "Runtime/Scripting/Scripting.h" + +#if UNITY_EDITOR +#include "Editor/Src/AssetPipeline/AssetImporter.h" +#endif + +#define PRINT_BASEMAP_TIME 0 + +#if PRINT_BASEMAP_TIME +double GetTimeSinceStartup(); +#endif + + +using namespace std; + +SplatPrototype::SplatPrototype () +: tileSize(15, 15), + tileOffset(0, 0) +{ + +} + +SplatDatabase::SplatDatabase (TerrainData *owner) +: m_BaseMap(NULL) +, m_AlphamapResolution(512) +, m_BaseMapResolution(512) +, m_BaseMapDirty (true) +{ + m_TerrainData = owner; +} + +SplatDatabase::~SplatDatabase() +{ + if( m_BaseMap ) + DestroySingleObject( m_BaseMap ); +} + +void SplatDatabase::UploadBasemap() +{ + if (!m_BaseMap->IsInstanceIDCreated()) + { + Object::AllocateAndAssignInstanceID(m_BaseMap); + m_BaseMap->SetHideFlags (Object::kHideAndDontSave); + m_BaseMap->SetWrapMode( kTexWrapClamp ); + } + m_BaseMap->AwakeFromLoad(kDefaultAwakeFromLoad); +} + + +// Typical time spent in recalculate base maps (core duo w/ gcc): +// 6 splats, basemap size 2048: +// 0.53 seconds (0.072 getting the mips, 0.031 getting alpha map, 0.268 blending, 0.16 uploading texture) +// 6 splats, basemap size 1024: +// 0.21 seconds (0.072 getting the mips, 0.031 getting alpha map, 0.067 blending, 0.04 uploading texture) +// +// Here we operate on ColorRGBA32 colors. The first quick implementation used ColorRGBAf, +// that was about 40% slower. + +void SplatDatabase::RecalculateBasemap(bool allowUpload) +{ + // + // create/resize base map texture + + int basemapSize = m_BaseMapResolution; + if( !m_BaseMap ) + { + m_BaseMap = NEW_OBJECT_FULL (Texture2D, kCreateObjectFromNonMainThread); + m_BaseMap->Reset(); + + // ok, just from performance standpoint we don't want to upload texture here + // or get an assert from uninited texture, so, let's cheat + m_BaseMap->HackSetAwakeDidLoadThreadedWasCalled(); + + // uncomment this for proper handling (but assert will fire in current impl) + //m_BaseMap->AwakeFromLoad(kDidLoadThreaded); + + m_BaseMap->InitTexture( basemapSize, basemapSize, kTexFormatRGBA32, Texture2D::kMipmapMask | Texture2D::kThreadedInitialize, 1 ); + + // uncomment this for proper handling (but you will upload texture twice in current impl) + //m_BaseMap->AwakeFromLoad(kDidLoadThreaded); + } + else + { + if( m_BaseMap->GetDataWidth() != basemapSize || m_BaseMap->GetDataHeight() != basemapSize || m_BaseMap->GetTextureFormat() != kTexFormatRGBA32 || !m_BaseMap->HasMipMap() ) + m_BaseMap->ResizeWithFormat (basemapSize, basemapSize, kTexFormatRGBA32, Texture2D::kMipmapMask); + } + + Vector3f terrainSize = m_TerrainData->GetHeightmap().GetSize(); + + #if PRINT_BASEMAP_TIME + double t0 = GetTimeSinceStartup(); + #endif + + #define SPLAT_FIX_BITS 16 // use 16.16 fixed point + + // Note to optimizers: separating this struct into hot (x,y,yoffset) & cold (the rest) data + // actually made it slower (about 20%, core duo w/ gcc), due to more housekeeping in the + // innermost loop. + struct SplatData + { + int xmask; // 16.16 fixed point + int ymask; // 16.16 fixed point + int dx; // 16.16 fixed point + int dy; // 16.16 fixed point + int x; // 16.16 fixed point + int y; // 16.16 fixed point + int xoffset;// 16.16 fixed point + int yoffset; + int width; + ColorRGBA32* mip; + }; + + + // + // Get data needed from the splat textures. + // This gets the optimal mip level in RGBA32 colors, and computes step sizes etc. + + int splatCount = m_Splats.size(); + + SplatData* splatData = new SplatData[splatCount]; + + // Set up sRGB state for splat maps + bool sRGBEncountered = false; + for( int i = 0; i < splatCount; ++i ) + { + // figure out which mip level of the splat map to use + Texture2D* splatTex = dynamic_pptr_cast<Texture2D*> (InstanceIDToObjectThreadSafe(m_Splats[i].texture.GetInstanceID())); + if( !splatTex ) + { + // if no splat texture, fill in a dummy one white pixel + ErrorStringObject( Format ("Terrain splat %d is null.", i), m_TerrainData ); + splatData[i].mip = new ColorRGBA32[1]; + splatData[i].mip[0] = 0xFFFFFFFF; + splatData[i].width = 1; + splatData[i].xmask = 0; + splatData[i].ymask = 0; + splatData[i].dx = 0; + splatData[i].dy = 0; + splatData[i].x = 0; + splatData[i].y = 0; + continue; + } + + if (splatTex->GetStoredColorSpace () != kTexColorSpaceLinear) + sRGBEncountered = true; + + #if !UNITY_EDITOR + AssertIf(!splatTex->GetIsReadable()); + #endif + int splatWidth = splatTex->GetDataWidth(); + int splatHeight = splatTex->GetDataHeight(); + float tilesX = terrainSize.x / m_Splats[i].tileSize.x; + float tilesY = terrainSize.z / m_Splats[i].tileSize.y; + int splatBaseWidth = std::max( (int)(basemapSize / tilesX), 1 ); + int splatBaseHeight = std::max( (int)(basemapSize / tilesY), 1 ); + float areaRatio = (splatWidth * splatHeight) / (splatBaseWidth * splatBaseHeight); + + const float tileOffsetX = m_Splats[i].tileOffset.x / terrainSize.x * tilesX; + const float tileOffsetY = m_Splats[i].tileOffset.y / terrainSize.z * tilesY; + + int mipLevel = int(0.5f * Log2( areaRatio )); + mipLevel = clamp( mipLevel, 0, splatTex->CountDataMipmaps() - 1); + + // get the pixels of this mip level + int minSplatSize = GetMinimumTextureMipSizeForFormat( splatTex->GetTextureFormat() ); + int width = std::max( splatWidth >> mipLevel, minSplatSize ); + int height = std::max( splatHeight >> mipLevel, minSplatSize ); + splatData[i].mip = new ColorRGBA32[width * height]; + if( !splatTex->GetPixels32( mipLevel, splatData[i].mip ) ) + { + memset( splatData[i].mip, 0, width*height*sizeof(ColorRGBA32) ); + ErrorStringObject( "Failed to get pixels of splat texture", m_TerrainData ); + } + + // compute step sizes for this splat + splatData[i].width = width; + splatData[i].xmask = (width << SPLAT_FIX_BITS) - 1; + splatData[i].ymask = (height << SPLAT_FIX_BITS) - 1; + float dx = tilesX * width / basemapSize; + splatData[i].dx = (int)( dx * (1<<SPLAT_FIX_BITS) ); + float dy = tilesY * height / basemapSize; + splatData[i].dy = (int)( dy * (1<<SPLAT_FIX_BITS) ); + splatData[i].xoffset = (int)(tileOffsetX * width * (1<<SPLAT_FIX_BITS)); + int yoffset = (int)(tileOffsetY * height * (1<<SPLAT_FIX_BITS)); + splatData[i].x = 0; + splatData[i].y = yoffset + (splatData[i].dy / 2) & splatData[i].ymask; // start at mid-pixel + } + + if (sRGBEncountered) + m_BaseMap->SetStoredColorSpaceNoDirtyNoApply (kTexColorSpaceSRGB); + else + m_BaseMap->SetStoredColorSpaceNoDirtyNoApply (kTexColorSpaceLinear); + + #if PRINT_BASEMAP_TIME + double t1 = GetTimeSinceStartup(); + #endif + + // + // get alpha map + + dynamic_array<UInt8> alphamap; + GetAlphamaps( alphamap ); + + #if PRINT_BASEMAP_TIME + double t2 = GetTimeSinceStartup(); + #endif + + // + // blend splats directly into texture data + + AssertIf( m_BaseMap->GetTextureFormat() != kTexFormatRGBA32 ); + ColorRGBA32* pix = reinterpret_cast<ColorRGBA32*>( m_BaseMap->GetRawImageData() ); + + int alphaFixStep = (m_AlphamapResolution << SPLAT_FIX_BITS) / basemapSize; // 16.16 fixed point + int alphaFixY = 0; + int pixIndex = 0; + for( int y = 0; y < basemapSize; ++y, alphaFixY += alphaFixStep ) + { + int alphaY = (alphaFixY >> SPLAT_FIX_BITS); + int alphaYIndex = alphaY * m_AlphamapResolution * splatCount; + + int alphaFixX = 0; + for( int s = 0; s < splatCount; ++s ) { + splatData[s].x = (splatData[s].dx / 2 + splatData[s].xoffset) & splatData[s].xmask; + splatData[s].yoffset = (splatData[s].y >> SPLAT_FIX_BITS) * splatData[s].width; + } + for( int x = 0; x < basemapSize; ++x, alphaFixX += alphaFixStep, ++pixIndex ) + { + int alphaX = (alphaFixX >> SPLAT_FIX_BITS); + int alphaXIndex = alphaX * splatCount; + + // Per-pixel loop: accumulate splat textures into final color. + // No need for pixel clipping since alphamap weights are always normalized. + UInt32 c = 0; + for( int s = 0; s < splatCount; ++s ) + { + SplatData& splat = splatData[s]; + ColorRGBA32 splatCol = splat.mip[ splat.yoffset + (splat.x >> SPLAT_FIX_BITS) ]; + + // Note for optimizers: trying to get rid of multiplies in color*byte by using + // a 256x256 lookup table is about 2x slower (core duo w/ gcc). + + // We always have to set alpha to zero. Using color * byte and then setting + // alpha to zero does not get optimized away by gcc, so use directly changed code + // from color * byte. + int scale = alphamap[alphaYIndex + alphaXIndex + s]; + const UInt32& u = reinterpret_cast<const UInt32&> (splatCol); + #if UNITY_LITTLE_ENDIAN + UInt32 lsb = (((u & 0x00ff00ff) * scale) >> 8) & 0x00ff00ff; + UInt32 msb = (((u & 0xff00ff00) >> 8) * scale) & 0x0000ff00; + #else + UInt32 lsb = (((u & 0x00ff00ff) * scale) >> 8) & 0x00ff0000; + UInt32 msb = (((u & 0xff00ff00) >> 8) * scale) & 0xff00ff00; + #endif + c += lsb | msb; + + splat.x = (splat.x + splat.dx) & splat.xmask; // next splat texel + } + pix[pixIndex] = ColorRGBA32(c); + + } + for( int s = 0; s < splatCount; ++s ) + splatData[s].y = (splatData[s].y + splatData[s].dy) & splatData[s].ymask; + } + + if (splatCount == 0) + { + memset(pix, 0xFFFFFFFF, basemapSize * basemapSize * GetBytesFromTextureFormat(m_BaseMap->GetTextureFormat())); + } + + #if PRINT_BASEMAP_TIME + double t3 = GetTimeSinceStartup(); + #endif + + m_BaseMap->RebuildMipMap(); + + // Upload base map texture + + if(allowUpload) + UploadBasemap(); + + // Cleanup + for( int i = 0; i < splatCount; ++i ) + delete[] splatData[i].mip; + delete[] splatData; + + #if PRINT_BASEMAP_TIME + double t4 = GetTimeSinceStartup(); + printf_console( "basemap time calc: %.2f (%.3f mips, %.3f alpha, %.3f blend, %.2f upload)\n", (t4-t0), (t1-t0), (t2-t1), (t3-t2), (t4-t3) ); + #endif + m_BaseMapDirty = false; +} + + +void SplatDatabase::GetAlphamaps (int xBase, int yBase, int width, int height, float* buffer) +{ + int layers = GetDepth(); + + ColorRGBAf *tempBuffer; + ALLOC_TEMP(tempBuffer, ColorRGBAf, width * height); + + + for (int m=0;m<m_AlphaTextures.size();m++) + { + int componentCount = min(layers - m * 4, 4); + + Texture2D* texture = m_AlphaTextures[m]; + if (texture) { + texture->GetPixels (xBase, yBase, width, height, 0, tempBuffer); + } else { + ErrorStringObject (Format ("splatdatabase alphamap %d is null", m), m_TerrainData); + memset (tempBuffer, 0, width * height * sizeof(ColorRGBAf)); + } + + for (int y=0;y<height;y++) + { + for (int x=0;x<width;x++) + { + float *pixel = reinterpret_cast<float*> (&tempBuffer[y*width+x]); + for (int a=0;a<componentCount;a++) { + int layer = m * 4+a; + buffer[y * width * layers + x * layers + layer] = pixel[a]; + } + } + } + } +} + +void SplatDatabase::GetAlphamaps( dynamic_array<UInt8>& buffer ) +{ + // resync the m_AlphamapResolution with textures in m_AlphaTextures array + for (int m = 0; m < m_AlphaTextures.size(); ++m) + { + Texture2D* texture = dynamic_pptr_cast<Texture2D*>(InstanceIDToObjectThreadSafe(m_AlphaTextures[m].GetInstanceID())); + if (texture == NULL) + { + ErrorStringObject(Format("splatdatabase alphamap %d is null", m), m_TerrainData); + continue; + } + + if (texture->GetDataWidth() != m_AlphamapResolution) + { + if (m == 0) + { + WarningStringObject(Format("splatdatabase alphamap %d texture size doesn't match alphamap resolution setting: please resave the terrain asset.", m), m_TerrainData); + m_AlphamapResolution = texture->GetDataWidth(); + } + else + { + ErrorStringObject(Format("splatdatabase alphamap %d texture size doesn't match to other alphamap textures.", m), m_TerrainData); + } + } + } + + int size = m_AlphamapResolution; + int pixelCount = size * size; + int layers = GetDepth(); + + buffer.resize_uninitialized( size * size * layers ); + + ColorRGBA32 *tempBuffer; + ALLOC_TEMP(tempBuffer, ColorRGBA32, size * size); + + for( int m=0;m<m_AlphaTextures.size();m++ ) + { + int componentCount = min(layers - m * 4, 4); + + Texture2D* texture = dynamic_pptr_cast<Texture2D*>(InstanceIDToObjectThreadSafe(m_AlphaTextures[m].GetInstanceID())); + if (texture != NULL && texture->GetDataWidth() == size && texture->GetDataHeight() == size) + { + texture->GetPixels32( 0, tempBuffer ); + } + else + { + ErrorStringObject (Format ("splatdatabase alphamap %d is invalid", m), m_TerrainData); + memset (tempBuffer, 0, pixelCount * sizeof(ColorRGBA32)); + } + + int bufferIndex = m * 4; + for( int i = 0; i < pixelCount; ++i ) + { + UInt8* pixel = reinterpret_cast<UInt8*>( &tempBuffer[i] ); + for( int a = 0; a < componentCount; a++ ) + { + buffer[bufferIndex + a] = pixel[a]; + } + bufferIndex += layers; + } + } +} + + +/// Assign back the alpha map in the given area +void SplatDatabase::SetAlphamaps (int xBase, int yBase, int width, int height, float* buffer) +{ + int layers = GetDepth (); + + ColorRGBAf *tempBuffer; + ALLOC_TEMP(tempBuffer, ColorRGBAf, width * height); + int alphamaps = m_AlphaTextures.size(); + for (int m=0; m < alphamaps; m++) + { + memset (tempBuffer, 0, width * height* sizeof (ColorRGBAf)); + int componentCount = min(layers - m * 4, 4); + + for (int y=0;y<height;y++) + { + for (int x=0;x<width;x++) + { + float *pixel = reinterpret_cast<float*> (&tempBuffer[y*width+x]); + for (int a=0; a<componentCount; a++) { + int layer = m * 4 + a; + pixel[a] = buffer[y * width * layers + x * layers + layer]; + + } + } + } + Texture2D* texture = m_AlphaTextures[m]; + if (!texture) { + ErrorStringObject (Format ("splatdatabase alphamap %d is null", m), m_TerrainData); + continue; + } + + texture->SetPixels(xBase, yBase, width, height, width * height, tempBuffer, 0 ); + texture->UpdateImageData(); + } + m_BaseMapDirty = true; +} + +static void ClearAlphaMap (Texture2D * map, const ColorRGBAf &color) +{ + ImageReference image; + if( !map->GetWriteImageReference(&image, 0, 0) ) + { + ErrorString("Unable to retrieve image reference"); + return; + } + + ColorRGBA32 tempColor (color); + ColorRGBA32 colorARGB (tempColor.a, tempColor.r, tempColor.g, tempColor.b); + + int texWidth = image.GetWidth(); + int texHeight = image.GetHeight(); + for( int iy = 0; iy < texHeight; ++iy ) + { + ColorRGBA32* pixel = (ColorRGBA32*)image.GetRowPtr(iy); + for( int ix = 0; ix < texWidth; ++ix ) + { + *pixel = colorARGB; + ++pixel; + } + } + map->UpdateImageData(); +} + + +Texture2D *SplatDatabase::AllocateAlphamap (const ColorRGBAf &color) +{ + Texture2D* map = CreateObjectFromCode<Texture2D>(kDefaultAwakeFromLoad); + map->ResizeWithFormat(m_AlphamapResolution, m_AlphamapResolution, kTexFormatARGB32, Texture2D::kMipmapMask); + map->SetWrapMode (kTexWrapClamp); + + ClearAlphaMap(map, color); + + map->SetName(Format ("SplatAlpha %u", (int)m_AlphaTextures.size()).c_str ()); + #if UNITY_EDITOR + if (m_TerrainData->IsPersistent ()) + AddAssetToSameFile (*map, *m_TerrainData); + #endif + return map; +} + +void SplatDatabase::AwakeFromLoad (AwakeFromLoadMode mode) +{ + if ((mode & kDidLoadThreaded) == 0) + { + // Ensure that the alpha map textures are allocated properly. + // When removing some splat texture, that can cause an alpha map to be destroyed. + // If later the removal is undone, we need to recreate the alpha map texture again. + int splatCount = m_Splats.size(); + int required = (splatCount / 4) + ((splatCount % 4) != 0 ? 1 : 0); + AssertIf( m_AlphaTextures.size() != required ); + for( int i = 0; i < required; ++i ) + { + Texture2D* tex = m_AlphaTextures[i]; + if( tex == NULL ) + { + ColorRGBAf color = ColorRGBAf (0,0,0,0); + if( i == 0 ) + color.r = 1.0f; + m_AlphaTextures[i] = AllocateAlphamap (color); + } + } + + m_BaseMapDirty = true; + } +} + + +void SplatDatabase::Init (int alphamapResolution, int basemapResolution) +{ + m_AlphamapResolution = alphamapResolution; + m_BaseMapResolution = basemapResolution; +} + +void SplatDatabase::SetAlphamapResolution (int res) +{ + m_AlphamapResolution = clamp( res, 16, 2048 ); + for (int i=0;i<m_AlphaTextures.size();i++) + { + Texture2D* map = m_AlphaTextures[i]; + if (map) + { + map->ResizeWithFormat(m_AlphamapResolution, m_AlphamapResolution, kTexFormatARGB32, Texture2D::kMipmapMask); + ClearAlphaMap(map, i == 0 ? ColorRGBAf (1, 0, 0, 0) : ColorRGBAf (0, 0, 0, 0)); + } + } + RecalculateBasemap(true); +} + +void SplatDatabase::SetBaseMapResolution( int res ) +{ + m_BaseMapResolution = clamp( res, 16, 2048 ); + RecalculateBasemap(true); +} + +bool SplatDatabase::RecalculateBasemapIfDirty() +{ + if (m_BaseMapDirty) + { + RecalculateBasemap(true); + return true; + } + return false; +} + +void SplatDatabase::SetSplatPrototypes (const vector<SplatPrototype> &splats ) +{ + // TODO: TEST for adding & removing + // TODO: renormalize & optionally ditch an alphatexture when removing one + // Do we need another one + int required = (splats.size() / 4) + ((splats.size() % 4) != 0 ? 1 : 0); + if (m_AlphaTextures.size() < required) + { + for (int i = m_AlphaTextures.size(); i < required; i++) + { + ColorRGBAf color = ColorRGBAf (0,0,0,0); + if (m_AlphaTextures.empty ()) + color.r = 1; + m_AlphaTextures.push_back (AllocateAlphamap (color)); + } + } + else if ( m_AlphaTextures.size() > required) + { + for (int i = required; i < m_AlphaTextures.size(); i++) + { + DestroySingleObject(m_AlphaTextures[i]); + } + m_AlphaTextures.resize (required); + } + m_Splats = splats; + RecalculateBasemap(true); + m_TerrainData->SetDirty(); +} + +Texture2D * SplatDatabase::GetAlphaTexture (int index) +{ + return m_AlphaTextures[index]; +} + +Texture2D* SplatDatabase::GetBasemap() +{ + return m_BaseMap; +} + + +void SplatPrototypeToMono (const SplatPrototype &src, MonoSplatPrototype &dest) { + dest.texture = Scripting::ScriptingWrapperFor (src.texture); + dest.normalMap = Scripting::ScriptingWrapperFor (src.normalMap); + dest.tileSize = src.tileSize; + dest.tileOffset = src.tileOffset; +} +void SplatPrototypeToCpp (MonoSplatPrototype &src, SplatPrototype &dest) { + dest.texture = ScriptingObjectToObject<Texture2D> (src.texture); + dest.normalMap = ScriptingObjectToObject<Texture2D> (src.normalMap); + dest.tileSize = src.tileSize; + dest.tileOffset = src.tileOffset; +} + + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/SplatDatabase.h b/Runtime/Terrain/SplatDatabase.h new file mode 100644 index 0000000..d96d9f2 --- /dev/null +++ b/Runtime/Terrain/SplatDatabase.h @@ -0,0 +1,111 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Math/Vector2.h" +#include "Heightmap.h" +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/BaseClasses/BaseObject.h" + +using std::vector; +class Texture2D; +class ColorRGBAf; +class TerrainData; + +struct SplatPrototype +{ + DECLARE_SERIALIZE (SplatPrototype) + PPtr<Texture2D> texture; + PPtr<Texture2D> normalMap; + Vector2f tileSize; + Vector2f tileOffset; + + SplatPrototype (); +}; + +template<class TransferFunc> +void SplatPrototype::Transfer (TransferFunc& transfer) +{ + TRANSFER (texture); + TRANSFER (normalMap); + TRANSFER (tileSize); + TRANSFER (tileOffset); +} + +class SplatDatabase { +public: + DECLARE_SERIALIZE (SplatDatabase) + + SplatDatabase (TerrainData *owner); + ~SplatDatabase(); + + void AwakeFromLoad (AwakeFromLoadMode mode); + + void Init (int splatResolution, int basemapResolution); + void GetSplatTextures (vector<Texture2D*> *dest); + + int GetAlphamapResolution () { return m_AlphamapResolution; } + int GetBaseMapResolution () { return m_BaseMapResolution; } + int GetDepth () const {return m_Splats.size (); } + void SetAlphamapResolution (int res); + void SetBaseMapResolution (int res); + + // Extract a copy of the alpha map in the given area + void GetAlphamaps (int xBase, int yBase, int width, int height, float* buffer); + // Set alpha map in the given area + void SetAlphamaps (int xBase, int yBase, int width, int height, float* buffer); + // Extract whole alpha map, as byte weights + void GetAlphamaps (dynamic_array<UInt8>& buffer); + + // NOT IMPLEMENTED void SetResolution (int width, int height); + + + Texture2D *GetAlphaTexture (int index); + int GetAlphaTextureCount () { return m_AlphaTextures.size(); } + + const vector<SplatPrototype> &GetSplatPrototypes () const { return m_Splats; } + void SetSplatPrototypes (const vector<SplatPrototype> &splats ); + + Texture2D* GetBasemap(); + void UploadBasemap(); + bool RecalculateBasemapIfDirty(); + void RecalculateBasemap(bool allowUpload); + void SetBasemapDirty(bool dirty) { m_BaseMapDirty = dirty; } + +private: + + Texture2D *AllocateAlphamap (const ColorRGBAf &color); + + vector<SplatPrototype> m_Splats; + vector<PPtr<Texture2D> > m_AlphaTextures; + Texture2D* m_BaseMap; + TerrainData* m_TerrainData; + int m_AlphamapResolution; + int m_BaseMapResolution; + bool m_BaseMapDirty; +}; + +template<class TransferFunc> +void SplatDatabase::Transfer (TransferFunc& transfer) +{ + TRANSFER (m_Splats); + TRANSFER (m_AlphaTextures); + TRANSFER (m_AlphamapResolution); + TRANSFER (m_BaseMapResolution); +} + + +struct MonoSplatPrototype +{ + ScriptingObjectPtr texture; + ScriptingObjectPtr normalMap; + Vector2f tileSize; + Vector2f tileOffset; +}; + + +void SplatPrototypeToMono (const SplatPrototype &src, MonoSplatPrototype &dest); +void SplatPrototypeToCpp (MonoSplatPrototype &src, SplatPrototype &dest); + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/SplatMaterials.cpp b/Runtime/Terrain/SplatMaterials.cpp new file mode 100644 index 0000000..3a3acb7 --- /dev/null +++ b/Runtime/Terrain/SplatMaterials.cpp @@ -0,0 +1,228 @@ +#include "UnityPrefix.h" +#include "SplatMaterials.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Shaders/ShaderNameRegistry.h" +#include "Runtime/Utilities/ArrayUtility.h" + +namespace SplatMaterials_Static +{ +static SHADERPROP(MainTex); +static SHADERPROP(Control); +} // namespace SplatMaterials_Static + +static const char* kSplatNameStrings[4] = {"_Splat0","_Splat1","_Splat2","_Splat3"}; +static const char* kSplatNormalNameStrings[4] = {"_Normal0","_Normal1","_Normal2","_Normal3"}; + + +SplatMaterials::SplatMaterials (PPtr<TerrainData> terrain) +: m_CurrentTemplateMaterial(NULL) +{ + m_TerrainData = terrain; + for(int i=0;i<kTerrainShaderCount;i++) + m_Shaders[i] = NULL; + m_BaseMapMaterial = NULL; + for(int i = 0; i < 32; i++) + m_AllocatedMaterials[i] = NULL; +} + +SplatMaterials::~SplatMaterials () +{ + Cleanup (); +} + +void SplatMaterials::Cleanup () +{ + for (int s = 0; s < ARRAY_SIZE(m_Shaders); ++s) + { + m_Shaders[s] = NULL; + } + for (int x = 0; x < ARRAY_SIZE(m_AllocatedMaterials); ++x) + { + DestroySingleObject (m_AllocatedMaterials[x]); + m_AllocatedMaterials[x] = NULL; + } + DestroySingleObject (m_BaseMapMaterial); + m_BaseMapMaterial = NULL; + m_CurrentTemplateMaterial = NULL; +} + + + +void SplatMaterials::LoadSplatShaders (Material* templateMat) +{ + // If material is already created, and our template material is still the same: + // nothing to do + if ((m_Shaders[0] != NULL) && (templateMat == m_CurrentTemplateMaterial)) + return; + + // Material template has changed; recreate internal materials + if (templateMat != m_CurrentTemplateMaterial) + { + Cleanup(); + m_CurrentTemplateMaterial = templateMat; + } + + Shader* templateShader = NULL; + if (templateMat) + templateShader = templateMat->GetShader(); + if (templateShader) + { + // Shader in the template is for the first pass; + // additive pass and basemap are queried from dependencies + m_Shaders[kTerrainShaderFirst] = templateShader; + m_Shaders[kTerrainShaderAdd] = templateShader->GetDependency("AddPassShader"); + m_Shaders[kTerrainShaderBaseMap] = templateShader->GetDependency("BaseMapShader"); + } + + ScriptMapper& sm = GetScriptMapper (); + + // Note: No good reason to keep "Lightmap-" in the built-in terrain shader names, except + // to be able to run Unity 2.x content on regression rig with 3.0 player. The shader + // names just have to match _some_ existing built-in shader in Unity 2.x. + + if (m_Shaders[kTerrainShaderBaseMap] == NULL) + m_Shaders[kTerrainShaderBaseMap] = sm.FindShader("Diffuse"); + if (m_Shaders[kTerrainShaderFirst] == NULL) + m_Shaders[kTerrainShaderFirst] = sm.FindShader("Hidden/TerrainEngine/Splatmap/Lightmap-FirstPass"); + if (m_Shaders[kTerrainShaderAdd] == NULL) + m_Shaders[kTerrainShaderAdd] = sm.FindShader("Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"); + + bool shaderNotFound = false; + for (int i = kTerrainShaderFirst; i <= kTerrainShaderAdd; ++i) + { + if (m_Shaders[i] == NULL) + { + shaderNotFound = true; + m_Shaders[i] = sm.FindShader("Diffuse"); + } + } + + if (shaderNotFound) + { + ErrorString("Unable to find shaders used for the terrain engine. Please include Nature/Terrain/Diffuse shader in Graphics settings."); + } +} + + + + +Material *SplatMaterials::GetSplatBaseMaterial (Material* templateMat) +{ + using namespace SplatMaterials_Static; + + LoadSplatShaders (templateMat); + + Material *mat = m_BaseMapMaterial; + if (mat && mat->GetTexture(kSLPropMainTex) == NULL) + { + DestroySingleObject( mat ); + mat = NULL; + } + if (!mat) { + mat = Material::CreateMaterial (*m_Shaders[kTerrainShaderBaseMap], Object::kHideAndDontSave); + mat->SetTexture( kSLPropMainTex, m_TerrainData->GetSplatDatabase().GetBasemap() ); + m_BaseMapMaterial = mat; + } + if (templateMat) + { + mat->CopyPropertiesFromMaterial (*templateMat); + mat->SetTexture (kSLPropMainTex, m_TerrainData->GetSplatDatabase().GetBasemap()); + } + return mat; +} + +Material **SplatMaterials::GetMaterials (Material* templateMat, int &materialCount) +{ + using namespace SplatMaterials_Static; + + TerrainData *terrainData = m_TerrainData; + LoadSplatShaders (templateMat); + + const bool setNormalMaps = IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1); + + int materialIndex = -1; + Material *splatMaterial = NULL; + int actualNbSplats = terrainData->GetSplatDatabase().GetDepth(); + int nbSplats = actualNbSplats>1?actualNbSplats: 1; + for( int i = 0; i < nbSplats; ++i ) + { + if (i / 4 != materialIndex) + { + materialIndex = i / 4; + + splatMaterial = m_AllocatedMaterials[materialIndex]; + if (splatMaterial == NULL) + { + const int shaderIndex = (materialIndex != 0) ? kTerrainShaderAdd : kTerrainShaderFirst; + Shader* shader = m_Shaders[shaderIndex]; + splatMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave); + splatMaterial->SetCustomRenderQueue(splatMaterial->GetActualRenderQueue() + materialIndex); + m_AllocatedMaterials[materialIndex] = splatMaterial; + } + + if (templateMat) + splatMaterial->CopyPropertiesFromMaterial (*templateMat); + + if( splatMaterial->HasProperty(kSLPropMainTex) ) + splatMaterial->SetTexture(kSLPropMainTex, terrainData->GetSplatDatabase().GetBasemap() ); + + // As soon as our shader does not support 4 splats per pass, + // that means (at least currently) it's a single pass base map. + // So stop adding materials. + if( splatMaterial->GetTag("SplatCount",false,"") != "4" ) + break; + } + + int localSplatIndex = i - (materialIndex * 4); + if (materialIndex < actualNbSplats) + { + splatMaterial->SetTexture( kSLPropControl, terrainData->GetSplatDatabase().GetAlphaTexture(materialIndex)); + } + else + splatMaterial->SetTexture( kSLPropControl, NULL); + SetupSplat(*splatMaterial, localSplatIndex, i, setNormalMaps); + } + + materialCount = materialIndex + 1; + + return m_AllocatedMaterials; +} + + +void SplatMaterials::SetupSplat (Material &m, int splatIndex, int index, bool setNormalMap) +{ + ShaderLab::FastPropertyName slName = ShaderLab::Property(kSplatNameStrings[splatIndex]); + ShaderLab::FastPropertyName slNormalName = ShaderLab::Property(kSplatNormalNameStrings[splatIndex]); + const bool hasTex = m.HasProperty(slName); + const bool hasNormalMap = setNormalMap && m.HasProperty(slNormalName); + + if (index < m_TerrainData->GetSplatDatabase().GetDepth()) + { + const SplatPrototype& splat = m_TerrainData->GetSplatDatabase().GetSplatPrototypes()[index]; + + Vector3f heightmapSize = m_TerrainData->GetHeightmap().GetSize(); + Vector2f splatScale(heightmapSize.x / splat.tileSize.x, heightmapSize.z / splat.tileSize.y); + Vector2f splatOffset(splat.tileOffset.x / heightmapSize.x * splatScale.x, splat.tileOffset.y / heightmapSize.z * splatScale.y); + + if (hasTex) + { + m.SetTexture (slName, splat.texture); + m.SetTextureScale (slName, splatScale); + m.SetTextureOffset (slName, splatOffset); + } + if (hasNormalMap) + m.SetTexture (slNormalName, splat.normalMap); + } + else + { + if (hasTex) + m.SetTexture (slName, NULL); + if (hasNormalMap) + m.SetTexture (slNormalName, NULL); + } +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/SplatMaterials.h b/Runtime/Terrain/SplatMaterials.h new file mode 100644 index 0000000..27b6696 --- /dev/null +++ b/Runtime/Terrain/SplatMaterials.h @@ -0,0 +1,41 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "TerrainData.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/Shader.h" + +enum{ + kTerrainShaderBaseMap, + kTerrainShaderFirst, + kTerrainShaderAdd, + kTerrainShaderCount +}; + + +class SplatMaterials +{ +public: + SplatMaterials (PPtr<TerrainData> terrain); + ~SplatMaterials (); + + Material** GetMaterials (Material* templateMat, int &materialCount); + Material* GetSplatBaseMaterial (Material* templateMat); + void Cleanup (); + +private: + void LoadSplatShaders (Material* templateMat); + void SetupSplat (Material &m, int splatIndex, int index, bool setNormalMap); + +private: + PPtr<TerrainData> m_TerrainData; + Shader *m_Shaders[kTerrainShaderCount]; + + Material* m_AllocatedMaterials[32]; + Material* m_BaseMapMaterial; + Material* m_CurrentTemplateMaterial; +}; + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TerrainData.cpp b/Runtime/Terrain/TerrainData.cpp new file mode 100644 index 0000000..ef9d934 --- /dev/null +++ b/Runtime/Terrain/TerrainData.cpp @@ -0,0 +1,169 @@ +#include "UnityPrefix.h" +#include "TerrainData.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Shaders/Shader.h" +#include "Runtime/Shaders/ShaderNameRegistry.h" +#include "Runtime/Profiler/Profiler.h" +#include "TerrainManager.h" +#if UNITY_EDITOR +#include "Runtime/Serialize/PersistentManager.h" +#endif + +IMPLEMENT_CLASS_HAS_INIT (TerrainData) +IMPLEMENT_OBJECT_SERIALIZE(TerrainData) + +PROFILER_INFORMATION(gAwakeFromLoadTerrain, "TerrainData.AwakeFromLoad", kProfilerLoading) + +TerrainData::TerrainData(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode), m_Heightmap (this), m_TreeDatabase(*this), m_DetailDatabase(this, &m_TreeDatabase), m_SplatDatabase(this) +{ +} + +TerrainData::~TerrainData () +{ + UpdateUsers(kWillBeDestroyed); +} + +void TerrainData::InitializeClass () +{ +#if UNITY_EDITOR + GetPersistentManager().AddNonTextSerializedClass (ClassID (TerrainData)); +#endif + TerrainManager::InitializeClass(); +} + +void TerrainData::CleanupClass () +{ + TerrainManager::CleanupClass(); +} + +SplatDatabase &TerrainData::GetSplatDatabase () +{ + return m_SplatDatabase; +} + +DetailDatabase &TerrainData::GetDetailDatabase () +{ + return m_DetailDatabase; +} + +void TerrainData::ExtractPreloadShaders (vector<PPtr<Object> >& shaders) +{ + ScriptMapper& sm = GetScriptMapper (); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/BillboardTree")); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/Details/BillboardWavingDoublePass")); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/Details/Vertexlit")); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/Details/WavingDoublePass")); + shaders.push_back(sm.FindShader("Hidden/Nature/Tree Soft Occlusion Leaves Rendertex")); + shaders.push_back(sm.FindShader("Hidden/Nature/Tree Soft Occlusion Bark Rendertex")); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/Details/Vertexlit")); + + shaders.push_back(sm.FindShader("Diffuse")); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/Splatmap/Lightmap-FirstPass")); + shaders.push_back(sm.FindShader("Hidden/TerrainEngine/Splatmap/Lightmap-AddPass")); + + for (int i=0;i<shaders.size();i++) + { + if (!shaders[i].IsValid()) + { + ErrorString("Terrain preloaded shaders could not be found"); + } + } +} + +void TerrainData::AwakeFromLoadThreaded () +{ + Super::AwakeFromLoadThreaded(); + m_SplatDatabase.RecalculateBasemap(false); + m_DetailDatabase.SetDetailPrototypesDirty(); + m_DetailDatabase.GenerateTextureAtlasThreaded(); +} + +void TerrainData::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + PROFILER_AUTO(gAwakeFromLoadTerrain, this) + + Super::AwakeFromLoad(awakeMode); + m_SplatDatabase.AwakeFromLoad(awakeMode); + m_DetailDatabase.SetDetailPrototypesDirty(); + + if (awakeMode & kDidLoadThreaded) + m_DetailDatabase.UpdateDetailPrototypesIfDirty(); + + m_TreeDatabase.RefreshPrototypes(); + + UpdateUsers(kFlushEverythingImmediately); + + m_Heightmap.AwakeFromLoad(); + + // Just upload image - threaded basemap calculation already done + if (awakeMode & kDidLoadThreaded) + { + m_SplatDatabase.UploadBasemap(); + } + // Do full recalculation + else + { + m_SplatDatabase.RecalculateBasemapIfDirty(); + } +} + +template<class TransferFunc> +void TerrainData::Transfer (TransferFunc& transfer) +{ + Super::Transfer(transfer); + + transfer.Transfer (m_SplatDatabase, "m_SplatDatabase", kHideInEditorMask); + transfer.Transfer (m_DetailDatabase, "m_DetailDatabase", kHideInEditorMask); + transfer.Transfer (m_Heightmap, "m_Heightmap", kHideInEditorMask); + +#if UNITY_EDITOR + // Are we collecting all references for preloading? + if ((transfer.GetFlags () & kBuildPlayerOnlySerializeBuildProperties) && transfer.IsRemapPPtrTransfer()) + { + vector<PPtr<Object> > preloadShader; + ExtractPreloadShaders(preloadShader); + TRANSFER(preloadShader); + } +#endif +} + +bool TerrainData::HasUser (GameObject *user) const +{ + return m_Users.find(user) != m_Users.end(); +} + +void TerrainData::AddUser (GameObject *user) +{ + m_Users.insert (user); +} + +void TerrainData::RemoveUser (GameObject *user) +{ + m_Users.erase (user); +} + +void TerrainData::UpdateUsers (ChangedFlags changedFlag) +{ + for (std::set<PPtr<GameObject> >::iterator i = m_Users.begin(); i != m_Users.end(); i++) + { + GameObject *go = *i; + if (go) + go->SendMessage(kTerrainChanged, (int)changedFlag, ClassID (int)); + } +} + +void TerrainData::SetLightmapIndexOnUsers(int lightmapIndex) +{ + for (std::set<PPtr<GameObject> >::iterator i = m_Users.begin(); i != m_Users.end(); i++) + { + GameObject *go = *i; + if (go) + go->SendMessage(kSetLightmapIndex, (int)lightmapIndex, ClassID (int)); + } +} + +#endif // ENABLE_TERRAIN + diff --git a/Runtime/Terrain/TerrainData.h b/Runtime/Terrain/TerrainData.h new file mode 100644 index 0000000..3851c8c --- /dev/null +++ b/Runtime/Terrain/TerrainData.h @@ -0,0 +1,66 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/BaseClasses/NamedObject.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Serialize/SerializeUtility.h" + +#include "SplatDatabase.h" +#include "DetailDatabase.h" +#include "Heightmap.h" +#include "TreeDatabase.h" +#include "Runtime/Utilities/NonCopyable.h" + + +class TerrainData : public NamedObject, NonCopyable +{ + public: + REGISTER_DERIVED_CLASS(TerrainData, NamedObject) + DECLARE_OBJECT_SERIALIZE(TerrainData) + + TerrainData (MemLabelId label, ObjectCreationMode mode); + + SplatDatabase &GetSplatDatabase (); + DetailDatabase &GetDetailDatabase (); + Heightmap &GetHeightmap () { return m_Heightmap; } + TreeDatabase& GetTreeDatabase () { return m_TreeDatabase; } + + bool HasUser (GameObject *user) const; + void AddUser (GameObject *user); + void RemoveUser (GameObject *user); + + static void InitializeClass (); + static void CleanupClass (); + + enum ChangedFlags + { + kNoChange = 0, + kHeightmap = 1, + kTreeInstances = 2, + kDelayedHeightmapUpdate = 4, + kFlushEverythingImmediately = 8, + kRemoveDirtyDetailsImmediately = 16, + kWillBeDestroyed = 256, + }; + + // Sends a callback to any users of this terrainsData (typically C# Terrain objects) so they can update their renderers, etc. + void UpdateUsers (ChangedFlags changedFlag); + void SetLightmapIndexOnUsers(int lightmapIndex); + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + void AwakeFromLoadThreaded(); + + void ExtractPreloadShaders (vector<PPtr<Object> >& shaders); + + private: + SplatDatabase m_SplatDatabase; + TreeDatabase m_TreeDatabase; + DetailDatabase m_DetailDatabase; + Heightmap m_Heightmap; + std::set<PPtr<GameObject> > m_Users; // List of terrains for the client callbacks +}; +ENUM_FLAGS(TerrainData::ChangedFlags); + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TerrainIndexGenerator.cpp b/Runtime/Terrain/TerrainIndexGenerator.cpp new file mode 100644 index 0000000..bcbc8d3 --- /dev/null +++ b/Runtime/Terrain/TerrainIndexGenerator.cpp @@ -0,0 +1,379 @@ +#include "UnityPrefix.h" +#include "TerrainIndexGenerator.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Graphics/TriStripper.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Terrain/Heightmap.h" + +static unsigned int AddSliverTriangles (unsigned int *triangles, unsigned int index, int direction, int edgeMask); +static unsigned int AddSliverCorner (unsigned int* triangles, unsigned int index, int direction, int edgeMask); +static void FlipTriangle (unsigned int *triangles, unsigned int index); +static unsigned int AddQuad (unsigned int *triangles, unsigned int index, int xBase, int yBase); + + +struct CachedStrip { + unsigned int count; + unsigned short* triangles; + + CachedStrip() {count = 0; triangles = NULL;} + ~CachedStrip() { if(triangles) delete[] triangles;} +}; + +static CachedStrip gCachedStrips[16]; + +unsigned int *TerrainIndexGenerator::GetIndexBuffer (int edgeMask, unsigned int &count, int stride) +{ + unsigned int *triangles = new unsigned int[(kPatchSize) * (kPatchSize) * 6]; + unsigned int index = 0; + int size = kPatchSize; + + int minX = 0; + int minY = 0; + int maxX = kPatchSize-1; + int maxY = kPatchSize-1; + + if((edgeMask & kDirectionLeftFlag) == 0) + { + minX+=1; + index = AddSliverTriangles (triangles, index, kDirectionLeft, edgeMask); + } + if((edgeMask & kDirectionRightFlag) == 0) + { + maxX-=1; + index = AddSliverTriangles (triangles, index, kDirectionRight, edgeMask); + } + if((edgeMask & kDirectionUpFlag) == 0) + { + maxY-=1; + index = AddSliverTriangles (triangles, index, kDirectionUp, edgeMask); + } + if((edgeMask & kDirectionDownFlag) == 0) + { + minY+=1; + index = AddSliverTriangles (triangles, index, kDirectionDown, edgeMask); + } + + if((edgeMask & kDirectionLeftFlag) == 0 || (edgeMask & kDirectionUpFlag) == 0) + index = AddSliverCorner (triangles, index, kDirectionLeftUp, edgeMask); + if((edgeMask & kDirectionRightFlag) == 0 || (edgeMask & kDirectionUpFlag) == 0) + index = AddSliverCorner (triangles, index, kDirectionRightUp, edgeMask); + if((edgeMask & kDirectionLeftFlag) == 0 || (edgeMask & kDirectionDownFlag) == 0) + index = AddSliverCorner (triangles, index, kDirectionLeftDown, edgeMask); + if((edgeMask & kDirectionRightFlag) == 0 || (edgeMask & kDirectionDownFlag) == 0) + index = AddSliverCorner (triangles, index, kDirectionRightDown, edgeMask); + + for (int y=minY;y<maxY;y++) + { + for (int x=minX;x<maxX;x++) + { + // For each grid cell output two triangles + triangles[index++] = y + (x * size); + triangles[index++] = (y+1) + x * size; + triangles[index++] = (y+1) + (x + 1) * size; + + triangles[index++] = y + x * size; + triangles[index++] = (y+1) + (x + 1) * size; + triangles[index++] = y + (x + 1) * size; + } + } + + count = index; + return triangles; +} + +unsigned short *TerrainIndexGenerator::GetOptimizedIndexStrip (int edgeMask, unsigned int &count) +{ + edgeMask &= kDirectionDirectNeighbourMask; + if (gCachedStrips[edgeMask].triangles == NULL) + { + unsigned int *triangles = GetIndexBuffer (edgeMask, count, 0); + + Mesh::TemporaryIndexContainer newStrip; + + Stripify ((const UInt32*)triangles, count, newStrip); + + delete[] triangles; + + count = newStrip.size(); + unsigned short *strip = new unsigned short[count]; + for(int i=0;i<count;i++) + strip[i] = newStrip[i]; + gCachedStrips[edgeMask].count = count; + gCachedStrips[edgeMask].triangles = strip; + } + + count = gCachedStrips[edgeMask].count; + return gCachedStrips[edgeMask].triangles; +} + +static void FlipTriangle (unsigned int *triangles, unsigned int index) +{ + int temp = triangles[index]; + triangles[index] = triangles[index+1]; + triangles[index+1] = temp; +} + +static unsigned int AddQuad (unsigned int *triangles, unsigned int index, int xBase, int yBase) +{ + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 1); + triangles[index++] = (xBase + 1) * kPatchSize + (yBase + 1); + + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + 1) * kPatchSize + (yBase + 1); + triangles[index++] = (xBase + 1) * kPatchSize + (yBase + 0); + + return index; +} + +static unsigned int AddSliverCorner (unsigned int* triangles, unsigned int index, int direction, int edgeMask) +{ + int xBase, yBase, ox, oy; + bool flip = false; + + int vMask = 0; + int hMask = 0; + + if (direction == kDirectionLeftDown) + { + xBase = 1; + yBase = 1; + ox = 1; + oy = 1; + flip = false; + + hMask = 1 << kDirectionLeft; + vMask = 1 << kDirectionDown; + } + else if (direction == kDirectionRightDown) + { + xBase = kPatchSize-2; + yBase = 1; + ox = -1; + oy = 1; + flip = true; + + hMask = 1 << kDirectionRight; + vMask = 1 << kDirectionDown; + } + else if (direction == kDirectionLeftUp) + { + xBase = 1; + yBase = kPatchSize-2; + ox = 1; + oy = -1; + flip = true; + + hMask = 1 << kDirectionLeft; + vMask = 1 << kDirectionUp; + } + else + { + xBase = kPatchSize-2; + yBase = kPatchSize-2; + ox = -1; + oy = -1; + flip = false; + + hMask = 1 << kDirectionRight; + vMask = 1 << kDirectionUp; + } + + int mask = 0; + if ((hMask & edgeMask) != 0) + mask |= 1; + if ((vMask & edgeMask) != 0) + mask |= 2; + + // Both edges are tesselated + // Vertical edge is tesselated + if (mask == 1) + { + // Stitch big down and small up + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase + 0); + + // rigth up small + triangles[index++] = (xBase + ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + ox) * kPatchSize + (yBase + 0); + + // Down Big span + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase - oy); + + if (flip) + { + FlipTriangle(triangles, index - 9); + FlipTriangle(triangles, index - 6); + FlipTriangle(triangles, index - 3); + } + + } + // Horizontal edge is tesselated + else if (mask == 2) + { + // Left up small + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + oy); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase + oy); + + // Left Big span + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase + oy); + + // Stitch right-down and big left span + triangles[index++] = (xBase - ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase - oy); + + if (flip) + { + FlipTriangle(triangles, index - 9); + FlipTriangle(triangles, index - 6); + FlipTriangle(triangles, index - 3); + } + + } + // Nothing tesselated + else + { + // Left up small + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase + oy); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + oy); + // right up small + triangles[index++] = (xBase + ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + ox) * kPatchSize + (yBase + 0); + // Left Big span + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase + oy); + // Down Big span + triangles[index++] = (xBase + 0) * kPatchSize + (yBase + 0); + triangles[index++] = (xBase + ox) * kPatchSize + (yBase - oy); + triangles[index++] = (xBase - ox) * kPatchSize + (yBase - oy); + + if (flip) + { + FlipTriangle(triangles, index - 12); + FlipTriangle(triangles, index - 9); + FlipTriangle(triangles, index - 6); + FlipTriangle(triangles, index - 3); + } + } + + return index; +} + +static unsigned int AddSliverTriangles (unsigned int *triangles, unsigned int index, int direction, int edgeMask) +{ + int directionMask = 1 << direction; + if ((edgeMask & directionMask) != 0) + { + for (int y=2;y<kPatchSize-3;y++) + { + if (direction == kDirectionLeft) + index = AddQuad(triangles, index, 0, y); + else if (direction == kDirectionRight) + index = AddQuad(triangles, index, kPatchSize - 2, y); + else if (direction == kDirectionUp) + index = AddQuad(triangles, index, y, kPatchSize - 2); + else if (direction == kDirectionDown) + index = AddQuad(triangles, index, y, 0); + } + } + else + { + for (int i=2;i<kPatchSize-3;i+=2) + { + if (direction == kDirectionLeft) + { + int x = 0; + int y = i; + + // fixup bottom + triangles[index++] = (x + 1) * kPatchSize + (y + 0); + triangles[index++] = (x + 0) * kPatchSize + (y + 0); + triangles[index++] = (x + 1) * kPatchSize + (y + 1); + + // Big span + triangles[index++] = (x + 0) * kPatchSize + (y + 0); + triangles[index++] = (x + 0) * kPatchSize + (y + 2); + triangles[index++] = (x + 1) * kPatchSize + (y + 1); + + // fixup top + triangles[index++] = (x + 0) * kPatchSize + (y + 2); + triangles[index++] = (x + 1) * kPatchSize + (y + 2); + triangles[index++] = (x + 1) * kPatchSize + (y + 1); + } + else if (direction == kDirectionRight) + { + int x = kPatchSize - 1; + int y = i; + + // fixup bottom + triangles[index++] = (x - 1) * kPatchSize + (y + 0); + triangles[index++] = (x - 1) * kPatchSize + (y + 1); + triangles[index++] = (x - 0) * kPatchSize + (y + 0); + + // Big span + triangles[index++] = (x - 0) * kPatchSize + (y + 0); + triangles[index++] = (x - 1) * kPatchSize + (y + 1); + triangles[index++] = (x - 0) * kPatchSize + (y + 2); + + // fixup top + triangles[index++] = (x - 0) * kPatchSize + (y + 2); + triangles[index++] = (x - 1) * kPatchSize + (y + 1); + triangles[index++] = (x - 1) * kPatchSize + (y + 2); + } + else if (direction == kDirectionDown) + { + int x = i; + int y = 0; + + // fixup bottom + triangles[index++] = (x + 0) * kPatchSize + (y + 0); + triangles[index++] = (x + 0) * kPatchSize + (y + 1); + triangles[index++] = (x + 1) * kPatchSize + (y + 1); + + // Big span + triangles[index++] = (x + 1) * kPatchSize + (y + 1); + triangles[index++] = (x + 2) * kPatchSize + (y + 0); + triangles[index++] = (x + 0) * kPatchSize + (y + 0); + // fixup top + triangles[index++] = (x + 2) * kPatchSize + (y + 0); + triangles[index++] = (x + 1) * kPatchSize + (y + 1); + triangles[index++] = (x + 2) * kPatchSize + (y + 1); + } + else + { + int x = i; + int y = kPatchSize - 1; + + // fixup bottom + triangles[index++] = (x + 0) * kPatchSize + (y - 0); + triangles[index++] = (x + 1) * kPatchSize + (y - 1); + triangles[index++] = (x + 0) * kPatchSize + (y - 1); + + // Big span + triangles[index++] = (x + 1) * kPatchSize + (y - 1); + triangles[index++] = (x + 0) * kPatchSize + (y - 0); + triangles[index++] = (x + 2) * kPatchSize + (y - 0); + // fixup top + triangles[index++] = (x + 2) * kPatchSize + (y - 0); + triangles[index++] = (x + 2) * kPatchSize + (y - 1); + triangles[index++] = (x + 1) * kPatchSize + (y - 1); + } + } + } + return index; +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TerrainIndexGenerator.h b/Runtime/Terrain/TerrainIndexGenerator.h new file mode 100644 index 0000000..cf7da96 --- /dev/null +++ b/Runtime/Terrain/TerrainIndexGenerator.h @@ -0,0 +1,12 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +struct TerrainIndexGenerator +{ + static unsigned int *GetIndexBuffer (int edgeMask, unsigned int &count, int stride); + static unsigned short *GetOptimizedIndexStrip (int edgeMask, unsigned int &count); +}; + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TerrainInstance.cpp b/Runtime/Terrain/TerrainInstance.cpp new file mode 100644 index 0000000..3438fc1 --- /dev/null +++ b/Runtime/Terrain/TerrainInstance.cpp @@ -0,0 +1,344 @@ +#include "UnityPrefix.h" +#include "TerrainInstance.h" + +#if ENABLE_TERRAIN +#include "Runtime/Terrain/TerrainData.h" +#include "Runtime/Terrain/TerrainManager.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Camera/Culler.h" +#include "Runtime/Camera/LightManager.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Camera/RenderSettings.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Input/TimeManager.h" + +#include "DetailRenderer.h" +#include "TreeRenderer.h" +#include "TerrainRenderer.h" + +#include <list> + +using namespace std; + +TerrainInstance::TerrainInstance (GameObject* go) +: m_Position(0,0,0) +, m_GameObject(go) +, m_HeightmapPixelError(5.0f) +, m_HeightmapMaximumLOD(0) +, m_SplatMapDistance(1000.0f) +, m_TreeDistance(5000.0f) +, m_TreeBillboardDistance(50.0f) +, m_TreeCrossFadeLength(5.0f) +, m_TreeMaximumFullLODCount(50) +, m_DetailObjectDistance(80.0f) +, m_DetailObjectDensity(1.0f) +, m_CastShadows(true) +, m_DrawTreesAndFoliage(true) +, m_LeftNeighbor(NULL) +, m_RightNeighbor(NULL) +, m_BottomNeighbor(NULL) +, m_TopNeighbor(NULL) +, m_EditorRenderFlags(kRenderAll) +, m_LightmapIndex(-1) +, m_LightmapSize(1024) +{ + Assert(go != NULL); +} + +TerrainInstance::~TerrainInstance() +{ + // Determine what to clean up +} + +void TerrainInstance::OnEnable() +{ + if (m_TerrainData.IsValid()) + { + Assert(m_GameObject != NULL); + m_TerrainData->AddUser(m_GameObject); + } +} + +void TerrainInstance::OnDisable() +{ + if (m_TerrainData.IsValid()) + { + Assert(m_GameObject != NULL); + m_TerrainData->RemoveUser(m_GameObject); + } +} + +const Vector3f TerrainInstance::GetPosition() const +{ + return m_GameObject->GetComponentT<Transform>(Transform::GetClassIDStatic()).GetPosition(); +} + +void TerrainInstance::Flush() +{ + for (dynamic_array<Renderer>::iterator i = m_Renderers.begin(); i != m_Renderers.end(); ++i) + { + UNITY_DELETE ((*i).trees, kMemTerrain); + UNITY_DELETE ((*i).terrain, kMemTerrain); + UNITY_DELETE ((*i).details, kMemTerrain); + } + + m_Renderers.clear(); +} + +void TerrainInstance::GarbageCollectRenderers() +{ + int frame = GetTimeManager ().GetRenderFrameCount (); + // traverse backwards so we can remove renderers + dynamic_array<Renderer>::iterator i = m_Renderers.begin(); + while (i != m_Renderers.end()) + { + int frameDiff = frame - (*i).lastUsedFrame; + // cleanup renderers after they are unused for some frames; handle wrap-around just in case + // also cleanup immediately when camera is destroyed + if( frameDiff > 100 || frameDiff < 0 || (*i).camera == NULL) { + UNITY_DELETE((*i).trees, kMemTerrain); + UNITY_DELETE((*i).terrain, kMemTerrain); + UNITY_DELETE((*i).details, kMemTerrain); + i = m_Renderers.erase(i); + } + else + ++i; + } +} + +void TerrainInstance::FlushDirty () +{ + bool reloadDetails = false; + bool reloadTrees = false; + + // Figure out what we need to recalc depending on what to update. + // we build some bool flags so we never do more than one update no matter + // what was changed and which dependencies they have of each other. + if ((m_DirtyFlags & TerrainData::kHeightmap) != 0) + reloadDetails = reloadTrees = true; + if ((m_DirtyFlags & TerrainData::kTreeInstances) != 0) + reloadTrees = true; + + // Optimized live terrain painting update mode + if ((m_DirtyFlags & TerrainData::kDelayedHeightmapUpdate) != 0) + { + // Reload precomputed error, this will make affected patches reload the vertices! + for (dynamic_array<Renderer>::iterator i = m_Renderers.begin(); i != m_Renderers.end(); ++i) + (*i).terrain->ReloadPrecomputedError (); + } + + if (reloadTrees) + { + for (dynamic_array<Renderer>::iterator i = m_Renderers.begin(); i != m_Renderers.end(); ++i) + (*i).trees->ReloadTrees(); + } + + if (reloadDetails) + { + for (dynamic_array<Renderer>::iterator i = m_Renderers.begin(); i != m_Renderers.end(); ++i) + (*i).details->ReloadAllDetails (); + } + + + if ((m_DirtyFlags & TerrainData::kHeightmap) != 0) + { + for (dynamic_array<Renderer>::iterator i = m_Renderers.begin(); i != m_Renderers.end(); ++i) + (*i).terrain->ReloadAll(); + } + + m_DirtyFlags = TerrainData::kNoChange; +} + +const TerrainInstance::Renderer* TerrainInstance::GetRenderer() +{ + Camera* cam = GetCurrentCameraPtr(); + + if ((cam->GetCullingMask() & (1 << m_GameObject->GetLayer())) == 0) + return NULL; + +#if UNITY_EDITOR + if (!cam->IsFiltered(*m_GameObject)) + return NULL; +#endif + + int frame = GetTimeManager ().GetRenderFrameCount (); + for (dynamic_array<Renderer>::iterator i = m_Renderers.begin(); i != m_Renderers.end(); ++i) + { + if (i->camera == cam) + { + if (i->terrain->GetTerrainData().IsNull()) + { + Flush(); + break; + } + i->lastUsedFrame = frame; + return &*i; + } + } + + SET_ALLOC_OWNER(m_GameObject); + + if (m_TerrainData.IsValid()) + { + Vector3f position = GetPosition(); + + m_Renderers.resize_uninitialized(m_Renderers.size() + 1, false); + Renderer& renderer = m_Renderers.back(); + renderer.camera = cam; + renderer.terrain = UNITY_NEW(TerrainRenderer, kMemTerrain) (m_GameObject->GetInstanceID(), m_TerrainData, position, m_LightmapIndex); +#if UNITY_EDITOR + renderer.terrain->SetLightmapSize(m_LightmapSize); +#endif + renderer.trees = UNITY_NEW(TreeRenderer, kMemTerrain) (m_TerrainData->GetTreeDatabase(), position, m_LightmapIndex); + renderer.details = UNITY_NEW(DetailRenderer, kMemTerrain) (m_TerrainData, position, m_LightmapIndex); + renderer.lastUsedFrame = frame; + return &renderer; + } + else + { + return NULL; + } +} + +TerrainRenderer* TerrainInstance::GetTerrainRendererDontCreate() +{ + Camera* cam = GetCurrentCameraPtr(); + + if ((cam->GetCullingMask() & (1 << m_GameObject->GetLayer())) == 0) + return NULL; + + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + { + if (r->camera == cam) + return r->terrain; + } + + return NULL; +} + +void TerrainInstance::SetLightmapIndex(int value) +{ + m_LightmapIndex = value; + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + { + r->terrain->SetLightmapIndex(value); + r->trees->SetLightmapIndex(value); + r->details->SetLightmapIndex(value); + } +} + +void TerrainInstance::InitializeClass() +{ +} + +void TerrainInstance::CleanupClass() +{ +} + +void TerrainInstance::SetDetailObjectDensity(float value) +{ + value = ::clamp(value,0.0f,1.0f); + bool changed = (value != m_DetailObjectDensity); + m_DetailObjectDensity = value; + if (changed) + { + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + r->details->ReloadAllDetails(); + } +} + +void TerrainInstance::SetLightmapSize(int value) +{ +#if UNITY_EDITOR + m_LightmapSize = value > 0 ? value : 1; + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + r->terrain->SetLightmapSize(value); +#endif +} + +void TerrainInstance::ApplyDelayedHeightmapModification() +{ + UNITY_TEMP_VECTOR(int) invalidPatches; + m_TerrainData->GetHeightmap().RecomputeInvalidPatches(invalidPatches); + if (invalidPatches.size() != 0) + { + m_TerrainData->GetTreeDatabase().RecalculateTreePositions(); + + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + { + r->terrain->ReloadPrecomputedError (); + r->terrain->ReloadBounds (); + r->details->ReloadAllDetails (); + } + } + +} + +///TODO: This should be moved to TerrainData. Each TreeRenderer should register when its using a TerrainData with a linked list and when a tree is added, +/// a callback should be invoked which lets the TreeRenderer ineject the tree into the spatial database +void TerrainInstance::AddTreeInstance(const TreeInstance& tree) +{ + bool hasTrees = !m_TerrainData->GetTreeDatabase().GetInstances().empty(); + m_TerrainData->GetTreeDatabase().AddTree(tree); + + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + { + if (hasTrees) + { + r->trees->InjectTree (m_TerrainData->GetTreeDatabase().GetInstances().back()); + } + else + { + delete r->trees; + r->trees = new TreeRenderer (m_TerrainData->GetTreeDatabase(), GetPosition(), m_LightmapIndex); + } + } +} + +void TerrainInstance::RemoveTrees(const Vector2f& position, float radius, int prototypeIndex) +{ + int trees = m_TerrainData->GetTreeDatabase().RemoveTrees(position, radius, prototypeIndex); + if (trees != 0) + { + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + { + r->trees->RemoveTrees(Vector3f(position[0], position[1], 0.0f), radius, prototypeIndex); + } + } +} + +void TerrainInstance::OnTerrainChanged(TerrainData::ChangedFlags flags) +{ + // Dirty details must be deleted immediately because we are clearing data that stores what was set dirty immediately afterwards. + if ((flags & TerrainData::kRemoveDirtyDetailsImmediately) != 0) + { + for (dynamic_array<Renderer>::iterator r = m_Renderers.begin(); r != m_Renderers.end(); ++r) + { + r->details->ReloadDirtyDetails(); + } + } + + if ((flags & TerrainData::kFlushEverythingImmediately) != 0) + Flush (); + else + m_DirtyFlags |= flags; +} + +void TerrainInstance::SetNeighbors (TerrainInstance* left, TerrainInstance* top, TerrainInstance* right, TerrainInstance* bottom) +{ + m_TopNeighbor = top; + m_LeftNeighbor = left; + m_RightNeighbor = right; + m_BottomNeighbor = bottom; +} + +float TerrainInstance::SampleHeight(Vector3f worldPosition) const +{ + worldPosition -= GetPosition(); + worldPosition.x /= m_TerrainData->GetHeightmap().GetSize().x; + worldPosition.z /= m_TerrainData->GetHeightmap().GetSize().z; + return m_TerrainData->GetHeightmap().GetInterpolatedHeight(worldPosition.x, worldPosition.z); +} +#undef LISTFOREACH + +#endif //ENABLE_TERRAIN diff --git a/Runtime/Terrain/TerrainInstance.h b/Runtime/Terrain/TerrainInstance.h new file mode 100644 index 0000000..d0ce1cd --- /dev/null +++ b/Runtime/Terrain/TerrainInstance.h @@ -0,0 +1,132 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Terrain/TerrainData.h" +#include "Runtime/Utilities/NonCopyable.h" + +class Camera; +class DetailRenderer; +class TerrainManager; +class TerrainRenderer; +class TreeRenderer; + +class TerrainInstance +{ +public: + enum RenderFlags + { + kRenderHeightmap = 1, + kRenderTrees = 2, + kRenderDetails = 4, + kRenderAll = kRenderHeightmap | kRenderTrees | kRenderDetails + }; + + TerrainInstance (GameObject* go); + ~TerrainInstance (); + + static void InitializeClass (); + static void CleanupClass (); + + void OnEnable (); + void OnDisable (); + GameObject* GetGameObject() const { return m_GameObject; } + + const Vector3f GetPosition () const; + void SetNeighbors (TerrainInstance* left, TerrainInstance* top, TerrainInstance* right, TerrainInstance* bottom); + + void Flush (); + void FlushDirty (); + void GarbageCollectRenderers (); + + int GetLightmapIndex() const { return m_LightmapIndex; } + void SetLightmapIndex (int value); + + const int GetLightmapSize () const { return m_LightmapSize; } + void SetLightmapSize (int value); + + GET_SET(PPtr<TerrainData>, TerrainData, m_TerrainData); + GET_SET(float, HeightmapPixelError, m_HeightmapPixelError); + GET_SET(int, HeightmapMaximumLOD, m_HeightmapMaximumLOD); + GET_SET(float, BasemapDistance, m_SplatMapDistance); + GET_SET(float, TreeDistance, m_TreeDistance); + GET_SET(float, TreeBillboardDistance, m_TreeBillboardDistance); + GET_SET(float, TreeCrossFadeLength, m_TreeCrossFadeLength); + GET_SET(int, TreeMaximumFullLODCount, m_TreeMaximumFullLODCount); + GET_SET(float, DetailObjectDistance, m_DetailObjectDistance); + GET_SET(bool, CastShadows, m_CastShadows); + GET_SET(bool, DrawTreesAndFoliage, m_DrawTreesAndFoliage); + GET_SET(Material*, MaterialTemplate, m_MaterialTemplate); + GET_SET(RenderFlags, EditorRenderFlags, m_EditorRenderFlags); + + const float GetDetailObjectDensity () const { return m_DetailObjectDensity; } + void SetDetailObjectDensity (float value); + + float SampleHeight (Vector3f worldPosition) const; + + void ApplyDelayedHeightmapModification (); + + void AddTreeInstance (const TreeInstance& tree); + void RemoveTrees (const Vector2f& position, float radius, int prototypeIndex); + + void OnTerrainChanged(TerrainData::ChangedFlags flags); + + TerrainRenderer* GetTerrainRendererDontCreate (); + + bool NeedRenderTerrainGeometry() const { return (m_EditorRenderFlags & kRenderHeightmap) != 0; } + bool NeedRenderDetails() const { return (m_EditorRenderFlags & kRenderDetails) != 0 && m_DrawTreesAndFoliage && m_DetailObjectDistance > 0.001; } + bool NeedRenderTrees() const { return (m_EditorRenderFlags & kRenderTrees) != 0 && m_DrawTreesAndFoliage && m_TreeDistance > 0.001; } + +private: + friend class TerrainManager; + + struct Renderer + { + Camera* camera; + TerrainRenderer* terrain; + TreeRenderer* trees; + DetailRenderer* details; + int lastUsedFrame; + + Renderer() : camera(NULL), terrain(NULL), trees(NULL), details(NULL), lastUsedFrame(0) {} + }; + + const Renderer* GetRenderer(); + + PPtr<TerrainData> m_TerrainData; + Vector3f m_Position; + + GameObject* m_GameObject; + + float m_HeightmapPixelError; + int m_HeightmapMaximumLOD; + float m_SplatMapDistance; + int m_LightmapIndex; + int m_LightmapSize; + float m_TreeDistance; + float m_TreeBillboardDistance; + float m_TreeCrossFadeLength; + int m_TreeMaximumFullLODCount; + float m_DetailObjectDistance; + float m_DetailObjectDensity; + bool m_CastShadows; + bool m_DrawTreesAndFoliage; + PPtr<Material> m_MaterialTemplate; + + TerrainInstance* m_LeftNeighbor; + TerrainInstance* m_RightNeighbor; + TerrainInstance* m_BottomNeighbor; + TerrainInstance* m_TopNeighbor; + + RenderFlags m_EditorRenderFlags; + + // Which part of the terrain is dirty + TerrainData::ChangedFlags m_DirtyFlags; + + dynamic_array<Renderer> m_Renderers; +}; + +#endif
\ No newline at end of file diff --git a/Runtime/Terrain/TerrainManager.cpp b/Runtime/Terrain/TerrainManager.cpp new file mode 100644 index 0000000..3f3805f --- /dev/null +++ b/Runtime/Terrain/TerrainManager.cpp @@ -0,0 +1,280 @@ +#include "UnityPrefix.h" +#if ENABLE_TERRAIN +#include "TerrainManager.h" +#include "TerrainData.h" +#include "TerrainInstance.h" +#include "DetailRenderer.h" +#include "TreeRenderer.h" +#include "TerrainRenderer.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Graphics/Transform.h" + +#include "Runtime/Camera/LightManager.h" +using namespace std; + +#define LISTFOREACH(TYPE, X, Y) for (UNITY_LIST(kMemRenderer, TYPE)::iterator Y = X.begin(); Y != X.end(); ++Y) + +PROFILER_INFORMATION(gTerrainFlushDirty, "TerrainInstance.FlushDirty", kProfilerRender); +PROFILER_INFORMATION(gTerrainRenderStep1, "Terrain.Heightmap.RenderStep1", kProfilerRender); +PROFILER_INFORMATION(gTerrainRenderStep2, "Terrain.Heightmap.RenderStep2", kProfilerRender); +PROFILER_INFORMATION(gTerrainRenderStep3, "Terrain.Heightmap.RenderStep3", kProfilerRender); +PROFILER_INFORMATION(gTerrainDetailsRender, "Terrain.Details.Render", kProfilerRender); +PROFILER_INFORMATION(gTerrainTreesRender, "Terrain.Trees.Render", kProfilerRender); + +void TerrainManager::InitializeClass () +{ + SetITerrainManager (new TerrainManager()); +} + +void TerrainManager::CleanupClass () +{ + TerrainManager* manager = reinterpret_cast<TerrainManager*> (GetITerrainManager()); + delete manager; + SetITerrainManager (NULL); +} + +TerrainManager::TerrainManager() +: m_ActiveTerrain(NULL) +{ +} + + +bool TerrainManager::GetInterpolatedHeight (const Object* inTerrainData, const Vector3f& terrainPosition, const Vector3f& position, float& outputHeight) +{ + TerrainData* terrainData = dynamic_pptr_cast<TerrainData*> (inTerrainData); + if (terrainData == NULL) + return false; + + const Vector3f terrainSize (terrainData->GetHeightmap ().GetSize ()); + const Vector3f localPosition = Scale (position-terrainPosition, Inverse (terrainSize)); + + // Outside of terrain.. Skip. + if (localPosition.x > 1.0f || localPosition.x < 0.0f || localPosition.z > 1.0f || localPosition.z < 0.0f) + return false; + + outputHeight = terrainPosition.y + terrainData->GetHeightmap ().GetInterpolatedHeight (localPosition.x, localPosition.z); + return true; +} + +void TerrainManager::CullAllTerrains(int cullingMask) +{ + m_TempCulledTerrains.clear(); + + // Generate basic terrain data and do basic tessellation + LISTFOREACH(TerrainInstance*, m_ActiveTerrains, i) + { + TerrainInstance* terrain = (*i); + + int terrainLayer = terrain->m_GameObject->GetLayer(); + if( ((1<<terrainLayer) & cullingMask) == 0 ) + continue; + + m_TempCulledTerrains.push_back(*i); + const Vector3f& currentTerrrainPosition = terrain->GetPosition(); + if (currentTerrrainPosition != terrain->m_Position) + { + terrain->m_Position = currentTerrrainPosition; + terrain->Flush (); + } + + terrain->GarbageCollectRenderers (); + PROFILER_BEGIN(gTerrainFlushDirty, NULL) + terrain->FlushDirty (); + PROFILER_END + + PROFILER_BEGIN(gTerrainRenderStep1, NULL) + const TerrainInstance::Renderer* renderer = terrain->GetRenderer (); + if (renderer != NULL) + { + // Draw terrain + terrain->m_TerrainData->GetSplatDatabase().RecalculateBasemapIfDirty(); + + if (terrain->NeedRenderTerrainGeometry()) + { + float splatMapDistance = terrain->m_EditorRenderFlags == TerrainInstance::kRenderHeightmap ? FLT_MAX : terrain->m_SplatMapDistance; + renderer->terrain->RenderStep1 (renderer->camera, terrain->m_HeightmapMaximumLOD, terrain->m_HeightmapPixelError, splatMapDistance); + } + } + PROFILER_END + } + + // Setup neighbors + LISTFOREACH(TerrainInstance*, m_TempCulledTerrains, i) + { + TerrainInstance* terrain = (*i); + TerrainRenderer* renderer = terrain->GetTerrainRendererDontCreate (); + if (renderer != NULL && terrain->NeedRenderTerrainGeometry()) + { + // Find neighbor Terrains and update TerrainRenderer + TerrainRenderer* left = NULL, * right = NULL, * top = NULL, * bottom = NULL; + if (terrain->m_LeftNeighbor != NULL) + left = terrain->m_LeftNeighbor->GetTerrainRendererDontCreate (); + if (terrain->m_RightNeighbor != NULL) + right = terrain->m_RightNeighbor->GetTerrainRendererDontCreate (); + if (terrain->m_TopNeighbor != NULL) + top = terrain->m_TopNeighbor->GetTerrainRendererDontCreate (); + if (terrain->m_BottomNeighbor != NULL) + bottom = terrain->m_BottomNeighbor->GetTerrainRendererDontCreate (); + renderer->SetNeighbors(left, top, right, bottom); + } + } + + // Apply force splitting on boundaries + LISTFOREACH(TerrainInstance*, m_TempCulledTerrains, i) + { + TerrainInstance* terrain = (*i); + TerrainRenderer* renderer = terrain->GetTerrainRendererDontCreate (); + if (renderer != NULL && terrain->NeedRenderTerrainGeometry()) + { + PROFILER_BEGIN(gTerrainRenderStep2, NULL) + renderer->RenderStep2 (); + PROFILER_END + } + } + + // Do the actual rendering + LISTFOREACH(TerrainInstance*, m_TempCulledTerrains, i) + { + TerrainInstance* terrain = (*i); + const TerrainInstance::Renderer* renderer = terrain->GetRenderer (); + if (renderer != NULL) + { + int terrainLayer = terrain->m_GameObject->GetLayer(); + + UNITY_VECTOR(kMemRenderer, Light*) lights = GetLightManager().GetLights(kLightDirectional, terrainLayer); + + PROFILER_BEGIN(gTerrainRenderStep3, NULL) + if (terrain->NeedRenderTerrainGeometry()) + { + renderer->terrain->RenderStep3 (renderer->camera, terrainLayer, terrain->m_CastShadows, terrain->m_MaterialTemplate); + } + PROFILER_END + + PROFILER_BEGIN(gTerrainDetailsRender, NULL) + if (terrain->NeedRenderDetails()) + { + renderer->details->Render (renderer->camera, terrain->m_DetailObjectDistance, terrainLayer, terrain->m_DetailObjectDensity); + } + PROFILER_END + + PROFILER_BEGIN(gTerrainTreesRender, NULL) + if (terrain->NeedRenderTrees()) + { + renderer->trees->Render (*renderer->camera, lights, terrain->m_TreeBillboardDistance, terrain->m_TreeDistance, terrain->m_TreeCrossFadeLength, terrain->m_TreeMaximumFullLODCount, terrainLayer); + } + PROFILER_END + // terrain.m_DebugTreeRenderTex = renderer.trees.GetImposterRenderTexture(); + } + } +} + +void TerrainManager::SetLightmapIndexOnAllTerrains (int lightmapIndex) +{ + LISTFOREACH(TerrainInstance*, m_ActiveTerrains, terrain) + { + (*terrain)->SetLightmapIndex(lightmapIndex); + } +} + +/// Creates a Terrain including collider from [[TerrainData]] +PPtr<GameObject> TerrainManager::CreateTerrainGameObject (const TerrainData& assignTerrain) +{ +/* // Also create the renderer game object +#if ENABLE_PHYSICS + GameObject* go = &CreateGameObjectWithHideFlags ("Terrain", true, 0, "Terrain", NULL, NULL); +#else + GameObject* go = &CreateGameObjectWithHideFlags ("Terrain", true, 0, "Terrain", NULL, NULL); +#endif + go->SetIsStatic (true); + TerrainInstance* terrain = go.GetComponent(typeof(Terrain)) as Terrain; +#if ENABLE_PHYSICS + TerrainCollider collider = go.GetComponent(typeof(TerrainCollider)) as TerrainCollider; + collider.terrainData = assignTerrain; + terrain.terrainData = assignTerrain; +#endif + // The terrain already got an OnEnable, but the terrain data had not been set up correctly. + terrain->OnEnable (); + + return go;*/ + return NULL; +} + +void TerrainManager::AddTerrainAndSetActive(TerrainInstance* terrain) +{ + if (std::find(m_ActiveTerrains.begin(), m_ActiveTerrains.end(), terrain) == m_ActiveTerrains.end()) + m_ActiveTerrains.push_back(terrain); + + m_ActiveTerrain = terrain; +} + +void TerrainManager::RemoveTerrain(TerrainInstance* terrain) +{ + TerrainList::iterator i = std::find(m_ActiveTerrains.begin(), m_ActiveTerrains.end(), terrain); + if (i != m_ActiveTerrains.end()) + m_ActiveTerrains.erase(i); + + if (m_ActiveTerrain == terrain) + m_ActiveTerrain = NULL; +} + +void TerrainManager::UnloadTerrainsFromGfxDevice() +{ + LISTFOREACH(TerrainInstance*, m_ActiveTerrains, terrain) + { + dynamic_array<TerrainInstance::Renderer> renderers = (*terrain)->m_Renderers; + for (int i = 0; i < renderers.size(); ++i) + { + renderers[i].terrain->UnloadVBOFromGfxDevice(); + } + } +} + +void TerrainManager::ReloadTerrainsToGfxDevice() +{ + LISTFOREACH(TerrainInstance*, m_ActiveTerrains, terrain) + { + dynamic_array<TerrainInstance::Renderer> renderers = (*terrain)->m_Renderers; + for (int i = 0; i < renderers.size(); ++i) + { + renderers[i].terrain->ReloadVBOToGfxDevice(); + } + } +} + +#if ENABLE_PHYSICS +NxHeightField* TerrainManager::Heightmap_GetNxHeightField(Heightmap& heightmap) +{ + return heightmap.GetNxHeightField(); +} +#endif + +int TerrainManager::Heightmap_GetMaterialIndex(Heightmap& heightmap) +{ + return heightmap.GetMaterialIndex(); +} + +Vector3f TerrainManager::Heightmap_GetSize(Heightmap& heightmap) +{ + return heightmap.GetSize(); +} + +void TerrainManager::CollectTreeRenderers(dynamic_array<SceneNode>& sceneNodes, dynamic_array<AABB>& boundingBoxes) const +{ + for (TerrainList::const_iterator it = m_ActiveTerrains.begin(); it != m_ActiveTerrains.end(); ++it) + { + if (!(*it)->NeedRenderTrees()) + { + continue; + } + + const TerrainInstance::Renderer* renderer = (*it)->GetRenderer(); + if (renderer != NULL && renderer->trees != NULL) + { + renderer->trees->CollectTreeRenderers(sceneNodes, boundingBoxes); + } + } +} + +#undef LISTFOREACH + +#endif diff --git a/Runtime/Terrain/TerrainManager.h b/Runtime/Terrain/TerrainManager.h new file mode 100644 index 0000000..d3904c1 --- /dev/null +++ b/Runtime/Terrain/TerrainManager.h @@ -0,0 +1,47 @@ +#pragma once + +#include "Runtime/BaseClasses/BaseObject.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Interfaces/ITerrainManager.h" +#include <list> + +class TerrainData; +class HeightMap; + +class TerrainManager : public ITerrainManager +{ +public: + TerrainManager(); + + virtual void CullAllTerrains (int cullingMask); + virtual void SetLightmapIndexOnAllTerrains (int lightmapIndex); + virtual void AddTerrainAndSetActive (TerrainInstance* terrain); + virtual void RemoveTerrain (TerrainInstance* terrain); + TerrainInstance* GetActiveTerrain() const { return m_ActiveTerrain; } + const TerrainList& GetActiveTerrains() const { return m_ActiveTerrains; } + void UnloadTerrainsFromGfxDevice (); + void ReloadTerrainsToGfxDevice (); + + // TODO Move these to heightmap, does not really belong here +#if ENABLE_PHYSICS + virtual NxHeightField* Heightmap_GetNxHeightField(Heightmap& heightmap); +#endif + virtual int Heightmap_GetMaterialIndex(Heightmap& heightmap); + virtual Vector3f Heightmap_GetSize(Heightmap& heightmap); + + // TODO this should move to TerrainData + /// Extracts the height on the heightmap from a TerrainData + virtual bool GetInterpolatedHeight (const Object* terrainData, const Vector3f& terrainPosition, const Vector3f& position, float& outputHeight); + + virtual void CollectTreeRenderers(dynamic_array<SceneNode>& sceneNodes, dynamic_array<AABB>& boundingBoxes) const; + + static void InitializeClass (); + static void CleanupClass (); + PPtr<GameObject> CreateTerrainGameObject (const TerrainData& assignTerrain); + +private: + + TerrainList m_TempCulledTerrains; + TerrainList m_ActiveTerrains; + TerrainInstance* m_ActiveTerrain; +}; diff --git a/Runtime/Terrain/TerrainModule.jam b/Runtime/Terrain/TerrainModule.jam new file mode 100644 index 0000000..09de7d8 --- /dev/null +++ b/Runtime/Terrain/TerrainModule.jam @@ -0,0 +1,67 @@ +rule TerrainModule_ReportCpp +{ + return + Runtime/Terrain/TerrainModule.jam + + Runtime/Terrain/DetailDatabase.cpp + Runtime/Terrain/DetailDatabase.h + Runtime/Terrain/DetailRenderer.cpp + Runtime/Terrain/DetailRenderer.h + Runtime/Terrain/ImposterRenderTexture.cpp + Runtime/Terrain/ImposterRenderTexture.h + Runtime/Terrain/SplatDatabase.cpp + Runtime/Terrain/SplatDatabase.h + Runtime/Terrain/SplatMaterials.cpp + Runtime/Terrain/SplatMaterials.h + Runtime/Terrain/TerrainData.cpp + Runtime/Terrain/TerrainData.h + Runtime/Terrain/TerrainIndexGenerator.cpp + Runtime/Terrain/TerrainIndexGenerator.h + Runtime/Terrain/TerrainInstance.cpp + Runtime/Terrain/TerrainInstance.h + Runtime/Terrain/TerrainRenderer.cpp + Runtime/Terrain/TerrainRenderer.h + Runtime/Terrain/TerrainManager.cpp + Runtime/Terrain/TerrainManager.h + Runtime/Terrain/TerrainModuleRegistration.cpp + Runtime/Terrain/Tree.cpp + Runtime/Terrain/Tree.h + Runtime/Terrain/TreeDatabase.cpp + Runtime/Terrain/TreeDatabase.h + Runtime/Terrain/TreeRenderer.cpp + Runtime/Terrain/TreeRenderer.h + Runtime/Terrain/Heightmap.cpp + Runtime/Terrain/Heightmap.h + Runtime/Terrain/QuadTreeNodeRenderer.cpp + Runtime/Terrain/QuadTreeNodeRenderer.h + ; +} + +rule TerrainModule_ReportTxt +{ + return + Runtime/Terrain/ScriptBindings/TerrainDataBindings.txt + Runtime/Terrain/ScriptBindings/Terrains.txt + Runtime/Terrain/ScriptBindings/WindZoneBindings.txt + ; +} + +rule TerrainModule_ReportIncludes +{ + return + External/PhysX/builds/SDKs/Foundation/include + External/PhysX/builds/SDKs/Physics/include + External/PhysX/builds/SDKs/PhysXLoader/include + Projects/PrecompiledHeaders/ + ; +} + +rule TerrainModule_Init +{ + OverrideModule Terrain : GetModule_Cpp : byOverridingWithMethod : TerrainModule_ReportCpp ; + OverrideModule Terrain : GetModule_Txt : byOverridingWithMethod : TerrainModule_ReportTxt ; + OverrideModule Terrain : GetModule_Inc : byOverridingWithMethod : TerrainModule_ReportIncludes ; +} + + +#RegisterModule Terrain ;
\ No newline at end of file diff --git a/Runtime/Terrain/TerrainModuleRegistration.cpp b/Runtime/Terrain/TerrainModuleRegistration.cpp new file mode 100644 index 0000000..e178ace --- /dev/null +++ b/Runtime/Terrain/TerrainModuleRegistration.cpp @@ -0,0 +1,37 @@ +#include "UnityPrefix.h" + +#if ENABLE_TERRAIN +#include "Runtime/BaseClasses/ClassRegistration.h" +#include "Runtime/Modules/ModuleRegistration.h" + +static void RegisterTerrainClasses (ClassRegistrationContext& context) +{ + REGISTER_CLASS (TerrainData) + REGISTER_CLASS (Tree) +} + +#if ENABLE_MONO || UNITY_WINRT +void ExportTerrains (); +void ExportTerrainDataBindings (); +void ExportWindZoneBindings (); + +static void RegisterTerrainICallModule () +{ +#if !INTERNAL_CALL_STRIPPING + ExportTerrains (); + ExportTerrainDataBindings(); + ExportWindZoneBindings(); +#endif +} +#endif + +extern "C" EXPORT_MODULE void RegisterModule_Terrain () +{ + ModuleRegistrationInfo info; + info.registerClassesCallback = &RegisterTerrainClasses; +#if ENABLE_MONO || UNITY_WINRT + info.registerIcallsCallback = &RegisterTerrainICallModule; +#endif + RegisterModuleInfo (info); +} +#endif
\ No newline at end of file diff --git a/Runtime/Terrain/TerrainRenderer.cpp b/Runtime/Terrain/TerrainRenderer.cpp new file mode 100644 index 0000000..8ab2e0f --- /dev/null +++ b/Runtime/Terrain/TerrainRenderer.cpp @@ -0,0 +1,798 @@ +#include "UnityPrefix.h" +#include "TerrainRenderer.h" + +#if ENABLE_TERRAIN + +#ifdef TERRAIN_PREFER_VBO_OVER_MESH +# include "Runtime/GfxDevice/GfxDevice.h" +# include "Runtime/Terrain/QuadTreeNodeRenderer.h" +#else +# include "Runtime/Camera/IntermediateRenderer.h" +#endif +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Graphics/LightmapSettings.h" +#include "Runtime/Camera/Camera.h" + +TerrainRenderer::TerrainRenderer (SInt32 instanceID, PPtr<TerrainData> terrainData, const Vector3f &position, int lightmapIndex) +#if UNITY_EDITOR +: m_InstanceID(instanceID) +#endif +{ + m_Scale = terrainData->GetHeightmap().GetScale(); + m_Levels = terrainData->GetHeightmap().GetMipLevels(); + m_TerrainData = terrainData; + m_Position = position; + m_LeftNeighbor = NULL; + m_RightNeighbor = NULL; + m_BottomNeighbor = NULL; + m_TopNeighbor = NULL; + + m_SplatMaterials = new SplatMaterials (m_TerrainData); + + m_LightmapIndex = lightmapIndex; + + RebuildNodes (); +} + +TerrainRenderer::~TerrainRenderer () +{ + delete m_SplatMaterials; + + for (std::vector<QuadTreeNode>::iterator it = m_Quadtree.begin(), itEnd = m_Quadtree.end(); it != itEnd; ++it) + { + RemoveMesh(*it); + } + +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + for (std::list<VBO*>::iterator it = m_FreeVBOPool.begin(), itEnd = m_FreeVBOPool.end(); it != itEnd; ++it) + { + GetGfxDevice().DeleteVBO(*it); + } +#endif +} + +void TerrainRenderer::SetNeighbors (TerrainRenderer *left, TerrainRenderer *top, TerrainRenderer *right, TerrainRenderer *bottom) +{ + m_TopNeighbor = top; + m_RightNeighbor = right; + m_BottomNeighbor = bottom; + m_LeftNeighbor = left; +} + +void TerrainRenderer::SetTerrainData(PPtr<TerrainData> value) +{ + m_TerrainData = value; +} + +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + +VBO* TerrainRenderer::CreateVBO() +{ + if (!m_FreeVBOPool.empty()) + { + VBO* vbo = m_FreeVBOPool.back(); + m_FreeVBOPool.pop_back(); + return vbo; + } + + // create new VBO + return GetGfxDevice().CreateVBO(); +} + +void TerrainRenderer::ReclaimVBO(VBO* vbo) +{ + AssertIf(vbo == NULL); + m_FreeVBOPool.push_back(vbo); +} + +#endif + +int TerrainRenderer::GetIndex (int x, int y, int level) +{ + int index = 0; + for (int i=0;i<level;i++) + { + int size = 1 << (m_Levels - i); + index += size * size; + } + + int width = 1 << (m_Levels - level); + index += width * y; + index += x; + + Assert (index >= 0 && index < m_Quadtree.size()); + + return index; +} + +QuadTreeNode *TerrainRenderer::GetNode (int x, int y, int level) +{ + if (level < 0 || level > m_Levels) + return NULL; + int size = 1 << (m_Levels - level); + + if (x < 0 || x >= size || y < 0 || y >= size) + { + // Left + if (x == -1 && m_LeftNeighbor != NULL) + return m_LeftNeighbor->GetNode (size - 1, y, level); + + // right + if (x == size && m_RightNeighbor != NULL) + return m_RightNeighbor->GetNode (0, y, level); + + // top + if (y == size && m_TopNeighbor != NULL) + return m_TopNeighbor->GetNode (x, 0, level); + + // bottom + if (y == -1 && m_BottomNeighbor != NULL) + return m_BottomNeighbor->GetNode (x, size-1, level); + + return NULL; + } + + return &m_Quadtree[GetIndex(x, y, level)]; +} + +QuadTreeNode *TerrainRenderer::GetNodeAndRenderer (int x, int y, int level, TerrainRenderer *&renderer) +{ + if (level < 0 || level > m_Levels) + { + renderer = NULL; + return NULL; + } + int size = 1 << (m_Levels - level); + + if (x < 0 || x >= size || y < 0 || y >= size) + { + // Left + if (x == -1 && m_LeftNeighbor != NULL) + { + renderer = m_LeftNeighbor; + return m_LeftNeighbor->GetNode (size - 1, y, level); + } + + // right + if (x == size && m_RightNeighbor != NULL) + { + renderer = m_RightNeighbor; + return m_RightNeighbor->GetNode (0, y, level); + } + + // top + if (y == size && m_TopNeighbor != NULL) + { + renderer = m_TopNeighbor; + return m_TopNeighbor->GetNode (x, 0, level); + } + + // bottom + if (y == -1 && m_BottomNeighbor != NULL) + { + renderer = m_BottomNeighbor; + return m_BottomNeighbor->GetNode (x, size-1, level); + } + + renderer = NULL; + return NULL; + } + + renderer = this; + return &m_Quadtree[GetIndex(x, y, level)]; +} + +int TerrainRenderer::GetPatchCountX (int level) +{ + return 1 << (m_Levels - level); +} + +int TerrainRenderer::GetPatchCountY (int level) +{ + return 1 << (m_Levels - level); +} + +void TerrainRenderer::RebuildNodes () +{ + Heightmap &heightmap = m_TerrainData->GetHeightmap(); + int totalSize = heightmap.GetTotalPatchCount(); + + m_Quadtree.resize(totalSize); + for (int level=0;level <= m_Levels;level++) + { + for (int y=0;y<GetPatchCountY(level);y++) + { + for (int x=0;x<GetPatchCountX(level);x++) + { + int index = GetIndex(x, y, level); + m_Quadtree[index].x = x; + m_Quadtree[index].y = y; + m_Quadtree[index].level = level; + m_Quadtree[index].maxHeightError = heightmap.GetMaximumHeightError(x, y, level); + m_Quadtree[index].bounds = heightmap.GetBounds(x, y, level); + m_Quadtree[index].bounds.GetCenter() += m_Position; + } + } + } +} + +Vector3f TerrainRenderer::GetNodePosition (QuadTreeNode &node) +{ + Vector3f position = Vector3f::zero; + position += m_Position; + return position; +} + +int TerrainRenderer::CalculateEdgeMask (QuadTreeNode &node) +{ + //@TODO: Handle case where invisible (dont need to switch index buffers) + int mask = 0; + for (int i=kDirectionLeft;i<=kDirectionDown;i++) + { + QuadTreeNode *neighbor = FindNeighbor(node, i); + if (neighbor != NULL) + { + if (neighbor->visibility == kVisibilityDrawChild || neighbor->visibility == kVisibilityDrawSelf) + mask |= 1 << i; + } + else + mask |= 1 << i; + } + return mask; +} + +QuadTreeNode *TerrainRenderer::FindNeighbor (QuadTreeNode &node, int direction) +{ + if (direction == kDirectionUp) + return GetNode(node.x, node.y + 1, node.level); + else if (direction == kDirectionDown) + return GetNode(node.x, node.y - 1, node.level); + else if (direction == kDirectionLeft) + return GetNode(node.x - 1, node.y, node.level); + else + return GetNode(node.x + 1, node.y, node.level); +} + +QuadTreeNode *TerrainRenderer::FindNeighborAndRenderer (QuadTreeNode &node, int direction, TerrainRenderer *&renderer) +{ + if (direction == kDirectionUp) + return GetNodeAndRenderer(node.x, node.y + 1, node.level, renderer); + else if (direction == kDirectionDown) + return GetNodeAndRenderer(node.x, node.y - 1, node.level, renderer); + else if (direction == kDirectionLeft) + return GetNodeAndRenderer(node.x - 1, node.y, node.level, renderer); + else + return GetNodeAndRenderer(node.x + 1, node.y, node.level, renderer); +} + +QuadTreeNode *TerrainRenderer::FindChild (QuadTreeNode &node, int direction) +{ + if (direction == kDirectionLeftUp) + return GetNode(node.x * 2, node.y * 2, node.level - 1); + else if (direction == kDirectionRightUp) + return GetNode(node.x * 2 + 1, node.y * 2, node.level - 1); + else if (direction == kDirectionLeftDown) + return GetNode(node.x * 2, node.y * 2 + 1, node.level - 1); + else + return GetNode(node.x * 2 + 1, node.y * 2 + 1, node.level - 1); +} + +QuadTreeNode *TerrainRenderer::FindParent (QuadTreeNode &node) +{ + return GetNode(node.x / 2, node.y / 2, node.level + 1); +} + +void TerrainRenderer::RenderStep1 (Camera *camera, int maxLodLevel, float tau, float splatDistance) +{ + // clamp LOD level to valid values + maxLodLevel = clamp(maxLodLevel, 0, m_Levels); + + float nearClip = camera->GetNear(); + float vTop = nearClip * tanf( Deg2Rad( camera->GetFov() / 2.0F ) ); + float vres = camera->GetScreenViewportRect().height; + + float A = nearClip / fabs(vTop); + float T = 2 * tau / (float)vres; + float kC = A / T; + + QuadTreeNode& root = GetRootNode(); + + m_CachedCameraPosition = camera-> QueryComponent(Transform)->GetPosition(); + m_CachedMaxLodLevel = maxLodLevel; + m_CachedkC = kC; + m_CachedSqrSplatDistance = splatDistance * splatDistance; + + RecursiveCalculateLod(root); +} + +void TerrainRenderer::RenderStep2 () +{ + QuadTreeNode& root = GetRootNode(); + + EnforceLodTransitions(root); +} + +void TerrainRenderer::RenderStep3 (Camera *camera, int layer, bool castShadows, Material* mat) +{ + QuadTreeNode &root = GetRootNode(); + + m_CurrentLayer = layer; + m_CurrentCamera = camera; + m_CastShadows = castShadows; + + // Get Splat materials + m_CurrentMaterials = m_SplatMaterials->GetMaterials (mat, m_CurrentMaterialCount); + m_CurrentBaseMaterial = m_SplatMaterials->GetSplatBaseMaterial (mat); + + RecursiveRenderMeshes(root, m_TerrainData->GetHeightmap()); +} + + +void TerrainRenderer::CleanupMeshes () +{ + for (std::vector<QuadTreeNode>::iterator node=m_Quadtree.begin();node != m_Quadtree.end(); node++) + { + if (node->visibility == kVisibilityDrawSelf) + RemoveMesh(*node); + node->visibility = kVisibilityLoddedAway; + node->oldVisibility = kVisibilityLoddedAway; + } +} + +void TerrainRenderer::ReloadAll () { + Cleanup (); + + m_Scale = m_TerrainData->GetHeightmap().GetScale(); + m_Levels = m_TerrainData->GetHeightmap().GetMipLevels(); + + + delete m_SplatMaterials; + m_SplatMaterials = new SplatMaterials (m_TerrainData); + + RebuildNodes (); +} + +void TerrainRenderer::Cleanup () { + CleanupMeshes(); + m_SplatMaterials->Cleanup (); +} + +void TerrainRenderer::UnloadVBOFromGfxDevice () +{ +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + // This will put all VBOs back into m_FreeVBOPool + for (std::vector<QuadTreeNode>::iterator node=m_Quadtree.begin();node != m_Quadtree.end(); node++) + { + RemoveMesh(*node); + node->updateIndices = true; + node->updateMesh = true; + } + + for (std::list<VBO*>::iterator it = m_FreeVBOPool.begin(), itEnd = m_FreeVBOPool.end(); it != itEnd; ++it) + { + GetGfxDevice().DeleteVBO(*it); + } + m_FreeVBOPool.clear(); +#endif +} + +void TerrainRenderer::ReloadVBOToGfxDevice () +{ + // No need to do anything, since QuadTreeNodeRenderers will recreate themselves +} + +float TerrainRenderer::CalculateSqrDistance (Vector3f &rkPoint, AABB &rkBox) +{ + // compute coordinates of point in box coordinate system + Vector3f kClosest = rkPoint - rkBox.GetCenter(); + + // project test point onto box + float fSqrDistance = 0.0f; + float fDelta; + Vector3f& extents = rkBox.GetExtent(); + + for (int i=0;i<3;i++) + { + if ( kClosest[i] < -extents[i] ) + { + fDelta = kClosest[i] + extents[i]; + fSqrDistance += fDelta * fDelta; + kClosest[i] = -extents[i]; + } + else if ( kClosest[i] > extents[i] ) + { + fDelta = kClosest[i] - extents[i]; + fSqrDistance += fDelta * fDelta; + kClosest[i] = extents[i]; + } + } + + return fSqrDistance; +} + +void TerrainRenderer::MarkChildVisibilityRecurse (QuadTreeNode &node, int newVisibility) +{ + if (node.level == 0) + return; + + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode* childNode = FindChild (node, i); + if (childNode->visibility != newVisibility) + { + childNode->visibility = newVisibility; + MarkChildVisibilityRecurse(*childNode, newVisibility); + } + } +} + +void TerrainRenderer::ReloadPrecomputedError () +{ + Heightmap &heightmap = m_TerrainData->GetHeightmap(); + for (std::vector<QuadTreeNode>::iterator node=m_Quadtree.begin();node != m_Quadtree.end(); node++) + node->maxHeightError = heightmap.GetMaximumHeightError(node->x, node->y, node->level); +} + +void TerrainRenderer::ReloadBounds () +{ + Heightmap &heightmap = m_TerrainData->GetHeightmap(); + for (std::vector<QuadTreeNode>::iterator node=m_Quadtree.begin();node != m_Quadtree.end(); node++) + { + node->bounds = heightmap.GetBounds(node->x, node->y, node->level); + node->bounds.GetCenter() += m_Position; + } +} + +void TerrainRenderer::RecursiveCalculateLod (QuadTreeNode &node) +{ + // Distance to bounding volume based + float distance = CalculateSqrDistance(m_CachedCameraPosition, node.bounds); + + float D2 = m_CachedkC * node.maxHeightError; + D2 *= D2; + + // Node has good enough lod, render it + if (distance > D2 || node.level == m_CachedMaxLodLevel) + { + node.visibility = kVisibilityDrawSelf; + node.useSplatmap = distance < m_CachedSqrSplatDistance; + MarkChildVisibilityRecurse(node, kVisibilityLoddedAway); + } + // Node needs subdivision + else + { + node.visibility = kVisibilityDrawChild; + + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode* childNode = FindChild (node, i); + RecursiveCalculateLod (*childNode); + } + } +} + +void TerrainRenderer::ForceSplitParent (QuadTreeNode &node) +{ + QuadTreeNode* parent = FindParent(node); + Assert (parent != NULL); + if (parent->visibility == kVisibilityLoddedAway) + ForceSplitParent (*parent); + + if (parent->visibility == kVisibilityDrawSelf) + { + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode* childNode = FindChild (*parent, i); + Assert (childNode != NULL); + + childNode->visibility = kVisibilityDrawSelf; + childNode->useSplatmap = CalculateSqrDistance(m_CachedCameraPosition, childNode->bounds) < m_CachedSqrSplatDistance; + + MarkChildVisibilityRecurse(*childNode, kVisibilityLoddedAway); + } + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode *childNode = FindChild (*parent, i); + Assert (childNode != NULL); + + EnforceLodTransitions(*childNode); + } + parent->visibility = kVisibilityDrawChild; + } +} + +// Go through all nodes that are being rendered +// Check if the parent neighbor +void TerrainRenderer::EnforceLodTransitions (QuadTreeNode &node) +{ + if (node.visibility == kVisibilityLoddedAway) + return; + + if (node.visibility == kVisibilityDrawSelf) + { + for (int i=kDirectionLeft;i<=kDirectionDown;i++) + { + TerrainRenderer *renderer; + + // If we have a neighbor, make sure it's either a root node that's completely LOD'd away + // or that it's at most one detail level away from us (i.e. that the neighbor's tree is split down at + // least to the level of the neighbor's parent). + QuadTreeNode *neighbor = FindNeighborAndRenderer (node, i, renderer); + if (neighbor != NULL && neighbor->visibility == kVisibilityLoddedAway && !renderer->IsRootNode (*neighbor)) + { + QuadTreeNode *neighborParent = renderer->FindParent(*neighbor); + Assert (neighborParent != NULL); + + if (neighborParent->visibility == kVisibilityLoddedAway) + { + renderer->ForceSplitParent(*neighborParent); + } + } + } + } + else + { + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode *childNode = FindChild (node, i); + Assert (childNode != NULL); + + EnforceLodTransitions(*childNode); + } + } +} + +void TerrainRenderer::RemoveMesh (QuadTreeNode &node) +{ +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + if (node.vbo != NULL) + { + ReclaimVBO(node.vbo); + node.vbo = NULL; + node.updateMesh = false; + node.updateIndices = false; + } +#else + // Cleanup self + if(node.mesh) + { + DestroySingleObject (node.mesh); + node.mesh = NULL; + } +#endif +} + +void TerrainRenderer::RecursiveRemoveMeshes (QuadTreeNode &node) +{ + if (node.oldVisibility == kVisibilityLoddedAway) + return; + + if (node.oldVisibility == kVisibilityDrawSelf) + { + RemoveMesh(node); + } + else if (node.oldVisibility == kVisibilityDrawChild) + { + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode* childNode = FindChild (node, i); + RecursiveRemoveMeshes(*childNode); + } + } + + node.oldVisibility = kVisibilityLoddedAway; +} + +void TerrainRenderer::RenderNode (QuadTreeNode &node) +{ + int layer = m_CurrentLayer; + Camera *camera = m_CurrentCamera; + Vector3f position = GetNodePosition(node); + + Matrix4x4f matrix; + matrix.SetTranslate( position ); + bool castShadows = m_CastShadows; + + if (node.useSplatmap) + { + int count = m_CurrentMaterialCount; + for (int m=0;m<count;++m) + { + Material* mat = m_CurrentMaterials[m]; +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + QuadTreeNodeRenderer* r = AddQuadTreeNodeRenderer( matrix, node.subMesh.localAABB, mat, layer, castShadows, true, camera ); + r->Setup( this, &node ); +#else + IntermediateRenderer* r = AddIntermediateRenderer (matrix, node.mesh, mat, layer, castShadows, true, 0, camera); +#endif + r->SetLightmapIndexIntNoDirty(m_LightmapIndex); + #if UNITY_EDITOR + r->SetScaleInLightmap(m_LightmapSize); + r->SetInstanceID (m_InstanceID); + #endif + } + } + else + { +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + QuadTreeNodeRenderer* r = AddQuadTreeNodeRenderer( matrix, node.subMesh.localAABB, m_CurrentBaseMaterial, layer, castShadows, true, camera ); + r->Setup( this, &node ); +#else + IntermediateRenderer* r = new IntermediateRenderer( matrix, node.mesh, m_CurrentBaseMaterial, layer, castShadows, true, 0 ); +#endif + r->SetLightmapIndexIntNoDirty(m_LightmapIndex); + #if UNITY_EDITOR + r->SetScaleInLightmap(m_LightmapSize); + r->SetInstanceID (m_InstanceID); + #endif + } +} + +void TerrainRenderer::BuildRenderer (QuadTreeNode &node, int edgeMask) +{ + // Build mesh +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + RemoveMesh(node); +#else + DestroySingleObject (node.mesh); + + node.mesh = NEW_OBJECT (Mesh); + node.mesh->Reset(); + node.mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + node.mesh->SetHideFlags(Object::kDontSave); +#endif + + AABB bounds = m_TerrainData->GetHeightmap().GetBounds(node.x, node.y, node.level); + +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + node.subMesh.localAABB = bounds; + node.updateMesh = true; + node.updateIndices = true; +#else + node.mesh->SetBounds(bounds); + m_TerrainData->GetHeightmap().UpdatePatchMesh(*node.mesh, node.x, node.y, node.level, edgeMask, this); + m_TerrainData->GetHeightmap().UpdatePatchIndices(*node.mesh, node.x, node.y, node.level, edgeMask); + Assert(node.mesh->GetBounds().GetCenter () == bounds.GetCenter() && node.mesh->GetBounds().GetExtent() == bounds.GetExtent ()); +#endif +} + + +void TerrainRenderer::RecursiveRenderMeshes (QuadTreeNode &node, Heightmap &heightmap) +{ + // The node itself needs to be rendered + if (node.visibility == kVisibilityDrawSelf) + { + int newEdgeMask = CalculateEdgeMask(node); + // We didn't render the node last frame. So we need to build from scratch + if (node.oldVisibility != kVisibilityDrawSelf) + { + BuildRenderer(node, newEdgeMask); + node.edgeMask = newEdgeMask; + RenderNode(node); + } + // If highest lod error is infinity we rebuild the mesh constantly + // (This is set while the terrain is being painted) + else if (node.maxHeightError == std::numeric_limits<float>::infinity()) + { +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + node.updateMesh = true; +#else + heightmap.UpdatePatchMesh(*node.mesh, node.x, node.y, node.level, newEdgeMask, this); +#endif + if (newEdgeMask != node.edgeMask) + { +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + node.updateIndices = true; +#else + heightmap.UpdatePatchIndices(*node.mesh, node.x, node.y, node.level, newEdgeMask); +#endif + node.edgeMask = newEdgeMask; + } + + RenderNode(node); + } + // The edge mask has changed, we need to update the triangle indices + else if (newEdgeMask != node.edgeMask) + { +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + node.updateIndices = true; +#else + heightmap.UpdatePatchIndices(*node.mesh, node.x, node.y, node.level, newEdgeMask); +#endif + node.edgeMask = newEdgeMask; + RenderNode(node); + } + // Nothing changed just paint + else + { + RenderNode(node); + } + + // All old children need to be removed! + if (node.oldVisibility == kVisibilityDrawChild) + { + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode *childNode = FindChild (node, i); + Assert (childNode != NULL); + RecursiveRemoveMeshes (*childNode); + } + } + } + // Some children of the node need to be rendered + else if (node.visibility == kVisibilityDrawChild) + { + // Remove the old render mesh + if (node.oldVisibility == kVisibilityDrawSelf) + RemoveMesh (node); + + // Recurse into children + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode *childNode = FindChild (node, i); + RecursiveRenderMeshes (*childNode, heightmap); + } + } + // Node is invisible + else + { + // All old children need to be removed! + if (node.oldVisibility == kVisibilityDrawChild) + { + for (int i=kDirectionLeftUp;i<=kDirectionRightDown;i++) + { + QuadTreeNode *childNode = FindChild (node, i); + RecursiveRemoveMeshes (*childNode); + } + } + // Remove the old render mesh + else if (node.oldVisibility == kVisibilityDrawSelf) + RemoveMesh (node); + } + + node.oldVisibility = node.visibility; +} + +Mesh* GetMeshForPatch(int x, int y, int level, Heightmap& heightmap, TerrainRenderer* terrainRenderer) +{ + // Build mesh + Mesh* mesh = NEW_OBJECT (Mesh); + mesh->Reset(); + mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + mesh->SetHideFlags(Object::kDontSave); + + // judging by CalculateEdgeMask() the edgeMask equal to 15 means all the neighbouring + // patches are visible + heightmap.UpdatePatchMesh(*mesh, x, y, level, 15, terrainRenderer); + heightmap.UpdatePatchIndices(*mesh, x, y, level, 15); + + mesh->RecalculateBounds(); + + return mesh; +} + +std::vector<Mesh*> TerrainRenderer::GetMeshPatches() +{ + std::vector<Mesh*> meshes; + Heightmap &heightmap = m_TerrainData->GetHeightmap(); + + const int level = 0; + int xPatches = GetPatchCountX(level); + int yPatches = GetPatchCountY(level); + + for (int y = 0; y < yPatches; y++) + { + for (int x = 0; x < xPatches; x++) + { + Mesh* mesh = GetMeshForPatch(x, y, level, heightmap, this); + mesh->SetName(Format("%s[%i][%i]", m_TerrainData->GetName(), x, y).c_str()); + meshes.push_back(mesh); + } + } + + return meshes; +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TerrainRenderer.h b/Runtime/Terrain/TerrainRenderer.h new file mode 100644 index 0000000..d9bf683 --- /dev/null +++ b/Runtime/Terrain/TerrainRenderer.h @@ -0,0 +1,168 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/Math/Vector3.h" +#include "Runtime/Geometry/AABB.h" +#include "TerrainData.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Shaders/Material.h" +#include "SplatMaterials.h" + +#define TERRAIN_PREFER_VBO_OVER_MESH + +class Camera; + +enum +{ + kVisibilityLoddedAway = 0, + kVisibilityDrawChild = 1, + kVisibilityDrawSelf = 2 +}; + +struct QuadTreeNode +{ +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + VBO *vbo; + SubMesh subMesh; +#else + Mesh *mesh; +#endif + int edgeMask; + float maxHeightError; + + int visibility; + int oldVisibility; + + int x; + int y; + int level; + AABB bounds; + bool useSplatmap : 1; + +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + bool updateMesh : 1; + bool updateIndices : 1; +#endif + + QuadTreeNode() + { + edgeMask = -1; + maxHeightError = 1; + visibility = kVisibilityLoddedAway; + oldVisibility = kVisibilityLoddedAway; +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + vbo = NULL; + updateMesh = false; + updateIndices = false; +#else + mesh = NULL; +#endif + } + + ~QuadTreeNode() + { +#ifdef TERRAIN_PREFER_VBO_OVER_MESH + AssertIf(vbo != NULL); +#else + DestroySingleObject(mesh); +#endif + } +}; + +class TerrainRenderer +{ +public: + TerrainRenderer *m_TopNeighbor; + TerrainRenderer *m_RightNeighbor; + TerrainRenderer *m_BottomNeighbor; + TerrainRenderer *m_LeftNeighbor; + + Vector3f m_Scale; + std::vector<QuadTreeNode> m_Quadtree; + int m_Levels; + SplatMaterials *m_SplatMaterials; + Vector3f m_Position; + bool m_CastShadows; + + Vector3f m_CachedCameraPosition; + int m_CachedMaxLodLevel; + float m_CachedkC; + float m_CachedSqrSplatDistance; + + TerrainRenderer (SInt32 instanceID, PPtr<TerrainData> terrainData, const Vector3f &position, int lightmapIndex); + ~TerrainRenderer (); + void RenderStep1 (Camera *camera, int maxLodLevel, float tau, float splatDistance); + void RenderStep2 (); + void RenderStep3 (Camera *camera, int layer, bool castShadow, Material* mat); + void ReloadAll (); + void Cleanup (); + void UnloadVBOFromGfxDevice (); + void ReloadVBOToGfxDevice (); + void ReloadPrecomputedError (); + void ReloadBounds (); + void RecursiveCalculateLod (QuadTreeNode &node); + void SetNeighbors (TerrainRenderer *left, TerrainRenderer *top, TerrainRenderer *right, TerrainRenderer *bottom); + + PPtr<TerrainData> GetTerrainData() const { return m_TerrainData; } + void SetTerrainData(PPtr<TerrainData> value); + + int GetLightmapIndex() { return m_LightmapIndex; } + void SetLightmapIndex(int value) { m_LightmapIndex = value; } + + std::vector<Mesh*> GetMeshPatches(); + + #if UNITY_EDITOR + void SetLightmapSize(int lightmapSize) { m_LightmapSize = lightmapSize; } + #endif + +private: + + int m_CurrentLayer; + Camera *m_CurrentCamera; + Material *m_CurrentBaseMaterial; + int m_CurrentMaterialCount; + Material **m_CurrentMaterials; + PPtr<TerrainData> m_TerrainData; + int m_LightmapIndex; + #if UNITY_EDITOR + SInt32 m_InstanceID; + int m_LightmapSize; + #endif + + std::list<VBO*> m_FreeVBOPool; + VBO* CreateVBO(); + void ReclaimVBO(VBO* vbo); + + QuadTreeNode &GetRootNode() { return m_Quadtree.back(); } + bool IsRootNode (const QuadTreeNode& node) { return (node.level == GetRootNode().level); } + + + int GetIndex (int x, int y, int level); + QuadTreeNode *GetNode (int x, int y, int level); + QuadTreeNode *GetNodeAndRenderer (int x, int y, int level, TerrainRenderer *&renderer); + int GetPatchCountX (int level); + int GetPatchCountY (int level); + void RebuildNodes (); + Vector3f GetNodePosition (QuadTreeNode &node); + int CalculateEdgeMask (QuadTreeNode &node); + QuadTreeNode *FindNeighbor (QuadTreeNode &node, int direction); + QuadTreeNode *FindNeighborAndRenderer (QuadTreeNode &node, int direction, TerrainRenderer *&renderer); + QuadTreeNode *FindChild (QuadTreeNode &node, int direction); + QuadTreeNode *FindParent (QuadTreeNode &node); + void CleanupMeshes (); + static float CalculateSqrDistance (Vector3f &rkPoint, AABB &rkBox); + void MarkChildVisibilityRecurse (QuadTreeNode &node, int newVisibility); + void ForceSplitParent (QuadTreeNode &node); + void EnforceLodTransitions (QuadTreeNode &node); + void RemoveMesh (QuadTreeNode &node); + void RecursiveRemoveMeshes (QuadTreeNode &node); + void RenderNode (QuadTreeNode &node); + void BuildRenderer (QuadTreeNode &node, int edgeMask); + void RecursiveRenderMeshes (QuadTreeNode &node, Heightmap &heightmap); + + friend class QuadTreeNodeRenderer; +}; + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/Tree.cpp b/Runtime/Terrain/Tree.cpp new file mode 100644 index 0000000..630e319 --- /dev/null +++ b/Runtime/Terrain/Tree.cpp @@ -0,0 +1,78 @@ +#include "UnityPrefix.h" +#include "Tree.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Shaders/MaterialProperties.h" +#include "Runtime/Filters/Mesh/MeshRenderer.h" +#include "External/shaderlab/Library/FastPropertyName.h" +#include "Wind.h" +#include "Runtime/Mono/MonoBehaviour.h" +#include "Runtime/BaseClasses/SupportedMessageOptimization.h" + +IMPLEMENT_CLASS_HAS_INIT(Tree) +IMPLEMENT_OBJECT_SERIALIZE(Tree) + +SHADERPROP(Wind); + +Tree::Tree(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + +Tree::~Tree() +{ +} + +void Tree::InitializeClass () +{ + REGISTER_MESSAGE_VOID (Tree, kOnWillRenderObject, OnWillRenderObject); +} + +UInt32 Tree::CalculateSupportedMessages () +{ + return kHasOnWillRenderObject; +} + + +template<class TransferFunc> +void Tree::Transfer (TransferFunc& transfer) +{ + Super::Transfer(transfer); + TRANSFER_EDITOR_ONLY(m_TreeData); +} + +void Tree::SetTreeData (PPtr<MonoBehaviour> tree) +{ + #if UNITY_EDITOR + m_TreeData = tree; + SetDirty(); + #endif +} + +PPtr<MonoBehaviour> Tree::GetTreeData () +{ + #if UNITY_EDITOR + return m_TreeData; + #else + return NULL; + #endif +} + +void Tree::OnWillRenderObject() +{ + MeshRenderer* renderer = QueryComponent(MeshRenderer); + if (renderer == NULL) + return; + + AABB bounds; + renderer->GetWorldAABB(bounds); + + // Compute wind factor from wind zones + Vector4f wind = WindManager::GetInstance().ComputeWindForce(bounds); + + // Apply material property block + MaterialPropertyBlock& block = renderer->GetPropertyBlockRememberToUpdateHash (); + block.Clear(); + block.AddPropertyVector(kSLPropWind, wind); + renderer->ComputeCustomPropertiesHash(); +} diff --git a/Runtime/Terrain/Tree.h b/Runtime/Terrain/Tree.h new file mode 100644 index 0000000..af4e069 --- /dev/null +++ b/Runtime/Terrain/Tree.h @@ -0,0 +1,36 @@ +#ifndef TREE_H +#define TREE_H + +#include "Runtime/BaseClasses/GameObject.h" + +class MonoBehaviour; + + + +class Tree : public Unity::Component +{ +public: +public: + REGISTER_DERIVED_CLASS(Tree, Component) + DECLARE_OBJECT_SERIALIZE(Tree) + + Tree(MemLabelId label, ObjectCreationMode mode); + // ~Tree(); declared by a macro + + void SetTreeData (PPtr<MonoBehaviour> tree); + PPtr<MonoBehaviour> GetTreeData (); + + static void InitializeClass (); + static void CleanupClass () {} + + void OnWillRenderObject (); + +private: + UInt32 CalculateSupportedMessages (); + + #if UNITY_EDITOR + PPtr<MonoBehaviour> m_TreeData; + #endif +}; + +#endif diff --git a/Runtime/Terrain/TreeDatabase.cpp b/Runtime/Terrain/TreeDatabase.cpp new file mode 100644 index 0000000..bec9140 --- /dev/null +++ b/Runtime/Terrain/TreeDatabase.cpp @@ -0,0 +1,283 @@ +#include "UnityPrefix.h" +#include "TreeDatabase.h" + +#if ENABLE_TERRAIN + +#include "TerrainData.h" +#include "Runtime/Filters/Mesh/LodMeshFilter.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Scripting/Scripting.h" + +TreeDatabase::Prototype::Prototype() +: treeHeight (1) +, treeVisibleHeight (1) +, treeWidth (1) +, treeAspectRatio (1) +, bounds (Vector3f::zero, Vector3f::zero) +, bendFactor (0) +{ + prefab = NULL; + mesh = NULL; +} + +TreeDatabase::Prototype::~Prototype() +{ + for (std::vector<Material*>::iterator it = imposterMaterials.begin(), end = imposterMaterials.end(); it != end; ++it) + DestroySingleObject(*it); + + for (std::vector<Material*>::iterator it = materials.begin(), end = materials.end(); it != end; ++it) + DestroySingleObject(*it); +} + +void TreeDatabase::Prototype::Set(const PPtr<Unity::GameObject>& source, float inBendFactor, const TerrainData& terrainData) +{ + prefab = source; + if (!source) + { + ErrorStringObject( + Format( + "A tree couldn't be loaded because the prefab is missing.\nPlease select any instance of the %s asset in a scene and make it reference only tree prefabs that exist.", + terrainData.GetName ()), + &terrainData); + return; + } + + MeshFilter* filter = prefab->QueryComponent(MeshFilter); + Renderer* renderer = prefab->QueryComponent(Renderer); + + if (!filter || !filter->GetSharedMesh() || !renderer) + { + WarningStringObject(std::string() + "The tree " + source->GetName() + " couldn't be instanced because the prefab contains no valid mesh renderer", source); + return; + } + + mesh = filter->GetSharedMesh(); + const Renderer::MaterialArray& originalMaterials = renderer->GetMaterialArray(); + for (int m = 0; m < originalMaterials.size(); ++m) { + if (!originalMaterials[m]) + { + WarningStringObject(std::string() + "The tree " + source->GetName() + " couldn't be instanced because one of the materials is missing", source); + return; + } + } + + // * Create cloned imposter materials using special billboard shader + // * Create cloned normal materials so we can modify the color in place + // * Setup orginal colors array + //materials = originalMaterials; + imposterMaterials.resize(originalMaterials.size()); + materials.resize(originalMaterials.size()); + originalMaterialColors.resize(originalMaterials.size()); + inverseAlphaCutoff.resize(originalMaterials.size()); + + for (int m = 0; m < originalMaterials.size(); m++) + { + if (!SetMaterial (m, originalMaterials[m])) + { + WarningStringObject(std::string() + "The tree " + source->GetName() + " must use the Nature/Soft Occlusion shader. Otherwise billboarding/lighting will not work correctly.", source); + } + } + + + bounds = mesh->GetBounds(); + MinMaxAABB minMaxBounds(bounds); + treeVisibleHeight = minMaxBounds.GetMax().y; + treeHeight = bounds.GetExtent().y * 2; + + float x = std::max(std::abs(minMaxBounds.GetMin().x), std::abs(minMaxBounds.GetMax().x)); + float z = std::max(std::abs(minMaxBounds.GetMin().z), std::abs(minMaxBounds.GetMax().z)); + + treeWidth = std::max(x, z) * 1.9F; + + treeAspectRatio = treeWidth / treeHeight; + bendFactor = inBendFactor; +} + + +bool TreeDatabase::Prototype::SetMaterial (int index, Material* material) +{ + if (index < 0 || index >= materials.size()) + return true; + + const ShaderLab::FastPropertyName colorProperty = ShaderLab::Property("_Color"); + const ShaderLab::FastPropertyName cutoffProperty = ShaderLab::Property("_Cutoff"); + + if (material->HasProperty(colorProperty)) + originalMaterialColors[index] = material->GetColor(colorProperty); + else + originalMaterialColors[index].Set(1, 1, 1, 1); + + inverseAlphaCutoff[index] = 1.0F; + if (material->HasProperty(cutoffProperty)) + inverseAlphaCutoff[index] = 0.5F / material->GetFloat(cutoffProperty); + + // Instantiate normal material. + if (materials[index]) + DestroySingleObject (materials[index]); + materials[index] = Material::CreateMaterial (*material, Object::kHideAndDontSave); + + // Instantiate a special billboarding material. + // Eg. leaves shader needs specialized premultiplied alpha rendering into render tex. + if (imposterMaterials[index]) + DestroySingleObject (imposterMaterials[index]); + imposterMaterials[index] = Material::CreateMaterial (*material, Object::kHideAndDontSave); + Shader* imposterShader = imposterMaterials[index]->GetShader()->GetDependency ("BillboardShader"); + if (!imposterShader) + return false; + + imposterMaterials[index]->SetShader(imposterShader); + return true; +} + + +TreeDatabase::TreeDatabase (TerrainData& source) +: m_SourceData(source) +{ +} + +void TreeDatabase::RefreshPrototypes () +{ + m_Prototypes.clear(); + + const vector<TreePrototype>& sourcePrototypes = GetTreePrototypes(); + m_Prototypes.resize(sourcePrototypes.size()); + for (int i = 0; i < m_Prototypes.size(); ++i) + m_Prototypes[i].Set(sourcePrototypes[i].prefab, sourcePrototypes[i].bendFactor, m_SourceData); + + m_SourceData.UpdateUsers (TerrainData::kFlushEverythingImmediately); +} + + +// TODO: make sure all trees are within bounds. +void TreeDatabase::ValidateTrees () +{ + int prototypeCount = m_TreePrototypes.size(); + for (vector<TreeInstance>::iterator i = m_Instances.begin(); i != m_Instances.end(); i++) + { + i->position.x = clamp01(i->position.x); + i->position.y = clamp01(i->position.y); + i->position.z = clamp01(i->position.z); + i->index = clamp(i->index,0,prototypeCount - 1); + } +} + +void TreeDatabase::RecalculateTreePositions () +{ + Heightmap *heightmap = &m_SourceData.GetHeightmap(); + Vector3f terrainSize = heightmap->GetSize(); + for (int i = 0; i < m_Instances.size(); i++) + { + Vector3f pos = m_Instances[i].position; + pos.y = heightmap->GetInterpolatedHeight(pos.x, pos.z) / terrainSize.y; + m_Instances[i].position = pos; + } + + UpdateTreeInstances(); +} + +void TreeDatabase::AddTree (const TreeInstance& tree) +{ + m_Instances.push_back(tree); + + const Heightmap& heightmap = m_SourceData.GetHeightmap(); + + float height = heightmap.GetInterpolatedHeight(tree.position.x, tree.position.z); + height /= heightmap.GetSize().y; + m_Instances.back().position.y = height; + + UpdateTreeInstances(); +} + +int TreeDatabase::RemoveTrees (const Vector2f& position, float radius, int prototypeIndex) +{ + float sqrRadius = radius * radius; + std::vector<TreeInstance> instances; + instances.reserve(m_Instances.size()); + for (int i=0; i<m_Instances.size(); ++i) + { + const TreeInstance& instance = m_Instances[i]; + Vector2f offset(instance.position.x - position.x, instance.position.z - position.y); + bool shouldRemovePrototypeIndex = prototypeIndex == instance.index || prototypeIndex == -1; + if (!shouldRemovePrototypeIndex || SqrMagnitude(offset) > sqrRadius) + instances.push_back(instance); + } + + int removedTrees = 0; + if (m_Instances.size() != instances.size()) + { + removedTrees = m_Instances.size() - instances.size(); + m_Instances = instances; + + UpdateTreeInstances(); + } + return removedTrees; +} + +void TreeDatabase::UpdateTreeInstances () +{ + ValidateTrees (); + m_SourceData.SetDirty (); + m_SourceData.UpdateUsers (TerrainData::kTreeInstances); +} + +void TreeDatabase::SetTreePrototypes (const vector<TreePrototype> &treePrototypes) +{ + m_TreePrototypes = treePrototypes; + ValidateTrees (); + + RefreshPrototypes(); + + m_SourceData.SetDirty (); +} + +void TreeDatabase::RemoveTreePrototype (int index) +{ +#if UNITY_EDITOR + + if( index < 0 || index >= m_TreePrototypes.size() ) + { + ErrorString("invalid tree prototype index"); + return; + } + + // erase tree prototype + m_TreePrototypes.erase( m_TreePrototypes.begin() + index ); + + // update tree instance indices + for( vector<TreeInstance>::iterator it = m_Instances.begin(); it != m_Instances.end(); /**/ ) + { + if( it->index == index ) + { + it = m_Instances.erase( it ); + } + else + { + if( it->index > index ) + it->index--; + ++it; + } + } + + m_SourceData.SetDirty(); + + RefreshPrototypes(); + +#else + ErrorString("only implemented in editor"); +#endif +} + + +void TreePrototypeToMono (const TreePrototype &src, MonoTreePrototype &dest) { + dest.prefab = Scripting::ScriptingWrapperFor (src.prefab); + dest.bendFactor = src.bendFactor; +} +void TreePrototypeToCpp (MonoTreePrototype &src, TreePrototype &dest) { + dest.prefab = ScriptingObjectToObject<GameObject> (src.prefab); + dest.bendFactor = src.bendFactor; +} + + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TreeDatabase.h b/Runtime/Terrain/TreeDatabase.h new file mode 100644 index 0000000..6f07e8c --- /dev/null +++ b/Runtime/Terrain/TreeDatabase.h @@ -0,0 +1,152 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "Runtime/BaseClasses/BaseObject.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Filters/Mesh/LodMesh.h" + +#include <vector> + +class Vector2f; +class ColorRGBAf; +class TerrainData; +struct TreeInstance; + +namespace Unity +{ + class GameObject; + class Material; +} + +struct TreeInstance +{ + DECLARE_SERIALIZE (TreeInstance) + + Vector3f position; + float widthScale; + float heightScale; + ColorRGBA32 color; + ColorRGBA32 lightmapColor; + int index; + float temporaryDistance; +}; + +template<class TransferFunc> +void TreeInstance::Transfer (TransferFunc& transfer) +{ + TRANSFER (position); + TRANSFER (widthScale); + TRANSFER (heightScale); + TRANSFER (color); + TRANSFER (lightmapColor); + TRANSFER (index); +} + +struct TreePrototype +{ + DECLARE_SERIALIZE (TreePrototype) + + PPtr<GameObject> prefab; + float bendFactor; + + TreePrototype () + { + bendFactor = 1.0F; + } +}; + + +struct MonoTreePrototype { + ScriptingObjectPtr prefab; + float bendFactor; +}; +void TreePrototypeToMono (const TreePrototype &src, MonoTreePrototype &dest) ; +void TreePrototypeToCpp (MonoTreePrototype &src, TreePrototype &dest); + +template<class TransferFunc> +void TreePrototype::Transfer (TransferFunc& transfer) +{ + TRANSFER (prefab); + TRANSFER (bendFactor); +} + +class TreeDatabase +{ +public: + class Prototype + { + public: + Prototype(); + ~Prototype(); + + void Set (const PPtr<Unity::GameObject>& source, float inBendFactor, const TerrainData& terrainData); + bool SetMaterial (int index, Material* material); + + // if a tree is more tall than wide then we want to use square billboards, + // because tree has to fit into billboard when looking from above (or bellow) + float getBillboardAspect() const { return std::min<float>(1.f, treeAspectRatio); } + float getBillboardHeight() const { return std::max<float>(treeHeight, treeWidth); } + + // offset of center point of the tree from the ground (i.e. from pivot point/root of the tree) + float getCenterOffset() const { return treeVisibleHeight - treeHeight / 2; } + + public: + PPtr<Unity::GameObject> prefab; + PPtr<Mesh> mesh; + + std::vector<float> inverseAlphaCutoff; + std::vector<Material*> materials; + std::vector<ColorRGBAf> originalMaterialColors; + std::vector<Material*> imposterMaterials; + + // actual tree height from the bottom to the top + float treeHeight; + // visible tree height, i.e. the part which is above terrain (i.e. from pivot point/root of the tree to the top) + float treeVisibleHeight; + // The width of the tree + float treeWidth; + // How wide is the tree relative to the height. (Usually less than 1) + float treeAspectRatio; + AABB bounds; + float bendFactor; + }; + + typedef std::vector<Prototype> PrototypeVector; + +public: + TreeDatabase (TerrainData& source); + + void AddTree (const TreeInstance& tree); + int RemoveTrees (const Vector2f& position, float radius, int prototypeIndex); + + TerrainData& GetTerrainData() const { return m_SourceData; } + + PrototypeVector& GetPrototypes() { return m_Prototypes; } + const std::vector<Prototype>& GetPrototypes() const { return m_Prototypes; } + + + std::vector<TreeInstance> &GetInstances () { return m_Instances; } + std::vector<TreePrototype> &GetTreePrototypes () { return m_TreePrototypes; } + + void SetTreePrototypes (const std::vector<TreePrototype> &treePrototypes ); + void RefreshPrototypes (); + void RemoveTreePrototype (int index); + + void UpdateTreeInstances (); + void RecalculateTreePositions (); + void ValidateTrees (); + +private: + TerrainData& m_SourceData; + std::vector<TreePrototype> m_TreePrototypes; + std::vector<TreeInstance> m_Instances; + + PrototypeVector m_Prototypes; +}; + +#endif // ENABLE_TERRAIN + + diff --git a/Runtime/Terrain/TreeRenderer.cpp b/Runtime/Terrain/TreeRenderer.cpp new file mode 100644 index 0000000..6ebb3f0 --- /dev/null +++ b/Runtime/Terrain/TreeRenderer.cpp @@ -0,0 +1,1223 @@ +#include "UnityPrefix.h" +#include "TreeRenderer.h" + +#if ENABLE_TERRAIN + +#include "TerrainData.h" +#include "PerlinNoise.h" +#include "Wind.h" + +#include "Runtime/Camera/Camera.h" +#include "Runtime/Camera/Light.h" +#include "Runtime/Camera/CameraUtil.h" +#include "Runtime/Camera/IntermediateRenderer.h" +#include "Runtime/Camera/RenderSettings.h" +#include "Runtime/Camera/Shadows.h" +#include "Runtime/Geometry/Intersection.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Graphics/RenderTexture.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/Shaders/ShaderNameRegistry.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/MaterialProperties.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "External/shaderlab/Library/properties.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Allocator/MemoryMacros.h" + +static ShaderLab::FastPropertyName kTerrainTreeLightDirections[4] = { + ShaderLab::Property("_TerrainTreeLightDirections0"), + ShaderLab::Property("_TerrainTreeLightDirections1"), + ShaderLab::Property("_TerrainTreeLightDirections2"), + ShaderLab::Property("_TerrainTreeLightDirections3"), +}; +static ShaderLab::FastPropertyName kTerrainTreeLightColors[4] = { + ShaderLab::Property("_TerrainTreeLightColors0"), + ShaderLab::Property("_TerrainTreeLightColors1"), + ShaderLab::Property("_TerrainTreeLightColors2"), + ShaderLab::Property("_TerrainTreeLightColors3"), +}; + + +// when we have shadow we need to emit all renderers to let them casting shadows +#define EMIT_ALL_TREE_RENDERERS ENABLE_SHADOWS + + +// -------------------------------------------------------------------------- + + +struct TreeBinaryTree : public NonCopyable +{ + TreeBinaryTree(TreeDatabase* database) + : database(database), mesh(0), sortIndex(0), targetSortIndex(0), visible(0) + { + Assert(database != NULL); + } + + ~TreeBinaryTree() + { + DestroySingleObject(mesh); + } + + TreeDatabase* database; + std::vector<int> instances; + AABB bounds; + Mesh* mesh; + + int sortIndex; + int targetSortIndex; + int visible; + + Plane splittingPlane; + std::vector<int> usedPrototypes; + + std::auto_ptr<TreeBinaryTree> left; + std::auto_ptr<TreeBinaryTree> right; +}; + +class TreeBinaryTreeBuilder +{ +public: + static bool AddLastTree (TreeBinaryTree& binTree, const Vector3f& position, const Vector3f& scale, int minimumInstances) + { + const std::vector<TreeInstance>& instances = binTree.database->GetInstances(); + Assert(!instances.empty()); + const TreeInstance& instance = instances.back(); + + const std::vector<TreeDatabase::Prototype>& prototypes = binTree.database->GetPrototypes(); + + EncapsulateBounds(binTree.bounds, instance, prototypes, position, scale); + + // Leaf node + if (!binTree.left.get()) + { + binTree.sortIndex = -1; + if (binTree.visible != 0) + { + binTree.visible = 0; + DestroySingleObject(binTree.mesh); + binTree.mesh = 0; + } + + if (binTree.instances.empty()) + { + binTree.instances.resize(1); + binTree.instances[0] = instances.size() - 1; + binTree.bounds = CalculateBounds(instances, binTree.instances, prototypes, position, scale); + binTree.usedPrototypes = CalculateSupportedInstances(instances, binTree.instances, prototypes); + + return true; + } + else if (binTree.instances.size() < minimumInstances * 2) + { + binTree.instances.push_back(instances.size() - 1); + EncapsulateBounds(binTree.bounds, instance, prototypes, position, scale); + binTree.usedPrototypes = CalculateSupportedInstances(instances, binTree.instances, prototypes); + return true; + } + else + // Adding will actually fail once all leaves are filled up to minimumInstances * 2. + // Is that what we want? + return false; + } + else + { + const Vector3f pos = Scale(instance.position, scale); + if (binTree.splittingPlane.GetSide (pos)) + return AddLastTree(*binTree.left, position, scale, minimumInstances); + else + return AddLastTree(*binTree.right, position, scale, minimumInstances); + } + } + + static std::auto_ptr<TreeBinaryTree> Build(TreeDatabase& database, const Vector3f& position, const Vector3f& scale, int minimumInstances) + { + std::auto_ptr<TreeBinaryTree> tree(new TreeBinaryTree(&database)); + const std::vector<TreeInstance>& instances = database.GetInstances(); + if (instances.empty()) + return tree; + + tree->instances.resize(instances.size()); + for (int i = 0; i < instances.size(); ++i) + { + tree->instances[i] = i; + } + + Assert(Magnitude(scale) > 0); + Split(*tree, position, scale, minimumInstances); + return tree; + } + +private: + static void Split(TreeBinaryTree& bintree, const Vector3f& position, const Vector3f& scale, int minimumInstances) + { + const std::vector<TreeInstance>& allInstances = bintree.database->GetInstances(); + const std::vector<TreeDatabase::Prototype>& prototypes = bintree.database->GetPrototypes(); + bintree.bounds = CalculateBounds(allInstances, bintree.instances, prototypes, position, scale); + bintree.usedPrototypes = CalculateSupportedInstances(allInstances, bintree.instances, prototypes); + + // Stop splitting when we reach a certain amount of trees + if (bintree.instances.size() <= minimumInstances) + return; + + { + // We need to calculate bounds of position of all instances and then use median position (i.e. center) + // as a splitting point. We can't just use center of bintree.bounds, because it might be shifted sideways + // if bounds of each prototype is shifted and that would result in left or right bintree being empty + // (see case 345094) + AABB posBounds = CalculatePosBounds (allInstances, bintree.instances, position, scale); + + // The extent might be 0,0,0 if all instances are at the same position. In that case just stop subdiving this subtree. + // That will cause this subtree to have more instances than minimumInstances. If that's not we want, then we should + // just split the instances in half by the index. + if (CompareApproximately (posBounds.GetExtent().x, 0.0f) && CompareApproximately (posBounds.GetExtent().z, 0.0f)) + return; + + // Splitting plane is the largest axis + bintree.splittingPlane.SetNormalAndPosition( + posBounds.GetExtent().x > posBounds.GetExtent().z ? Vector3f::xAxis : Vector3f::zAxis, + posBounds.GetCenter() + ); + } + + // Now just queue up the binary tree + std::vector<int> left, right; + for (std::vector<int>::const_iterator it = bintree.instances.begin(), end = bintree.instances.end(); it != end; ++it) + { + if (bintree.splittingPlane.GetSide (Scale(allInstances[*it].position, scale) + position)) + left.push_back(*it); + else + right.push_back(*it); + } + + // Due to numerical errors we might still end up with one of the lists empty (not if the the epsilon for CompareApprox was big enough, + // but that's another story). In that case just don't split this subtree. + if (left.empty() || right.empty()) + return; + + bintree.instances.clear(); + bintree.usedPrototypes.clear(); + bintree.left.reset(new TreeBinaryTree(bintree.database)); + bintree.right.reset(new TreeBinaryTree(bintree.database)); + bintree.left->instances.swap(left); + bintree.right->instances.swap(right); + + Split (*bintree.left, position, scale, minimumInstances); + Split (*bintree.right, position, scale, minimumInstances); + } + + // Build bounds of tree positions for a bunch of trees + static AABB CalculatePosBounds (const std::vector<TreeInstance>& allInstances, const std::vector<int>& instances, const Vector3f& position, const Vector3f& scale) + { + AssertIf(instances.empty()); + AABB bounds(Scale(allInstances[instances[0]].position, scale) + position, Vector3f::zero); + for (std::vector<int>::const_iterator it = instances.begin(), end = instances.end(); it != end; ++it) + bounds.Encapsulate (Scale(allInstances[*it].position, scale) + position); + + return bounds; + } + + // Build bounds for a bunch of trees + static AABB CalculateBounds (const std::vector<TreeInstance>& allInstances, const std::vector<int>& instances, const TreeDatabase::PrototypeVector& prototypes, const Vector3f& position, const Vector3f& scale) + { + AssertIf(instances.empty()); + AABB bounds(Scale(allInstances[instances[0]].position, scale) + position, Vector3f::zero); + for (std::vector<int>::const_iterator it = instances.begin(), end = instances.end(); it != end; ++it) + EncapsulateBounds(bounds, allInstances[*it], prototypes, position, scale); + + return bounds; + } + + // Encapsulate a single tree in the bounds + static void EncapsulateBounds (AABB& bounds, const TreeInstance& instance, const TreeDatabase::PrototypeVector& prototypes, const Vector3f& position, const Vector3f& scale) + { + Vector3f pos = Scale(instance.position, scale) + position; + Vector3f treeScale(instance.widthScale, instance.heightScale, instance.widthScale); + const AABB& pb = prototypes[instance.index].bounds; + bounds.Encapsulate (pos + Scale(treeScale, pb.GetMin())); + bounds.Encapsulate (pos + Scale(treeScale, pb.GetMax())); + } + + + // Calculates an array of indices, for which prototypes are being used in the batch + static std::vector<int> CalculateSupportedInstances (const std::vector<TreeInstance>& allInstances, const std::vector<int>& instances, const TreeDatabase::PrototypeVector& prototypes) + { + std::vector<char> supported(prototypes.size(), 0); + for (std::vector<int>::const_iterator it = instances.begin(), end = instances.end(); it != end; ++it) + supported[allInstances[*it].index] = 1; + + std::vector<int> supportedIndices; + supportedIndices.reserve(prototypes.size()); + for (int i = 0; i < supported.size(); ++i) + { + if (supported[i]) + supportedIndices.push_back(i); + } + + // we do explicit copy because we want to reduce reserved size + return std::vector<int>(supportedIndices.begin(), supportedIndices.end()); + } +}; + +struct TreeInstanceIndexSorter : public std::binary_function<int, int, bool> +{ + const std::vector<TreeInstance>* m_AllInstances; + + TreeInstanceIndexSorter(const std::vector<TreeInstance>& allInstances) + : m_AllInstances(&allInstances) + { } + + bool operator ()(int lhs, int rhs) const + { + return (*m_AllInstances)[lhs].temporaryDistance > (*m_AllInstances)[rhs].temporaryDistance; + } +}; + +class TreeMeshIntermediateRenderer : public MeshIntermediateRenderer +{ +public: + static ForwardLinearAllocator* s_Allocator; + + inline void* operator new( size_t size) + { + Assert(s_Allocator != NULL); + return s_Allocator->allocate(size); + } + + inline void operator delete( void* p) + { + Assert(s_Allocator != NULL); + s_Allocator->deallocate(p); + } + + void Update(int layer, bool shadows, int lightmapIndex, const MaterialPropertyBlock& properties) + { + m_Layer = layer; + m_CastShadows = m_ReceiveShadows = shadows; + SetLightmapIndexIntNoDirty(lightmapIndex); + SetPropertyBlock(properties); + } +}; + +ForwardLinearAllocator* TreeMeshIntermediateRenderer::s_Allocator = NULL; + + +// -------------------------------------------------------------------------- + + + +const int kTreesPerBatch = 500; + +TreeRenderer::TreeRenderer(TreeDatabase& database, const Vector3f& position, int lightmapIndex) +: m_Database(0), m_BillboardMaterial(0), m_TreeBinaryTree(0), m_CloseBillboardMesh(0), m_LightmapIndex(lightmapIndex) +, m_RendererAllocator(8 * 1024, kMemTerrain) +, m_LegacyTreeSceneNodes(kMemTerrain), m_LegacyTreeBoundingBoxes(kMemTerrain), m_TreeIndexToSceneNode(kMemTerrain) +{ + const TreeDatabase::PrototypeVector& prototypes = database.GetPrototypes(); + bool anyValidTrees = false; + for (TreeDatabase::PrototypeVector::const_iterator it = prototypes.begin(), end = prototypes.end(); it != end; ++it) + { + if (!it->materials.empty()) + anyValidTrees = true; + } + + if (!anyValidTrees) + return; + + m_Database = &database; + + m_TerrainSize = database.GetTerrainData().GetHeightmap().GetSize(); + m_Position = position; + m_ImposterRenderTexture.reset(new ImposterRenderTexture(database)); + + // SetupMaterials + Shader* s = GetScriptMapper().FindShader("Hidden/TerrainEngine/BillboardTree"); + if (s == NULL) + { + ErrorString("Unable to find shaders used for the terrain engine. Please include Nature/Terrain/Diffuse shader in Graphics settings."); + s = GetScriptMapper().FindShader("Diffuse"); + } + m_BillboardMaterial = Material::CreateMaterial(*s, Object::kHideAndDontSave); + if (m_BillboardMaterial->HasProperty(ShaderLab::Property("_MainTex"))) + m_BillboardMaterial->SetTexture(ShaderLab::Property("_MainTex"), m_ImposterRenderTexture->GetTexture()); + + if (database.GetInstances().empty() || database.GetPrototypes().empty()) + return; + + + // mircea@ for some reason it doesn't compile on the PS3 if assigned directly... + std::auto_ptr<TreeBinaryTree> TreeBinaryTree = TreeBinaryTreeBuilder::Build(database, m_Position, m_TerrainSize, kTreesPerBatch); + m_TreeBinaryTree = TreeBinaryTree; + +#if ENABLE_THREAD_CHECK_IN_ALLOCS + m_RendererAllocator.SetThreadIDs(Thread::mainThreadId, Thread::mainThreadId); +#endif + + for (int i = 0; i < database.GetInstances().size(); ++i) + { + CreateTreeRenderer(i); + } +} + +TreeRenderer::~TreeRenderer() +{ + CleanupBillboardMeshes(); + + DestroySingleObject(m_BillboardMaterial); + m_BillboardMaterial = 0; + + DeleteTreeRenderers(); +} + + +/// Injects a single tree into the renderer, without requiring a full rebuild of the binary tree. +/// Requires that there is at least one tree in the renderer +void TreeRenderer::InjectTree(const TreeInstance& newTree) +{ + Assert(&newTree == &m_Database->GetInstances().back()); + if (!m_TreeBinaryTree.get() || !TreeBinaryTreeBuilder::AddLastTree(*m_TreeBinaryTree, m_Position, m_TerrainSize, kTreesPerBatch)) + { + // Failed adding the tree (Too many trees in one batch) + + // mircea@ for some reason it doesn't compile on the PS3 if assigned directly... + std::auto_ptr<TreeBinaryTree> TreeBinaryTree = TreeBinaryTreeBuilder::Build(*m_Database, m_Position, m_TerrainSize, kTreesPerBatch); + m_TreeBinaryTree = TreeBinaryTree; + } + CreateTreeRenderer(m_Database->GetInstances().size() - 1); + //@TODO: Cleanup meshes +} + +void TreeRenderer::RemoveTrees(const Vector3f& pos, float radius, int prototypeIndex) +{ + // hack + ReloadTrees(); +} + +void TreeRenderer::ReloadTrees() +{ + ReloadTrees(kTreesPerBatch); +} + +void TreeRenderer::ReloadTrees(int treesPerBatch) +{ + if (m_Database) + { + if (m_Database->GetInstances().empty()) + m_TreeBinaryTree.reset(); + else + { + // mircea@ for some reason it doesn't compile on the PS3 if assigned directly... + std::auto_ptr<TreeBinaryTree> TreeBinaryTree = TreeBinaryTreeBuilder::Build(*m_Database, m_Position, m_TerrainSize, treesPerBatch); + m_TreeBinaryTree = TreeBinaryTree; + } + m_RenderedBatches.clear(); + + DeleteTreeRenderers(); + m_LegacyTreeSceneNodes.resize_uninitialized(0); + m_LegacyTreeBoundingBoxes.resize_uninitialized(0); + m_TreeIndexToSceneNode.resize_uninitialized(0); + for (int i = 0; i < m_Database->GetInstances().size(); ++i) + { + CreateTreeRenderer(i); + } + } +} + +namespace { + // Encapsulate a single tree in the bounds + static void GetBounds (AABB& bounds, const TreeInstance& instance, const TreeDatabase::PrototypeVector& prototypes, const Vector3f& position, const Vector3f& scale) + { + Vector3f pos = Scale(instance.position, scale) + position; + Vector3f treeScale(instance.widthScale, instance.heightScale, instance.widthScale); + const AABB& pb = prototypes[instance.index].bounds; + bounds.SetCenterAndExtent(pos, Scale(treeScale, pb.GetExtent())); + } +} + +static void CalculateTreeBend (const AABB& bounds, float bendFactor, float time, Matrix4x4f& matrix, Vector4f& outWind) +{ + Vector4f force = WindManager::GetInstance().ComputeWindForce(bounds); + + const Vector3f& pos = bounds.GetCenter(); + + Vector3f quaternionAxis(force.z, 0, -force.x); + float magnitude = Magnitude(quaternionAxis); + if (magnitude > 0.00001f) + quaternionAxis = Normalize(quaternionAxis); + else + { + magnitude = 0; + quaternionAxis.z = 1; + } + + Vector2f additionBend( + PerlinNoise::NoiseNormalized(pos.x * 0.22F - time * 4.7F, pos.x * 9.005F) * 0.7f, + PerlinNoise::NoiseNormalized(pos.z * 0.22F - time * 4.3F, pos.z * 9.005F) * 0.5f + ); + + Quaternionf q = EulerToQuaternion(Vector3f(additionBend.x, 0, additionBend.y) * (bendFactor * kDeg2Rad * force.w));; + + float bend = 7 * bendFactor * kDeg2Rad * magnitude; + matrix.SetTRS(Vector3f::zero, AxisAngleToQuaternion(quaternionAxis, bend) * q, Vector3f::one); + outWind = force; +} + +static void RenderMeshIdentityMatrix (Mesh& mesh, Material& material, int layer, Camera& camera, const MaterialPropertyBlock& properties) +{ + Matrix4x4f matrix; + matrix.SetIdentity(); + + IntermediateRenderer* r = AddMeshIntermediateRenderer (matrix, &mesh, &material, layer, true, true, 0, &camera); + r->SetPropertyBlock (properties); +} + +const int kFrustumPlaneCount = 6; + +void TreeRenderer::Render(Camera& camera, const UNITY_VECTOR(kMemRenderer, Light*)& lights, float meshTreeDistance, float billboardTreeDistance, float crossFadeLength, int maximumMeshTrees, int layer) +{ + if (!m_TreeBinaryTree.get() || !m_Database) + return; + + RenderTexture::SetTemporarilyAllowIndieRenderTexture (true); + + // Graphics emulation might have changed, so we check for a change here + bool supportedNow = (RenderTexture::IsEnabled()); + if (supportedNow != m_ImposterRenderTexture->GetSupported()) + { + m_ImposterRenderTexture.reset(new ImposterRenderTexture(*m_Database)); + m_BillboardMaterial->SetTexture(ShaderLab::Property("_MainTex"), m_ImposterRenderTexture->GetTexture()); + } + + // If we don't support render textures or vertex shaders, there will be no + // billboards. + if (!m_ImposterRenderTexture->GetSupported()) + { + billboardTreeDistance = meshTreeDistance; + crossFadeLength = 0.0f; + } + + meshTreeDistance = std::min(billboardTreeDistance, meshTreeDistance); + crossFadeLength = clamp(crossFadeLength, 0.0F, billboardTreeDistance - meshTreeDistance); + m_CrossFadeLength = crossFadeLength; + m_SqrMeshTreeDistance = meshTreeDistance * meshTreeDistance; + m_SqrBillboardTreeDistance = billboardTreeDistance * billboardTreeDistance; + m_SqrCrossFadeEndDistance = (meshTreeDistance + m_CrossFadeLength) * (meshTreeDistance + m_CrossFadeLength); + + Transform& cameraTransform = camera.GetComponent(Transform); + const Vector3f& cameraPos = cameraTransform.GetPosition(); + Vector3f cameraDir = cameraTransform.TransformDirection(Vector3f::zAxis); + + Plane frustum[kFrustumPlaneCount]; + ExtractProjectionPlanes(camera.GetWorldToClipMatrix(), frustum); + + // Mark as becoming invisible + std::vector<TreeBinaryTree*> oldRenderedBatches = m_RenderedBatches; + for (std::vector<TreeBinaryTree*>::iterator it = oldRenderedBatches.begin(), end = oldRenderedBatches.end(); it != end; ++it) + { + TreeBinaryTree* binTree = *it; + + // We need to check if the tree is actually visible here, since it might have been + // pulled away from us while rebuilding the bintree or adding a single tree in the editor + if (binTree->visible == 1) + binTree->visible = -1; + } + m_RenderedBatches.clear(); + + m_FullTrees.clear(); + std::vector<int> billboardsList; + + // Build billboard meshes and stuff + +#if EMIT_ALL_TREE_RENDERERS + // calculate distance to camera: do this when we emit all tree renderers + // because renderer's skewFade will be using it + for (int i = 0; i < m_Database->GetInstances().size(); ++i) + { + TreeInstance& instance = m_Database->GetInstances()[i]; + Vector3f offset = GetPosition(instance) - cameraPos; + offset.y = 0.0F; + instance.temporaryDistance = SqrMagnitude(offset); + } +#endif + + // Collect all batches, billboards and full trees + // batches will be rendered in place + RenderRecurse(m_TreeBinaryTree.get(), frustum, billboardsList, cameraPos); + + // Cleanup the batches that have become invisible + for (std::vector<TreeBinaryTree*>::iterator it = oldRenderedBatches.begin(), end = oldRenderedBatches.end(); it != end; ++it) + { + TreeBinaryTree* binTree = *it; + + if (binTree->visible == -1) + { + DestroySingleObject(binTree->mesh); + binTree->mesh = 0; + binTree->visible = 0; + } + } + +#if !EMIT_ALL_TREE_RENDERERS + // Sort single trees back to front + std::sort(m_FullTrees.begin(), m_FullTrees.end(), TreeInstanceIndexSorter(m_Database->GetInstances())); + + // We are exceeding the mesh tree limit + // Move some trees into billboard list + if (m_FullTrees.size() > maximumMeshTrees) + { + int moveToBillboardCount = m_FullTrees.size() - maximumMeshTrees; + billboardsList.insert(billboardsList.end(), m_FullTrees.begin(), m_FullTrees.begin() + moveToBillboardCount); + m_FullTrees.erase(m_FullTrees.begin(), m_FullTrees.begin() + moveToBillboardCount); + } +#endif + + // Render the close by billboard mesh (Use immediate mode interface instead) + + // Sort billboards back to front + std::sort(billboardsList.begin(), billboardsList.end(), TreeInstanceIndexSorter(m_Database->GetInstances())); + + const int lightCount = std::min<int>(lights.size(), 4); + for (int i = 0; i < lightCount; ++i) + { + const Light* light = lights[i]; + const Transform& lightTransform = light->GetComponent(Transform); + Vector3f forward = RotateVectorByQuat (-lightTransform.GetRotation(), Vector3f::zAxis); + ShaderLab::g_GlobalProperties->SetVector(kTerrainTreeLightDirections[i], -forward.x, -forward.y, -forward.z, 0.0f); + ColorRGBAf color = light->GetColor() * light->GetIntensity(); + color = GammaToActiveColorSpace (color); + ShaderLab::g_GlobalProperties->SetVector(kTerrainTreeLightColors[i], color.GetPtr()); + } + for (int i = lightCount; i < 4; ++i) + ShaderLab::g_GlobalProperties->SetVector(kTerrainTreeLightColors[i], 0.0f, 0.0f, 0.0f, 0.0f); + + GetRenderSettings().SetupAmbient(); + + if (m_ImposterRenderTexture->GetSupported()) + m_ImposterRenderTexture->UpdateImposters (camera); + + // Setup billboard shader properties + UpdateShaderProps(camera); + + // Draw far away trees (only billboards) + for (std::vector<TreeBinaryTree*>::const_iterator it = m_RenderedBatches.begin(), end = m_RenderedBatches.end(); it != end; ++it) + { + RenderMeshIdentityMatrix (*(*it)->mesh, *m_BillboardMaterial, layer, camera, m_PropertyBlock); + } + + // Draw close by trees (billboard part for cross fade) + if (m_ImposterRenderTexture->GetSupported() && !billboardsList.empty()) + { + if (!m_CloseBillboardMesh) + { + m_CloseBillboardMesh = CreateObjectFromCode<Mesh>(kInstantiateOrCreateFromCodeAwakeFromLoad); + m_CloseBillboardMesh->SetHideFlags(Object::kDontSave); + m_CloseBillboardMesh->MarkDynamic(); // will be modified each frame, better use dynamic VBOs + } + GenerateBillboardMesh(*m_CloseBillboardMesh, billboardsList, true); + RenderMeshIdentityMatrix (*m_CloseBillboardMesh, *m_BillboardMaterial, layer, camera, m_PropertyBlock); + } + +#if EMIT_ALL_TREE_RENDERERS + // Draw all remaining trees (mesh part) +#else + // Draw close by trees (mesh part) +#endif + const float currentTime = GetTimeManager ().GetTimeSinceLevelLoad (); + + const ShaderLab::FastPropertyName propertyTerrainEngineBendTree = ShaderLab::Property("_TerrainEngineBendTree"); + const ShaderLab::FastPropertyName propertyColor = ShaderLab::Property("_Color"); + const ShaderLab::FastPropertyName propertyCutoff = ShaderLab::Property("_Cutoff"); + const ShaderLab::FastPropertyName propertyScale = ShaderLab::Property("_Scale"); + const ShaderLab::FastPropertyName propertySquashPlaneNormal = ShaderLab::Property("_SquashPlaneNormal"); + const ShaderLab::FastPropertyName propertySquashAmount = ShaderLab::Property("_SquashAmount"); + const ShaderLab::FastPropertyName propertyWind = ShaderLab::Property("_Wind"); + + float billboardOffsetFactor = 1; + if (m_ImposterRenderTexture->GetSupported()) + { + float tempBillboardAngleFactor; + m_ImposterRenderTexture->GetBillboardParams(tempBillboardAngleFactor, billboardOffsetFactor); + } + + for (std::vector<int>::iterator it = m_FullTrees.begin(), end = m_FullTrees.end(); it != end; ++it) + { + const TreeInstance& instance = m_Database->GetInstances()[*it]; + + const TreeDatabase::Prototype& prototype = m_Database->GetPrototypes()[instance.index]; + Mesh* mesh = prototype.mesh; + if (!mesh) + continue; + + const std::pair<int, int>& indexPair = m_TreeIndexToSceneNode[*it]; + if (indexPair.first == -1 || indexPair.second == 0) + { + continue; + } + + const std::vector<Material*>& materials = prototype.materials; + + float distance = sqrt(instance.temporaryDistance); + float skewFade = 1.0f; + // Skewfade is 1 when it looks like a real full mesh tree + // Skewfade is 0 where we switch to billboard + if (!CompareApproximately(crossFadeLength, 0.0F)) + skewFade = SmoothStep(0, 1, (meshTreeDistance + crossFadeLength - distance) / crossFadeLength); + float squashAmount = Lerp(0.04f, 1.0f, skewFade); + + // Only cast shadows from trees that are at full mesh appearance. + // When they start squashing, just stop casting shadows. + bool shadows = (skewFade >= 1.0f); + + AABB bounds; + GetBounds(bounds, instance, m_Database->GetPrototypes(), m_Position, m_TerrainSize); + + // Tree bending + Matrix4x4f rotate; + Vector4f wind; + CalculateTreeBend(bounds, skewFade * prototype.bendFactor, currentTime, rotate, wind); + + // we need to offset tree squash plane by the same amount as billboard is offsetted + const float centerOffset = prototype.treeWidth * instance.widthScale * 0.5F; + + // that will work as long as the trees are not rotated + // since the forward direction has to be in model space + Vector3f forward = cameraDir; + + ColorRGBAf treeColor = instance.color * instance.lightmapColor; + + int materialCount = std::min<int> (mesh->GetSubMeshCount(), materials.size()); + materialCount = std::min(materialCount, indexPair.second); + + for (int m = 0; m < materialCount; ++m) + { + m_PropertyBlock.Clear(); + m_PropertyBlock.AddPropertyMatrix(propertyTerrainEngineBendTree, rotate); + + ColorRGBAf color = prototype.originalMaterialColors[m] * treeColor; + + // Use the stored inverseAlphaCutoff value, since we're modifying the cutoff value + // in the material by using the property block below + float cutoff = 0.5f / prototype.inverseAlphaCutoff[m]; + + m_PropertyBlock.AddPropertyColor(propertyColor, color); + m_PropertyBlock.AddPropertyFloat(propertyCutoff, cutoff); + m_PropertyBlock.AddPropertyVector(propertyScale, Vector4f(instance.widthScale, instance.heightScale, instance.widthScale, 1)); + + // squash properties + // Position of squash plane has to match billboard plane - we use dual mode for billboard planes (see TerrainBillboardTree in TerrainEngine.cginc) + m_PropertyBlock.AddPropertyVector(propertySquashPlaneNormal, Vector4f(forward.x, forward.y, forward.z, centerOffset * billboardOffsetFactor)); + m_PropertyBlock.AddPropertyFloat(propertySquashAmount, squashAmount); + + m_PropertyBlock.AddPropertyVector(propertyWind, wind); + + SceneNode& sceneNode = m_LegacyTreeSceneNodes[indexPair.first + m]; + TreeMeshIntermediateRenderer* r = static_cast<TreeMeshIntermediateRenderer*>(sceneNode.renderer); + r->Update(layer, shadows, m_LightmapIndex != -1 ? 0xFE : -1, m_PropertyBlock); + sceneNode.layer = layer; + } + } + RenderTexture::SetTemporarilyAllowIndieRenderTexture (false); +} + +void TreeRenderer::CollectTreeRenderers(dynamic_array<SceneNode>& sceneNodes, dynamic_array<AABB>& boundingBoxes) +{ + for (int i = 0; i < m_FullTrees.size(); ++i) + { + const std::pair<int, int>& indexPair = m_TreeIndexToSceneNode[m_FullTrees[i]]; + if (indexPair.first == -1 || indexPair.second == 0) + { + continue; + } + + for (int j = 0; j < indexPair.second; ++j) + { + sceneNodes.push_back(m_LegacyTreeSceneNodes[indexPair.first + j]); + boundingBoxes.push_back(m_LegacyTreeBoundingBoxes[indexPair.first + j]); + } + } +} + + +// -------------------------------------------------------------------------- + + + +namespace +{ + template <class T, class A> + const T* GetData(const std::vector<T,A>& data) + { + return data.empty() ? 0 : &data.front(); + } + + float Calculate2DSqrDistance (const AABB& rkBox, const Vector3f& rkPoint) + { + // compute coordinates of point in box coordinate system + Vector3f kClosest = rkPoint - rkBox.GetCenter(); + kClosest.y = kClosest.z; + Vector3f extent = rkBox.GetExtent(); + extent.y = extent.z; + + // project test point onto box + float fSqrDistance = 0.0f; + float fDelta; + + for (int i = 0; i < 2; ++i) + { + if ( kClosest[i] < -extent[i] ) + { + fDelta = kClosest[i] + extent[i]; + fSqrDistance += fDelta * fDelta; + kClosest[i] = -extent[i]; + } + else if ( kClosest[i] > extent[i] ) + { + fDelta = kClosest[i] - extent[i]; + fSqrDistance += fDelta * fDelta; + kClosest[i] = extent[i]; + } + } + + return fSqrDistance; + } + + namespace SortUtility + { + static Vector3f sortDirections[] = { + Vector3f(-1, 0, 1), Vector3f(0, 0, 1), Vector3f(1, 0, 1), + Vector3f(-1, 0, 0), Vector3f(0, 0, 0), Vector3f(1, 0, 0), + Vector3f(-1, 0, -1), Vector3f(0, 0, -1), Vector3f(1, 0, -1) + }; + + const int insideBoundsDirection = 4; + + /* Because we want to do funky alpha blending instead of alpha cutoff (It looks SO MUCH BETTER) + We need to sort all patches. Now that would be really expensive, so we do a trick. + + We classify each patch by it's direction relative to the camera. + There are 8 potential directions. + 0 1 2 + 3 4 5 + 6 7 8 + + So every frame we check if the direction of the patch has changed and most of the time it doesn't. + Only when it changes do we resort. This lets us skip a lot of sorting. + Patches inside of the bounding volume are constantly being resorted, which is expensive but we can't do much about it! + */ + int CalculateTargetSortIndex (const AABB& bounds, const Vector3f& cameraPos) + { + MinMaxAABB aabb(bounds); + + int sortIndex = 0; + if (cameraPos.x > aabb.GetMax().x) + sortIndex += 2; + else if (cameraPos.x > aabb.GetMin().x) + sortIndex += 1; + + if (cameraPos.z > aabb.GetMax().z) + sortIndex += 0; + else if (cameraPos.z > aabb.GetMin().z) + sortIndex += 3; + else + sortIndex += 6; + + return sortIndex; + } + } + + struct BatchItem + { + int index; + float distance; + }; + + bool operator<(const BatchItem& soi1, const BatchItem& soi2) + { + return soi1.distance < soi2.distance; + } + + // Sorts a single batch by sort index (See SortUtility for more information) + void SortBatch (const TreeBinaryTree& tree, int sortIndex) + { + // Create sort order indices which are later used to build the sorted triangle indices + const std::vector<TreeInstance>& allInstances = tree.database->GetInstances(); + const std::vector<int>& instances = tree.instances; + const int size = instances.size(); + std::vector<BatchItem> sortOrder(size); + for (int i = 0; i < size; ++i) + sortOrder[i].index = i; + + if (sortIndex != SortUtility::insideBoundsDirection) + { + // Build array with distances + const Vector3f& direction = SortUtility::sortDirections[sortIndex]; + for (int i = 0; i < size; ++i) + sortOrder[i].distance = Dot(allInstances[instances[i]].position, direction); + + // Generate the sort order by sorting the sortDistances + std::sort(sortOrder.begin(), sortOrder.end()); + } + + // Build a triangle list from the sort order + UNITY_TEMP_VECTOR(UInt16) triangles(size * 6); + for (int i=0; i < size; ++i) + { + int triIndex = i * 6; + int vertexIndex = sortOrder[i].index * 4; + triangles[triIndex+0] = vertexIndex + 0; + triangles[triIndex+1] = vertexIndex + 1; + triangles[triIndex+2] = vertexIndex + 2; + triangles[triIndex+3] = vertexIndex + 2; + triangles[triIndex+4] = vertexIndex + 1; + triangles[triIndex+5] = vertexIndex + 3; + } + // Apply to mesh + tree.mesh->SetIndicesComplex (GetData(triangles), triangles.size(), 0, kPrimitiveTriangles, Mesh::k16BitIndices | Mesh::kDontSupportSubMeshVertexRanges); + } +} + + +void TreeRenderer::RenderRecurse(TreeBinaryTree* binTree, const Plane* planes, std::vector<int>& closeupBillboards, const Vector3f& cameraPos) +{ + // if we have exactly zero trees, we get null nodes a bit down - not sure why... + if (!binTree) + return; + + float sqrDistance = CalculateSqrDistance(cameraPos, binTree->bounds); + float sqr2DDistance = Calculate2DSqrDistance(binTree->bounds, cameraPos); + if (sqr2DDistance > m_SqrBillboardTreeDistance) + return; + + if (!IntersectAABBFrustumFull (binTree->bounds, planes)) + { +#if EMIT_ALL_TREE_RENDERERS + // we need to queue them as renderers since they might be visible in some shadow map + m_FullTrees.insert(m_FullTrees.end(), binTree->instances.begin(), binTree->instances.end()); +#endif + return; + } + + // Recurse children + if (binTree->instances.empty()) + { + if (binTree->splittingPlane.GetSide(cameraPos)) + { + RenderRecurse(binTree->right.get(), planes, closeupBillboards, cameraPos); + RenderRecurse(binTree->left.get(), planes, closeupBillboards, cameraPos); + } + else + { + RenderRecurse(binTree->left.get(), planes, closeupBillboards, cameraPos); + RenderRecurse(binTree->right.get(), planes, closeupBillboards, cameraPos); + } + } + // Render leaf node + else + { + // Render the trees as one big batch + if (sqr2DDistance > m_SqrCrossFadeEndDistance) + { + binTree->targetSortIndex = SortUtility::CalculateTargetSortIndex(binTree->bounds, cameraPos); + RenderBatch (*binTree, sqrDistance); + + if (binTree->targetSortIndex != binTree->sortIndex) + { + binTree->sortIndex = binTree->targetSortIndex; + SortBatch(*binTree, binTree->sortIndex); + } + } + // Render the trees individually + else + { + for (std::vector<int>::iterator it = binTree->instances.begin(), end = binTree->instances.end(); it != end; ++it) + { + TreeInstance& instance = m_Database->GetInstances()[*it]; + Vector3f position = GetPosition(instance); + Vector3f scale(instance.widthScale, instance.heightScale, instance.widthScale); + AABB treeBounds = m_Database->GetPrototypes()[instance.index].bounds; + treeBounds.SetCenterAndExtent(Scale(treeBounds.GetCenter(), scale) + position, Scale(treeBounds.GetExtent(), scale)); + +#if EMIT_ALL_TREE_RENDERERS + float indivdual2DSqrDistance = instance.temporaryDistance; + if (indivdual2DSqrDistance < m_SqrBillboardTreeDistance) + { + if (indivdual2DSqrDistance > m_SqrCrossFadeEndDistance + && IntersectAABBFrustumFull (treeBounds, planes)) + { + closeupBillboards.push_back(*it); + } + else + { + m_FullTrees.push_back(*it); + } + } +#else + if (!IntersectAABBFrustumFull (treeBounds, planes)) + continue; + + Vector3f offset = position - cameraPos; + offset.y = 0.0F; + float indivdual2DSqrDistance = SqrMagnitude(offset); + instance.temporaryDistance = indivdual2DSqrDistance; + + // Tree is close, render it as a mesh + if (indivdual2DSqrDistance < m_SqrCrossFadeEndDistance) + { + m_FullTrees.push_back(*it); + } + // Tree is still too far away, so render it as a billboard + else if (indivdual2DSqrDistance < m_SqrBillboardTreeDistance) + { + closeupBillboards.push_back(*it); + } +#endif + } + } + } +} + +void TreeRenderer::CleanupBillboardMeshes () +{ + for (std::vector<TreeBinaryTree*>::iterator it = m_RenderedBatches.begin(), end = m_RenderedBatches.end(); it != end; ++it) + { + TreeBinaryTree& binTree = **it; + if (binTree.visible != 0) + { + DestroySingleObject(binTree.mesh); + binTree.mesh = 0; + binTree.visible = 0; + } + } + + m_RenderedBatches.clear(); + + DestroySingleObject(m_CloseBillboardMesh); + m_CloseBillboardMesh = 0; +} + +void TreeRenderer::RenderBatch (TreeBinaryTree& binTree, float sqrDistance) +{ + if (binTree.visible == 0) + { + DestroySingleObject(binTree.mesh); + binTree.mesh = NULL; + + binTree.mesh = CreateObjectFromCode<Mesh>(kInstantiateOrCreateFromCodeAwakeFromLoad); + + binTree.mesh->SetHideFlags(Object::kDontSave); + binTree.mesh->SetName("tree billboard"); + GenerateBillboardMesh (*binTree.mesh, binTree.instances, false); + binTree.sortIndex = -1; + } + + binTree.visible = 1; + + m_RenderedBatches.push_back(&binTree); +} + +void TreeRenderer::UpdateShaderProps(const Camera& cam) +{ + const Transform& cameraTransform = cam.GetComponent(Transform); + const Vector3f& pos = cameraTransform.GetPosition(); + + // we want to get camera orientation from imposter in order to avoid any imprecisions + const Matrix4x4f& cameraMatrix = m_ImposterRenderTexture->getCameraOrientation(); + const Vector3f& right = cameraMatrix.GetAxisX(); + const Vector3f& up = cameraMatrix.GetAxisY(); + const Vector3f& front = cameraMatrix.GetAxisZ(); + + if (m_ImposterRenderTexture->GetSupported()) + { + float billboardAngleFactor, billboardOffsetFactor; + m_ImposterRenderTexture->GetBillboardParams(billboardAngleFactor, billboardOffsetFactor); + + m_PropertyBlock.Clear(); + m_PropertyBlock.AddPropertyVector(ShaderLab::Property("_TreeBillboardCameraRight"), Vector4f(right.x, right.y, right.z, 0.0F)); + m_PropertyBlock.AddPropertyVector(ShaderLab::Property("_TreeBillboardCameraUp"), Vector4f(up.x, up.y, up.z, billboardOffsetFactor)); + m_PropertyBlock.AddPropertyVector(ShaderLab::Property("_TreeBillboardCameraFront"), Vector4f(front.x, front.y, front.z, 0)); + m_PropertyBlock.AddPropertyVector(ShaderLab::Property("_TreeBillboardCameraPos"), Vector4f(pos.x, pos.y, pos.z, billboardAngleFactor)); + m_PropertyBlock.AddPropertyVector(ShaderLab::Property("_TreeBillboardDistances"), Vector4f(m_SqrBillboardTreeDistance,0,0,0)); + } +} + + +Vector3f TreeRenderer::GetPosition (const TreeInstance& instance) const +{ + return Scale(instance.position, m_TerrainSize) + m_Position; +} + +struct TreeBillboardVertex +{ + static const unsigned kFormat = VERTEX_FORMAT4(Vertex, TexCoord0, TexCoord1, Color); + + Vector3f p; + ColorRGBA32 color; + Vector2f uv, size; +}; + +void TreeRenderer::GenerateBillboardMesh(Mesh& mesh, const std::vector<int>& instances, bool buildTriangles) +{ + const std::vector<TreeInstance>& allInstances = m_Database->GetInstances(); + int treeCount = instances.size(); + + mesh.ResizeVertices (4 * treeCount, TreeBillboardVertex::kFormat); + TreeBillboardVertex* pv = (TreeBillboardVertex*)mesh.GetVertexDataPointer (); + bool swizzleColors = mesh.GetVertexColorsSwizzled(); + + AABB bounds; + + // Generate tree billboards + for (int i = 0; i < treeCount; ++i) + { + const TreeInstance& instance = allInstances[instances[i]]; + const TreeDatabase::Prototype& prototype = m_Database->GetPrototypes()[instance.index]; + + const Vector3f position = Scale(instance.position, m_TerrainSize) + m_Position; + ColorRGBAf color = instance.color; + color *= instance.lightmapColor; + + const float halfWidth = prototype.treeWidth * instance.widthScale * 0.5F; + const float halfHeight = prototype.treeHeight * instance.heightScale * 0.5F; + const float centerOffset = prototype.getCenterOffset() * instance.heightScale; + + //const float billboardTop = prototype.treeVisibleHeight * instance.heightScale; + //const float billboardBottom = (prototype.treeHeight - prototype.treeVisibleHeight) * instance.heightScale; + + const float billboardHeight = prototype.getBillboardHeight(); + const float billboardHeightDiff = (billboardHeight - prototype.treeHeight) / 2; + + const float billboardTop = (prototype.treeVisibleHeight + billboardHeightDiff) * instance.heightScale; + const float billboardBottom = (prototype.treeHeight - prototype.treeVisibleHeight + billboardHeightDiff) * instance.heightScale; + + const float billboardHalfHeight2 = prototype.getBillboardHeight() * instance.widthScale * 0.5F; + + // We position billboards at the root of the tree and offset billboard top and bottom differently (see billboardTop and billboardBottom) + // We blend between two modes (for billboard y axis in camera space): + // 1) vertical, which is based on billboardBottom and billboardTop + // 2) horizontal, which is based on billboardHalfHeight2 (the same for top and bottom) + // + // See TerrainBillboardTree in TerrainEngine.cginc for more detailed explanation + int index = i * 4; + pv[index + 0].p = position; + pv[index + 1].p = position; + pv[index + 2].p = position; + pv[index + 3].p = position; + + const Rectf& area = m_ImposterRenderTexture->GetArea(instance.index); + + // we're packing billboardHalfHeight2 into first uvs, and then recompute uvs.y by using formula uvs.y = uvs.y > 0 ? 1 : 0; + pv[index + 0].uv.Set(area.x, -billboardHalfHeight2); + pv[index + 1].uv.Set(area.GetRight(), -billboardHalfHeight2); + pv[index + 2].uv.Set(area.x, billboardHalfHeight2); + pv[index + 3].uv.Set(area.GetRight(), billboardHalfHeight2); + + pv[index + 0].size.Set(-halfWidth, -billboardBottom); + pv[index + 1].size.Set( halfWidth, -billboardBottom); + pv[index + 2].size.Set(-halfWidth, billboardTop); + pv[index + 3].size.Set( halfWidth, billboardTop); + + ColorRGBA32 color32 = (ColorRGBA32)color; + if (swizzleColors) + color32 = SwizzleColorForPlatform(color32); + pv[index + 0].color = pv[index + 1].color = pv[index + 2].color = pv[index + 3].color = color32; + + const Vector3f treeBounds(halfWidth, halfHeight, halfWidth); + const Vector3f treeCenter = position + Vector3f(0, centerOffset, 0); + if (0 == i) + { + bounds.SetCenterAndExtent(treeCenter, treeBounds); + } + else + { + bounds.Encapsulate(treeCenter + treeBounds); + bounds.Encapsulate(treeCenter - treeBounds); + } + } + + mesh.SetBounds(bounds); + mesh.SetChannelsDirty (mesh.GetAvailableChannels (), false); + + if (buildTriangles) + { + UNITY_TEMP_VECTOR(UInt16) triangles(instances.size() * 6); + for (int i = 0; i < instances.size(); ++i) + { + int triIndex = i * 6; + int vertexIndex = i * 4; + triangles[triIndex+0] = vertexIndex + 0; + triangles[triIndex+1] = vertexIndex + 1; + triangles[triIndex+2] = vertexIndex + 2; + triangles[triIndex+3] = vertexIndex + 2; + triangles[triIndex+4] = vertexIndex + 1; + triangles[triIndex+5] = vertexIndex + 3; + } + // Apply to mesh + mesh.SetIndicesComplex (GetData(triangles), triangles.size(), 0, kPrimitiveTriangles, Mesh::k16BitIndices | Mesh::kDontSupportSubMeshVertexRanges); + } +} + +void TreeRenderer::CreateTreeRenderer(int instance) +{ + const TreeInstance& tree = m_Database->GetInstances()[instance]; + + Assert(instance == m_TreeIndexToSceneNode.size()); + std::pair<int, int>& indexPair = m_TreeIndexToSceneNode.push_back(); + + const TreeDatabase::Prototype& prototype = m_Database->GetPrototypes()[tree.index]; + Mesh* mesh = prototype.mesh; + if (mesh == NULL) + { + indexPair.first = -1; + indexPair.second = 0; + return; + } + + const std::vector<Material*>& materials = prototype.materials; + + Matrix4x4f modelview; + modelview.SetTRS(GetPosition(tree), Quaternionf::identity(), Vector3f::one); + + const int materialCount = std::min<int> (mesh->GetSubMeshCount(), materials.size()); + + indexPair.first = m_LegacyTreeSceneNodes.size(); + indexPair.second = materialCount; + + for (int m = 0; m < materialCount; ++m) + { + AABB aabb = mesh->GetBounds(); + aabb.GetCenter().y += aabb.GetExtent().y * tree.heightScale - aabb.GetExtent().y; + aabb.SetCenterAndExtent (aabb.GetCenter(), Vector3f (aabb.GetExtent().x * tree.widthScale, aabb.GetExtent().y * tree.heightScale, aabb.GetExtent().z * tree.widthScale)); + + Assert(TreeMeshIntermediateRenderer::s_Allocator == NULL); + TreeMeshIntermediateRenderer::s_Allocator = &m_RendererAllocator; + TreeMeshIntermediateRenderer* r = new TreeMeshIntermediateRenderer(); + TreeMeshIntermediateRenderer::s_Allocator = NULL; + r->Initialize(modelview, mesh, aabb, materials[m], 0, false, false, m); + + m_LegacyTreeSceneNodes.push_back(SceneNode()); + SceneNode& node = m_LegacyTreeSceneNodes.back(); + node.renderer = r; + node.layer = 0; + + r->GetWorldAABB(m_LegacyTreeBoundingBoxes.push_back()); + } +} + +void TreeRenderer::DeleteTreeRenderers() +{ + Assert(TreeMeshIntermediateRenderer::s_Allocator == NULL); + TreeMeshIntermediateRenderer::s_Allocator = &m_RendererAllocator; + for (int i = 0; i < m_LegacyTreeSceneNodes.size(); ++i) + { + delete m_LegacyTreeSceneNodes[i].renderer; + } + TreeMeshIntermediateRenderer::s_Allocator = NULL; + +#if ENABLE_THREAD_CHECK_IN_ALLOCS + Assert(m_RendererAllocator.m_Allocated == 0); +#endif + m_RendererAllocator.purge(); +} + +#endif // ENABLE_TERRAIN diff --git a/Runtime/Terrain/TreeRenderer.h b/Runtime/Terrain/TreeRenderer.h new file mode 100644 index 0000000..21a3b48 --- /dev/null +++ b/Runtime/Terrain/TreeRenderer.h @@ -0,0 +1,90 @@ +#pragma once +#include "Configuration/UnityConfigure.h" + +#if ENABLE_TERRAIN + +#include "ImposterRenderTexture.h" + +#include "Runtime/Shaders/MaterialProperties.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Allocator/LinearAllocator.h" +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Camera/SceneNode.h" + +namespace Unity +{ + class Material; +} + +class TreeDatabase; +class Mesh; +struct TreeBinaryTree; +class Camera; +class Light; +class Plane; +struct TreeInstance; +struct ShadowCasterCull; +class Matrix4x4f; + +class TreeRenderer +{ +public: + TreeRenderer(TreeDatabase& database, const Vector3f& position, int lightmapIndex); + ~TreeRenderer(); + + void InjectTree(const TreeInstance& newTree); + void RemoveTrees(const Vector3f& pos, float radius, int prototypeIndex); + + void ReloadTrees(); + void Render(Camera& camera, const UNITY_VECTOR(kMemRenderer, Light*)& lights, float meshTreeDistance, float billboardTreeDistance, float crossFadeLength, int maximumMeshTrees, int layer); + void InvalidateImposter () { m_ImposterRenderTexture->InvalidateAngles(); } + + int GetLightmapIndex() { return m_LightmapIndex; } + void SetLightmapIndex(int value) { m_LightmapIndex = value; } + + void CollectTreeRenderers(dynamic_array<SceneNode>& sceneNodes, dynamic_array<AABB>& boundingBoxes); + +private: + void ReloadTrees(int treesPerBatch); + void CleanupBillboardMeshes(); + void RenderRecurse(TreeBinaryTree* binTree, const Plane* planes, std::vector<int>& closeupBillboards, const Vector3f& cameraPos); + void UpdateShaderProps(const Camera& cam); + void GenerateBillboardMesh(Mesh& mesh, const std::vector<int>& instances, bool buildTriangles); + Vector3f GetPosition(const TreeInstance& instance) const; + void RenderBatch(TreeBinaryTree& binTree, float sqrDistance); + void CreateTreeRenderer(int instance); + void DeleteTreeRenderers(); + +private: + MaterialPropertyBlock m_PropertyBlock; + TreeDatabase* m_Database; + Material* m_BillboardMaterial; + Vector3f m_TerrainSize; + Vector3f m_Position; + std::auto_ptr<TreeBinaryTree> m_TreeBinaryTree; + + float m_SqrBillboardTreeDistance; + float m_SqrMeshTreeDistance; + float m_CrossFadeLength; + float m_SqrCrossFadeEndDistance; + int m_LightmapIndex; + + Mesh* m_CloseBillboardMesh; + + std::vector<int> m_FullTrees; + std::vector<TreeBinaryTree*> m_RenderedBatches; + + std::auto_ptr<ImposterRenderTexture> m_ImposterRenderTexture; + + ForwardLinearAllocator m_RendererAllocator; + + // scene nodes and bounding boxes for all trees + // let's call them legacy here for easier merging with SpeedTree branch in the future + dynamic_array<SceneNode> m_LegacyTreeSceneNodes; + dynamic_array<AABB> m_LegacyTreeBoundingBoxes; + dynamic_array<std::pair<int, int> > m_TreeIndexToSceneNode; // {first renderer, number of renderers} +}; + +#endif // ENABLE_TERRAIN + + diff --git a/Runtime/Terrain/Wind.cpp b/Runtime/Terrain/Wind.cpp new file mode 100644 index 0000000..3fe6234 --- /dev/null +++ b/Runtime/Terrain/Wind.cpp @@ -0,0 +1,149 @@ +#include "UnityPrefix.h" +#include "Wind.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Math/Vector4.h" + +// -------------------------------------------------------------------------- + + +WindZone::WindZone (MemLabelId label, ObjectCreationMode mode) +: Super (label, mode) +, m_Mode (Directional) +, m_Radius (20) +, m_WindMain (1) +, m_WindTurbulence (1) +, m_WindPulseMagnitude (0.5f) +, m_WindPulseFrequency (0.01f) +, m_Node (this) +{ +} + + +WindZone::~WindZone () +{ +} + + +template<class TransferFunc> +void WindZone::Transfer (TransferFunc& transfer) +{ + Super::Transfer (transfer); + + TRANSFER_ENUM(m_Mode); + + transfer.Transfer (m_Radius, "m_Radius"); + + transfer.Transfer (m_WindMain, "m_WindMain"); + transfer.Transfer (m_WindTurbulence, "m_WindTurbulence"); + + transfer.Transfer (m_WindPulseMagnitude, "m_WindPulseMagnitude"); + transfer.Transfer (m_WindPulseFrequency, "m_WindPulseFrequency"); +} + + +Vector4f WindZone::ComputeWindForce (const AABB& bounds, float time) const +{ + // use point mid way between center and top center, as leaves are likely to be placed there + Vector3f center = bounds.GetCenter (); + // TODO : could use GetExtent instead + center.y += (bounds.GetMax ().y - bounds.GetMin ().y) * 0.25f; + + float windPhase = time * kPI * m_WindPulseFrequency; + float phase = windPhase + center.x * 0.1f + center.z * 0.1f; + float pulse = (cos (phase) + cos (phase * 0.375f) + cos (phase * 0.05f)) * 0.333f; + pulse = 1.0f + (pulse * m_WindPulseMagnitude); + + const Transform& transform = GetComponent (Transform); + const Vector3f position = transform.GetPosition (); + + if (m_Mode == Directional) + { + // Directional + float power = pulse; + + // TODO : could do this when m_direction is set + Vector3f delta = transform.TransformDirection (Vector3f::zAxis); + delta = Normalize (delta); + + return Vector4f ( + delta.x * m_WindMain * power, + delta.y * m_WindMain * power, + delta.z * m_WindMain * power, + m_WindTurbulence * power); + } + else + { + // Spherical with falloff + float power = 1.0f - CalculateSqrDistance (position, bounds) / (m_Radius * m_Radius); + if (power > 0.0f) + { + power *= pulse; + + Vector3f delta = center - position; + delta = Normalize (delta); + return Vector4f( + delta.x * m_WindMain * power, + delta.y * m_WindMain * power, + delta.z * m_WindMain * power, + m_WindTurbulence * power); + } + } + + return Vector4f(0,0,0,0); +} + + + +// -------------------------------------------------------------------------- + + +void WindZone::AddToManager () +{ + WindManager::GetInstance ().AddWindZone (m_Node); +} + + +void WindZone::RemoveFromManager () +{ + m_Node.RemoveFromList(); +} + +IMPLEMENT_CLASS(WindZone) +IMPLEMENT_OBJECT_SERIALIZE(WindZone) + +// -------------------------------------------------------------------------- + +// Empty destructor so it won't be autogenerated all over the place +WindManager::~WindManager() +{ +} + +// No need to allocate this dynamically, it's a tiny class +WindManager WindManager::s_WindManager; + +WindManager& WindManager::GetInstance () +{ + return s_WindManager; +} + +WindManager::WindZoneList& WindManager::GetList () +{ + return m_WindZones; +} + + +Vector4f WindManager::ComputeWindForce (const AABB& bounds) +{ + float time = GetTimeManager ().GetTimeSinceLevelLoad (); + + Vector4f force (0, 0, 0, 0); + for (WindZoneList::iterator it = m_WindZones.begin (); it != m_WindZones.end (); ++it) + { + const WindZone& zone = **it; + force = force + zone.ComputeWindForce (bounds, time); + } + return force; +} diff --git a/Runtime/Terrain/Wind.h b/Runtime/Terrain/Wind.h new file mode 100644 index 0000000..7ed310d --- /dev/null +++ b/Runtime/Terrain/Wind.h @@ -0,0 +1,103 @@ +#ifndef RUNTIME_TERRAIN_WIND_H +#define RUNTIME_TERRAIN_WIND_H + +#include "Runtime/GameCode/Behaviour.h" +class Vector4f; +class Vector3f; +class AABB; + + + +// -------------------------------------------------------------------------- + + + + +class WindZone : public Behaviour +{ +public: + REGISTER_DERIVED_CLASS (WindZone, Behaviour) + DECLARE_OBJECT_SERIALIZE (WindZone) + + enum WindZoneMode + { + Directional, // Wind has a direction along the z-axis of the transform + Spherical // Wind comes from the transform.position and affects in the direction towards the tree + }; + +public: + + + WindZone (MemLabelId label, ObjectCreationMode mode); + // ~WindZone(); declared by a macro + + // Directional / Spherical. Radius is only used in Spherical mode + inline WindZoneMode GetMode () const { return m_Mode; } + inline void SetMode (WindZoneMode value) { m_Mode = value; SetDirty(); } + + inline float GetRadius () const { return m_Radius; } + inline void SetRadius (float value) { m_Radius = value; SetDirty();} + + + // Parameters affecting the wind speed, strength and frequency + inline float GetWindMain () const { return m_WindMain; } + inline float GetWindTurbulence () const { return m_WindTurbulence; } + inline float GetWindPulseMagnitude () const { return m_WindPulseMagnitude; } + inline float GetWindPulseFrequency () const { return m_WindPulseFrequency; } + + inline void SetWindMain (float value) { m_WindMain = value; SetDirty(); } + inline void SetWindTurbulence (float value) { m_WindTurbulence = value; SetDirty();} + inline void SetWindPulseMagnitude (float value) { m_WindPulseMagnitude = value; SetDirty(); } + inline void SetWindPulseFrequency (float value) { m_WindPulseFrequency = value; SetDirty(); } + + Vector4f ComputeWindForce (const AABB& bounds, float time) const; + +protected: + virtual void AddToManager(); + virtual void RemoveFromManager(); + +private: + // Settings + WindZoneMode m_Mode; ///< enum { Directional = 0, Spherical = 1 } + + float m_Radius; + + float m_WindMain; + float m_WindTurbulence; + + float m_WindPulseMagnitude; + float m_WindPulseFrequency; + + // Node registered with the Wind Manager + ListNode<WindZone> m_Node; +}; + + +// -------------------------------------------------------------------------- + + +class WindManager +{ +public: + ~WindManager(); + + static WindManager& GetInstance(); + + void AddWindZone(ListNode<WindZone>& node) { m_WindZones.push_back (node); } + + Vector4f ComputeWindForce(const AABB& bounds); + + typedef List< ListNode<WindZone> > WindZoneList; + WindZoneList& GetList (); + +private: + static WindManager s_WindManager; + + WindZoneList m_WindZones; +}; + + +// -------------------------------------------------------------------------- + + +#endif |