summaryrefslogtreecommitdiff
path: root/Runtime/Terrain
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Terrain')
-rw-r--r--Runtime/Terrain/DetailDatabase.cpp1106
-rw-r--r--Runtime/Terrain/DetailDatabase.h270
-rw-r--r--Runtime/Terrain/DetailRenderer.cpp303
-rw-r--r--Runtime/Terrain/DetailRenderer.h56
-rw-r--r--Runtime/Terrain/Heightmap.cpp892
-rw-r--r--Runtime/Terrain/Heightmap.h199
-rw-r--r--Runtime/Terrain/ImposterRenderTexture.cpp229
-rw-r--r--Runtime/Terrain/ImposterRenderTexture.h65
-rw-r--r--Runtime/Terrain/PerlinNoise.cpp73
-rw-r--r--Runtime/Terrain/PerlinNoise.h16
-rw-r--r--Runtime/Terrain/QuadTreeNodeRenderer.cpp124
-rw-r--r--Runtime/Terrain/QuadTreeNodeRenderer.h40
-rw-r--r--Runtime/Terrain/ScriptBindings/TerrainDataBindings.txt406
-rw-r--r--Runtime/Terrain/ScriptBindings/Terrains.txt675
-rw-r--r--Runtime/Terrain/ScriptBindings/WindZoneBindings.txt183
-rw-r--r--Runtime/Terrain/SplatDatabase.cpp600
-rw-r--r--Runtime/Terrain/SplatDatabase.h111
-rw-r--r--Runtime/Terrain/SplatMaterials.cpp228
-rw-r--r--Runtime/Terrain/SplatMaterials.h41
-rw-r--r--Runtime/Terrain/TerrainData.cpp169
-rw-r--r--Runtime/Terrain/TerrainData.h66
-rw-r--r--Runtime/Terrain/TerrainIndexGenerator.cpp379
-rw-r--r--Runtime/Terrain/TerrainIndexGenerator.h12
-rw-r--r--Runtime/Terrain/TerrainInstance.cpp344
-rw-r--r--Runtime/Terrain/TerrainInstance.h132
-rw-r--r--Runtime/Terrain/TerrainManager.cpp280
-rw-r--r--Runtime/Terrain/TerrainManager.h47
-rw-r--r--Runtime/Terrain/TerrainModule.jam67
-rw-r--r--Runtime/Terrain/TerrainModuleRegistration.cpp37
-rw-r--r--Runtime/Terrain/TerrainRenderer.cpp798
-rw-r--r--Runtime/Terrain/TerrainRenderer.h168
-rw-r--r--Runtime/Terrain/Tree.cpp78
-rw-r--r--Runtime/Terrain/Tree.h36
-rw-r--r--Runtime/Terrain/TreeDatabase.cpp283
-rw-r--r--Runtime/Terrain/TreeDatabase.h152
-rw-r--r--Runtime/Terrain/TreeRenderer.cpp1223
-rw-r--r--Runtime/Terrain/TreeRenderer.h90
-rw-r--r--Runtime/Terrain/Wind.cpp149
-rw-r--r--Runtime/Terrain/Wind.h103
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