From 15740faf9fe9fe4be08965098bbf2947e096aeeb Mon Sep 17 00:00:00 2001 From: chai Date: Wed, 14 Aug 2019 22:50:43 +0800 Subject: +Unity Runtime code --- Runtime/Terrain/DetailDatabase.cpp | 1106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1106 insertions(+) create mode 100644 Runtime/Terrain/DetailDatabase.cpp (limited to 'Runtime/Terrain/DetailDatabase.cpp') 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(resolution / resolutionPerPatch, 0, 10000); + m_PatchSamples = clamp(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= 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= 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;yUpdateUsers (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 (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;iQueryComponent (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 (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(); + 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;iSetHideFlags (Object::kHideAndDontSave); + m_AtlasTexture->SetWrapMode( kTexWrapClamp ); + + AssertIf (m_PreloadTextureAtlasUVLayout.size() != m_DetailPrototypes.size()); + for (int i=0;iAwakeFromLoad(kDefaultAwakeFromLoad); + } + + m_IsPrototypesDirty = false; +} + + +void DetailDatabase::SetDirty () +{ + m_TerrainData->SetDirty (); +} + +void DetailDatabase::SetDetailPrototypes (const vector & 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;iReset(); + 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 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;yGetInterpolatedHeight (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& refreshed, const std::set& removed, const std::set& moved); +{ + for (std::set::iterator i=refresh.begin();i != refreshed.end();i++) + { + PPtr tex = dynamic_pptr_cast (GetMainAsset()); + if (tex) + { + vector 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 (src.prototype); + dest.prototypeTexture = ScriptingObjectToObject (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 -- cgit v1.1-26-g67d0