|
|
@@ -12,7 +12,7 @@ namespace gameplay
|
|
|
|
|
|
// The default square size of terrain patches for a terrain that
|
|
|
// does not have an explicitly specified patch size.
|
|
|
-#define DEFAULT_TERRAIN_PATCH_SIZE 32
|
|
|
+static const unsigned int DEFAULT_TERRAIN_PATCH_SIZE = 32;
|
|
|
|
|
|
// The default height of a terrain that does not have an explicitly
|
|
|
// specified terrain size, expressed as a ratio of the average
|
|
|
@@ -20,21 +20,15 @@ namespace gameplay
|
|
|
//
|
|
|
// heightMax = (image.width + image.height) / 2 * DEFAULT_TERRAIN_HEIGHT_RATIO
|
|
|
//
|
|
|
-#define DEFAULT_TERRAIN_HEIGHT_RATIO 0.3f
|
|
|
+static const float DEFAULT_TERRAIN_HEIGHT_RATIO = 0.3f;
|
|
|
|
|
|
-// Terrain dirty flag bits
|
|
|
-#define TERRAIN_DIRTY_WORLD_MATRIX 1
|
|
|
-#define TERRAIN_DIRTY_INV_WORLD_MATRIX 2
|
|
|
-#define TERRAIN_DIRTY_NORMAL_MATRIX 4
|
|
|
+// Terrain dirty flags
|
|
|
+static const unsigned int DIRTY_FLAG_INVERSE_WORLD = 1;
|
|
|
|
|
|
-/**
|
|
|
- * @script{ignore}
|
|
|
- */
|
|
|
-float getDefaultHeight(unsigned int width, unsigned int height);
|
|
|
+static float getDefaultHeight(unsigned int width, unsigned int height);
|
|
|
|
|
|
Terrain::Terrain() :
|
|
|
- _heightfield(NULL), _node(NULL), _normalMap(NULL), _flags(FRUSTUM_CULLING | LEVEL_OF_DETAIL),
|
|
|
- _dirtyFlags(TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX)
|
|
|
+ _heightfield(NULL), _node(NULL), _normalMap(NULL), _flags(FRUSTUM_CULLING | LEVEL_OF_DETAIL), _dirtyFlags(DIRTY_FLAG_INVERSE_WORLD)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
@@ -77,129 +71,132 @@ Terrain* Terrain::create(const char* path, Properties* properties)
|
|
|
p = Properties::create(path);
|
|
|
}
|
|
|
|
|
|
- if (p)
|
|
|
+ if (!p)
|
|
|
{
|
|
|
- pTerrain = strlen(p->getNamespace()) > 0 ? p : p->getNextNamespace();
|
|
|
- if (pTerrain == NULL)
|
|
|
+ GP_WARN("Failed to properties for terrain: %s", path ? path : "");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ pTerrain = strlen(p->getNamespace()) > 0 ? p : p->getNextNamespace();
|
|
|
+ if (pTerrain == NULL)
|
|
|
+ {
|
|
|
+ GP_WARN("Invalid terrain definition.");
|
|
|
+ if (!externalProperties)
|
|
|
+ SAFE_DELETE(p);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read heightmap info
|
|
|
+ Properties* pHeightmap = pTerrain->getNamespace("heightmap", true);
|
|
|
+ if (pHeightmap)
|
|
|
+ {
|
|
|
+ // Read heightmap path
|
|
|
+ std::string heightmap;
|
|
|
+ if (!pHeightmap->getPath("path", &heightmap))
|
|
|
{
|
|
|
- GP_WARN("Invalid terrain definition.");
|
|
|
+ GP_WARN("No 'path' property supplied in heightmap section of terrain definition: %s", path);
|
|
|
if (!externalProperties)
|
|
|
SAFE_DELETE(p);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- // Read heightmap info
|
|
|
- Properties* pHeightmap = pTerrain->getNamespace("heightmap", true);
|
|
|
- if (pHeightmap)
|
|
|
+ std::string ext = FileSystem::getExtension(heightmap.c_str());
|
|
|
+ if (ext == ".PNG")
|
|
|
{
|
|
|
- // Read heightmap path
|
|
|
- std::string heightmap;
|
|
|
- if (!pHeightmap->getPath("path", &heightmap))
|
|
|
+ // Read normalized height values from heightmap image
|
|
|
+ heightfield = HeightField::createFromImage(heightmap.c_str(), 0, 1);
|
|
|
+ }
|
|
|
+ else if (ext == ".RAW" || ext == ".R16")
|
|
|
+ {
|
|
|
+ // Require additional properties to be specified for RAW files
|
|
|
+ Vector2 imageSize;
|
|
|
+ if (!pHeightmap->getVector2("size", &imageSize))
|
|
|
{
|
|
|
- GP_WARN("No 'path' property supplied in heightmap section of terrain definition: %s", path);
|
|
|
+ GP_WARN("Invalid or missing 'size' attribute in heightmap defintion of terrain definition: %s", path);
|
|
|
if (!externalProperties)
|
|
|
SAFE_DELETE(p);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- std::string ext = FileSystem::getExtension(heightmap.c_str());
|
|
|
- if (ext == ".PNG")
|
|
|
- {
|
|
|
- // Read normalized height values from heightmap image
|
|
|
- heightfield = HeightField::createFromImage(heightmap.c_str(), 0, 1);
|
|
|
- }
|
|
|
- else if (ext == ".RAW" || ext == ".R16")
|
|
|
- {
|
|
|
- // Require additional properties to be specified for RAW files
|
|
|
- Vector2 imageSize;
|
|
|
- if (!pHeightmap->getVector2("size", &imageSize))
|
|
|
- {
|
|
|
- GP_WARN("Invalid or missing 'size' attribute in heightmap defintion of terrain definition: %s", path);
|
|
|
- if (!externalProperties)
|
|
|
- SAFE_DELETE(p);
|
|
|
- return NULL;
|
|
|
- }
|
|
|
-
|
|
|
- // Read normalized height values from RAW file
|
|
|
- heightfield = HeightField::createFromRAW(heightmap.c_str(), (unsigned int)imageSize.x, (unsigned int)imageSize.y, 0, 1);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Unsupported heightmap format
|
|
|
- GP_WARN("Unsupported heightmap format ('%s') in terrain definition: %s", heightmap.c_str(), path);
|
|
|
- if (!externalProperties)
|
|
|
- SAFE_DELETE(p);
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ // Read normalized height values from RAW file
|
|
|
+ heightfield = HeightField::createFromRAW(heightmap.c_str(), (unsigned int)imageSize.x, (unsigned int)imageSize.y, 0, 1);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- // Try to read 'heightmap' as a simple string property
|
|
|
- std::string heightmap;
|
|
|
- if (!pTerrain->getPath("heightmap", &heightmap))
|
|
|
- {
|
|
|
- GP_WARN("No 'heightmap' property supplied in terrain definition: %s", path);
|
|
|
- if (!externalProperties)
|
|
|
- SAFE_DELETE(p);
|
|
|
- return NULL;
|
|
|
- }
|
|
|
-
|
|
|
- std::string ext = FileSystem::getExtension(heightmap.c_str());
|
|
|
- if (ext == ".PNG")
|
|
|
- {
|
|
|
- // Read normalized height values from heightmap image
|
|
|
- heightfield = HeightField::createFromImage(heightmap.c_str(), 0, 1);
|
|
|
- }
|
|
|
- else if (ext == ".RAW" || ext == ".R16")
|
|
|
- {
|
|
|
- GP_WARN("RAW heightmaps must be specified inside a heightmap block with width and height properties.");
|
|
|
- if (!externalProperties)
|
|
|
- SAFE_DELETE(p);
|
|
|
- return NULL;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- GP_WARN("Unsupported 'heightmap' format ('%s') in terrain definition: %s.", heightmap.c_str(), path);
|
|
|
- if (!externalProperties)
|
|
|
- SAFE_DELETE(p);
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ // Unsupported heightmap format
|
|
|
+ GP_WARN("Unsupported heightmap format ('%s') in terrain definition: %s", heightmap.c_str(), path);
|
|
|
+ if (!externalProperties)
|
|
|
+ SAFE_DELETE(p);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
-
|
|
|
- // Read terrain 'size'
|
|
|
- if (pTerrain->exists("size"))
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Try to read 'heightmap' as a simple string property
|
|
|
+ std::string heightmap;
|
|
|
+ if (!pTerrain->getPath("heightmap", &heightmap))
|
|
|
{
|
|
|
- if (!pTerrain->getVector3("size", &terrainSize))
|
|
|
- {
|
|
|
- GP_WARN("Invalid 'size' value ('%s') in terrain definition: %s", pTerrain->getString("size"), path);
|
|
|
- }
|
|
|
+ GP_WARN("No 'heightmap' property supplied in terrain definition: %s", path);
|
|
|
+ if (!externalProperties)
|
|
|
+ SAFE_DELETE(p);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
|
|
|
- // Read terrain 'patch size'
|
|
|
- if (pTerrain->exists("patchSize"))
|
|
|
+ std::string ext = FileSystem::getExtension(heightmap.c_str());
|
|
|
+ if (ext == ".PNG")
|
|
|
{
|
|
|
- patchSize = pTerrain->getInt("patchSize");
|
|
|
+ // Read normalized height values from heightmap image
|
|
|
+ heightfield = HeightField::createFromImage(heightmap.c_str(), 0, 1);
|
|
|
}
|
|
|
-
|
|
|
- // Read terrain 'detailLevels'
|
|
|
- if (pTerrain->exists("detailLevels"))
|
|
|
+ else if (ext == ".RAW" || ext == ".R16")
|
|
|
{
|
|
|
- detailLevels = pTerrain->getInt("detailLevels");
|
|
|
+ GP_WARN("RAW heightmaps must be specified inside a heightmap block with width and height properties.");
|
|
|
+ if (!externalProperties)
|
|
|
+ SAFE_DELETE(p);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
+ else
|
|
|
+ {
|
|
|
+ GP_WARN("Unsupported 'heightmap' format ('%s') in terrain definition: %s.", heightmap.c_str(), path);
|
|
|
+ if (!externalProperties)
|
|
|
+ SAFE_DELETE(p);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // Read 'skirtScale'
|
|
|
- if (pTerrain->exists("skirtScale"))
|
|
|
+ // Read terrain 'size'
|
|
|
+ if (pTerrain->exists("size"))
|
|
|
+ {
|
|
|
+ if (!pTerrain->getVector3("size", &terrainSize))
|
|
|
{
|
|
|
- skirtScale = pTerrain->getFloat("skirtScale");
|
|
|
+ GP_WARN("Invalid 'size' value ('%s') in terrain definition: %s", pTerrain->getString("size"), path);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Read 'normalMap'
|
|
|
- normalMap = pTerrain->getString("normalMap");
|
|
|
+ // Read terrain 'patch size'
|
|
|
+ if (pTerrain->exists("patchSize"))
|
|
|
+ {
|
|
|
+ patchSize = pTerrain->getInt("patchSize");
|
|
|
+ }
|
|
|
|
|
|
- // Read 'material'
|
|
|
- materialPath = pTerrain->getString("material", "");
|
|
|
+ // Read terrain 'detailLevels'
|
|
|
+ if (pTerrain->exists("detailLevels"))
|
|
|
+ {
|
|
|
+ detailLevels = pTerrain->getInt("detailLevels");
|
|
|
}
|
|
|
|
|
|
+ // Read 'skirtScale'
|
|
|
+ if (pTerrain->exists("skirtScale"))
|
|
|
+ {
|
|
|
+ skirtScale = pTerrain->getFloat("skirtScale");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read 'normalMap'
|
|
|
+ normalMap = pTerrain->getString("normalMap");
|
|
|
+
|
|
|
+ // Read 'material'
|
|
|
+ materialPath = pTerrain->getString("material", "");
|
|
|
+
|
|
|
if (heightfield == NULL)
|
|
|
{
|
|
|
GP_WARN("Failed to read heightfield heights for terrain definition: %s", path);
|
|
|
@@ -215,7 +212,7 @@ Terrain* Terrain::create(const char* path, Properties* properties)
|
|
|
|
|
|
if (patchSize <= 0 || patchSize > (int)heightfield->getColumnCount() || patchSize > (int)heightfield->getRowCount())
|
|
|
{
|
|
|
- patchSize = std::min(heightfield->getRowCount(), std::min(heightfield->getColumnCount(), (unsigned int)DEFAULT_TERRAIN_PATCH_SIZE));
|
|
|
+ patchSize = std::min(heightfield->getRowCount(), std::min(heightfield->getColumnCount(), DEFAULT_TERRAIN_PATCH_SIZE));
|
|
|
}
|
|
|
|
|
|
if (detailLevels <= 0)
|
|
|
@@ -253,9 +250,11 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale,
|
|
|
// Create the terrain object
|
|
|
Terrain* terrain = new Terrain();
|
|
|
terrain->_heightfield = heightfield;
|
|
|
- terrain->_localScale = scale;
|
|
|
terrain->_materialPath = (materialPath == NULL || strlen(materialPath) == 0) ? TERRAIN_MATERIAL : materialPath;
|
|
|
|
|
|
+ // Store terrain local scaling so it can be applied to the heightfield
|
|
|
+ terrain->_localScale.set(scale);
|
|
|
+
|
|
|
// Store reference to bounding box (it is calculated and updated from TerrainPatch)
|
|
|
BoundingBox& bounds = terrain->_boundingBox;
|
|
|
|
|
|
@@ -264,6 +263,10 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale,
|
|
|
|
|
|
float halfWidth = (width - 1) * 0.5f;
|
|
|
float halfHeight = (height - 1) * 0.5f;
|
|
|
+
|
|
|
+ // Compute the maximum step size, which is a function of our lowest level of detail.
|
|
|
+ // This determines how many vertices will be skipped per triange/quad on the lowest
|
|
|
+ // level detail terrain patch.
|
|
|
unsigned int maxStep = (unsigned int)std::pow(2.0, (double)(detailLevels-1));
|
|
|
|
|
|
// Create terrain patches
|
|
|
@@ -373,21 +376,49 @@ void Terrain::setNode(Node* node)
|
|
|
if (_node != node)
|
|
|
{
|
|
|
if (_node)
|
|
|
+ {
|
|
|
_node->removeListener(this);
|
|
|
+ }
|
|
|
|
|
|
_node = node;
|
|
|
|
|
|
- // Update node bindings for all materails
|
|
|
+ if (_node)
|
|
|
+ {
|
|
|
+ _node->addListener(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update patch node bindings
|
|
|
for (size_t i = 0, count = _patches.size(); i < count; ++i)
|
|
|
{
|
|
|
- _patches[i]->updateNodeBinding(_node);
|
|
|
+ _patches[i]->updateNodeBindings();
|
|
|
}
|
|
|
|
|
|
+ _dirtyFlags |= DIRTY_FLAG_INVERSE_WORLD;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void Terrain::transformChanged(Transform* transform, long cookie)
|
|
|
+{
|
|
|
+ _dirtyFlags |= DIRTY_FLAG_INVERSE_WORLD;
|
|
|
+}
|
|
|
+
|
|
|
+const Matrix& Terrain::getInverseWorldMatrix() const
|
|
|
+{
|
|
|
+ if (_dirtyFlags & DIRTY_FLAG_INVERSE_WORLD)
|
|
|
+ {
|
|
|
+ _dirtyFlags &= ~DIRTY_FLAG_INVERSE_WORLD;
|
|
|
+
|
|
|
if (_node)
|
|
|
- _node->addListener(this);
|
|
|
+ _inverseWorldMatrix.set(_node->getWorldMatrix());
|
|
|
+ else
|
|
|
+ _inverseWorldMatrix = Matrix::identity();
|
|
|
|
|
|
- _dirtyFlags |= TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX;
|
|
|
+ // Apply local scale and invert
|
|
|
+ _inverseWorldMatrix.scale(_localScale);
|
|
|
+ _inverseWorldMatrix.invert();
|
|
|
+
|
|
|
}
|
|
|
+ return _inverseWorldMatrix;
|
|
|
}
|
|
|
|
|
|
bool Terrain::setLayer(int index, const char* texturePath, const Vector2& textureRepeat, const char* blendPath, int blendChannel, int row, int column)
|
|
|
@@ -484,10 +515,16 @@ float Terrain::getHeight(float x, float z) const
|
|
|
// Get the unscaled height value from the HeightField
|
|
|
float height = _heightfield->getHeight(x, z);
|
|
|
|
|
|
- // Now apply world scale (this includes local terrain scale) to the heightfield value
|
|
|
- Vector3 worldScale;
|
|
|
- getWorldMatrix().getScale(&worldScale);
|
|
|
- height *= worldScale.y;
|
|
|
+ // Apply world scale to the height value
|
|
|
+ if (_node)
|
|
|
+ {
|
|
|
+ Vector3 worldScale;
|
|
|
+ _node->getWorldMatrix().getScale(&worldScale);
|
|
|
+ height *= worldScale.y;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply local scale
|
|
|
+ height *= _localScale.y;
|
|
|
|
|
|
return height;
|
|
|
}
|
|
|
@@ -502,84 +539,7 @@ unsigned int Terrain::draw(bool wireframe)
|
|
|
return visibleCount;
|
|
|
}
|
|
|
|
|
|
-void Terrain::transformChanged(Transform* transform, long cookie)
|
|
|
-{
|
|
|
- _dirtyFlags |= TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX;
|
|
|
-}
|
|
|
-
|
|
|
-const Matrix& Terrain::getWorldMatrix() const
|
|
|
-{
|
|
|
- if (_dirtyFlags & TERRAIN_DIRTY_WORLD_MATRIX)
|
|
|
- {
|
|
|
- _dirtyFlags &= ~TERRAIN_DIRTY_WORLD_MATRIX;
|
|
|
-
|
|
|
- // Apply our attached node's world matrix
|
|
|
- if (_node)
|
|
|
- {
|
|
|
- _worldMatrix = _node->getWorldMatrix();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _worldMatrix.setIdentity();
|
|
|
- }
|
|
|
-
|
|
|
- // Factor in our local scaling
|
|
|
- _worldMatrix.scale(_localScale);
|
|
|
- }
|
|
|
-
|
|
|
- return _worldMatrix;
|
|
|
-}
|
|
|
-
|
|
|
-const Matrix& Terrain::getInverseWorldMatrix() const
|
|
|
-{
|
|
|
- if (_dirtyFlags & TERRAIN_DIRTY_INV_WORLD_MATRIX)
|
|
|
- {
|
|
|
- _dirtyFlags &= ~TERRAIN_DIRTY_INV_WORLD_MATRIX;
|
|
|
-
|
|
|
- getWorldMatrix().invert(&_inverseWorldMatrix);
|
|
|
- }
|
|
|
-
|
|
|
- return _inverseWorldMatrix;
|
|
|
-}
|
|
|
-
|
|
|
-const Matrix& Terrain::getNormalMatrix() const
|
|
|
-{
|
|
|
- if (_dirtyFlags & TERRAIN_DIRTY_NORMAL_MATRIX)
|
|
|
- {
|
|
|
- _dirtyFlags &= ~TERRAIN_DIRTY_NORMAL_MATRIX;
|
|
|
-
|
|
|
- // Note: Terrain lighting is done in world space to simplify use of object-space height normal maps.
|
|
|
- getInverseWorldMatrix().transpose(&_normalMatrix);
|
|
|
- }
|
|
|
-
|
|
|
- return _normalMatrix;
|
|
|
-}
|
|
|
-
|
|
|
-const Matrix& Terrain::getWorldViewMatrix() const
|
|
|
-{
|
|
|
- static Matrix worldView;
|
|
|
-
|
|
|
- if (_node)
|
|
|
- Matrix::multiply(_node->getViewMatrix(), getWorldMatrix(), &worldView);
|
|
|
- else
|
|
|
- worldView = getWorldMatrix(); // no node, so nothing to get view from
|
|
|
-
|
|
|
- return worldView;
|
|
|
-}
|
|
|
-
|
|
|
-const Matrix& Terrain::getWorldViewProjectionMatrix() const
|
|
|
-{
|
|
|
- static Matrix worldViewProj;
|
|
|
-
|
|
|
- if (_node)
|
|
|
- Matrix::multiply(_node->getViewProjectionMatrix(), getWorldMatrix(), &worldViewProj);
|
|
|
- else
|
|
|
- worldViewProj = getWorldMatrix(); // no node, so nothing to get viewProjection from
|
|
|
-
|
|
|
- return worldViewProj;
|
|
|
-}
|
|
|
-
|
|
|
-float getDefaultHeight(unsigned int width, unsigned int height)
|
|
|
+static float getDefaultHeight(unsigned int width, unsigned int height)
|
|
|
{
|
|
|
// When terrain height is not specified, we'll use a default height of ~ 0.3 of the image dimensions
|
|
|
return ((width + height) * 0.5f) * DEFAULT_TERRAIN_HEIGHT_RATIO;
|