void Terrain::CreateGeometry() { recreateTerrain_ = false; if (!node_) return; URHO3D_PROFILE(CreateTerrainGeometry); unsigned prevNumPatches = patches_.Size(); // Determine number of LOD levels unsigned lodSize = (unsigned)patchSize_; numLodLevels_ = 1; while (lodSize > MIN_PATCH_SIZE && numLodLevels_ < maxLodLevels_) { lodSize >>= 1; ++numLodLevels_; } // Determine total terrain size patchWorldSize_ = Vector2(spacing_.x_ * (float)patchSize_, spacing_.z_ * (float)patchSize_); bool updateAll = false; if (heightMap_) { numPatches_ = IntVector2((heightMap_->GetWidth() - 1) / patchSize_, (heightMap_->GetHeight() - 1) / patchSize_); numVertices_ = IntVector2(numPatches_.x_ * patchSize_ + 1, numPatches_.y_ * patchSize_ + 1); patchWorldOrigin_ = Vector2(-0.5f * (float)numPatches_.x_ * patchWorldSize_.x_, -0.5f * (float)numPatches_.y_ * patchWorldSize_.y_); if (numVertices_ != lastNumVertices_ || lastSpacing_ != spacing_ || patchSize_ != lastPatchSize_) updateAll = true; unsigned newDataSize = (unsigned)(numVertices_.x_ * numVertices_.y_); // Create new height data if terrain size changed if (!heightData_ || updateAll) heightData_ = new float[newDataSize]; // Ensure that the source (unsmoothed) data exists if smoothing is active if (smoothing_ && (!sourceHeightData_ || updateAll)) { sourceHeightData_ = new float[newDataSize]; updateAll = true; } else if (!smoothing_) sourceHeightData_.Reset(); } else { numPatches_ = IntVector2::ZERO; numVertices_ = IntVector2::ZERO; patchWorldOrigin_ = Vector2::ZERO; heightData_.Reset(); sourceHeightData_.Reset(); } lastNumVertices_ = numVertices_; lastPatchSize_ = patchSize_; lastSpacing_ = spacing_; // Remove old patch nodes which are not needed if (updateAll) { URHO3D_PROFILE(RemoveOldPatches); PODVector<Node*> oldPatchNodes; node_->GetChildrenWithComponent<TerrainPatch>(oldPatchNodes); for (PODVector<Node*>::Iterator i = oldPatchNodes.Begin(); i != oldPatchNodes.End(); ++i) { bool nodeOk = false; Vector<String> coords = (*i)->GetName().Substring(6).Split('_'); if (coords.Size() == 2) { int x = ToInt(coords[0]); int z = ToInt(coords[1]); if (x < numPatches_.x_ && z < numPatches_.y_) nodeOk = true; } if (!nodeOk) node_->RemoveChild(*i); } } // Keep track of which patches actually need an update PODVector<bool> dirtyPatches((unsigned)(numPatches_.x_ * numPatches_.y_)); for (unsigned i = 0; i < dirtyPatches.Size(); ++i) dirtyPatches[i] = updateAll; patches_.Clear(); if (heightMap_) { // Copy heightmap data const unsigned char* src = heightMap_->GetData(); float* dest = smoothing_ ? sourceHeightData_ : heightData_; unsigned imgComps = heightMap_->GetComponents(); unsigned imgRow = heightMap_->GetWidth() * imgComps; IntRect updateRegion(-1, -1, -1, -1); if (imgComps == 1) { URHO3D_PROFILE(CopyHeightData); for (int z = 0; z < numVertices_.y_; ++z) { for (int x = 0; x < numVertices_.x_; ++x) { float newHeight = (float)src[imgRow * (numVertices_.y_ - 1 - z) + x] * spacing_.y_; if (updateAll) *dest = newHeight; else { if (*dest != newHeight) { *dest = newHeight; GrowUpdateRegion(updateRegion, x, z); } } ++dest; } } } else { URHO3D_PROFILE(CopyHeightData); // If more than 1 component, use the green channel for more accuracy for (int z = 0; z < numVertices_.y_; ++z) { for (int x = 0; x < numVertices_.x_; ++x) { float newHeight = ((float)src[imgRow * (numVertices_.y_ - 1 - z) + imgComps * x] + (float)src[imgRow * (numVertices_.y_ - 1 - z) + imgComps * x + 1] / 256.0f) * spacing_.y_; if (updateAll) *dest = newHeight; else { if (*dest != newHeight) { *dest = newHeight; GrowUpdateRegion(updateRegion, x, z); } } ++dest; } } } // If updating a region of the heightmap, check which patches change if (!updateAll) { int lodExpand = 1 << (numLodLevels_ - 1); // Expand the right & bottom 1 pixel more, as patches share vertices at the edge updateRegion.left_ -= lodExpand; updateRegion.right_ += lodExpand + 1; updateRegion.top_ -= lodExpand; updateRegion.bottom_ += lodExpand + 1; int sX = Max(updateRegion.left_ / patchSize_, 0); int eX = Min(updateRegion.right_ / patchSize_, numPatches_.x_ - 1); int sY = Max(updateRegion.top_ / patchSize_, 0); int eY = Min(updateRegion.bottom_ / patchSize_, numPatches_.y_ - 1); for (int y = sY; y <= eY; ++y) { for (int x = sX; x <= eX; ++x) dirtyPatches[y * numPatches_.x_ + x] = true; } } patches_.Reserve((unsigned)(numPatches_.x_ * numPatches_.y_)); bool enabled = IsEnabledEffective(); { URHO3D_PROFILE(CreatePatches); // Create patches and set node transforms for (int z = 0; z < numPatches_.y_; ++z) { for (int x = 0; x < numPatches_.x_; ++x) { String nodeName = "Patch_" + String(x) + "_" + String(z); Node* patchNode = node_->GetChild(nodeName); if (!patchNode) { // Create the patch scene node as local and temporary so that it is not unnecessarily serialized to either // file or replicated over the network patchNode = node_->CreateChild(nodeName, LOCAL); patchNode->SetTemporary(true); } patchNode->SetPosition(Vector3(patchWorldOrigin_.x_ + (float)x * patchWorldSize_.x_, 0.0f, patchWorldOrigin_.y_ + (float)z * patchWorldSize_.y_)); TerrainPatch* patch = patchNode->GetComponent<TerrainPatch>(); if (!patch) { patch = patchNode->CreateComponent<TerrainPatch>(); patch->SetOwner(this); patch->SetCoordinates(IntVector2(x, z)); // Copy initial drawable parameters patch->SetEnabled(enabled); patch->SetMaterial(material_); patch->SetDrawDistance(drawDistance_); patch->SetShadowDistance(shadowDistance_); patch->SetLodBias(lodBias_); patch->SetViewMask(viewMask_); patch->SetLightMask(lightMask_); patch->SetShadowMask(shadowMask_); patch->SetZoneMask(zoneMask_); patch->SetMaxLights(maxLights_); patch->SetCastShadows(castShadows_); patch->SetOccluder(occluder_); patch->SetOccludee(occludee_); } patches_.Push(WeakPtr<TerrainPatch>(patch)); } } } // Create the shared index data if (updateAll) CreateIndexData(); // Create vertex data for patches. First update smoothing to ensure normals are calculated correctly across patch borders if (smoothing_) { URHO3D_PROFILE(UpdateSmoothing); for (unsigned i = 0; i < patches_.Size(); ++i) { if (dirtyPatches[i]) { TerrainPatch* patch = patches_[i]; const IntVector2& coords = patch->GetCoordinates(); int startX = coords.x_ * patchSize_; int endX = startX + patchSize_; int startZ = coords.y_ * patchSize_; int endZ = startZ + patchSize_; for (int z = startZ; z <= endZ; ++z) { for (int x = startX; x <= endX; ++x) { float smoothedHeight = ( GetSourceHeight(x - 1, z - 1) + GetSourceHeight(x, z - 1) * 2.0f + GetSourceHeight(x + 1, z - 1) + GetSourceHeight(x - 1, z) * 2.0f + GetSourceHeight(x, z) * 4.0f + GetSourceHeight(x + 1, z) * 2.0f + GetSourceHeight(x - 1, z + 1) + GetSourceHeight(x, z + 1) * 2.0f + GetSourceHeight(x + 1, z + 1) ) / 16.0f; heightData_[z * numVertices_.x_ + x] = smoothedHeight; } } } } } for (unsigned i = 0; i < patches_.Size(); ++i) { TerrainPatch* patch = patches_[i]; if (dirtyPatches[i]) { CreatePatchGeometry(patch); CalculateLodErrors(patch); } SetPatchNeighbors(patch); } } // Send event only if new geometry was generated, or the old was cleared if (patches_.Size() || prevNumPatches) { using namespace TerrainCreated; VariantMap& eventData = GetEventDataMap(); eventData[P_NODE] = node_; node_->SendEvent(E_TERRAINCREATED, eventData); } }
void Terrain::CreateGeometry() { recreateTerrain_ = false; if (!node_) return; PROFILE(CreateTerrainGeometry); unsigned prevNumPatches = patches_.Size(); // Determine number of LOD levels unsigned lodSize = patchSize_; numLodLevels_ = 1; while (lodSize > MIN_PATCH_SIZE && numLodLevels_ < MAX_LOD_LEVELS) { lodSize >>= 1; ++numLodLevels_; } // Determine total terrain size patchWorldSize_ = Vector2(spacing_.x_ * (float)patchSize_, spacing_.z_ * (float)patchSize_); if (heightMap_) { numPatches_ = IntVector2((heightMap_->GetWidth() - 1) / patchSize_, (heightMap_->GetHeight() - 1) / patchSize_); numVertices_ = IntVector2(numPatches_.x_ * patchSize_ + 1, numPatches_.y_ * patchSize_ + 1); patchWorldOrigin_ = Vector2(-0.5f * (float)numPatches_.x_ * patchWorldSize_.x_, -0.5f * (float)numPatches_.y_ * patchWorldSize_.y_); heightData_ = new float[numVertices_.x_ * numVertices_.y_]; } else { numPatches_ = IntVector2::ZERO; numVertices_ = IntVector2::ZERO; patchWorldOrigin_ = Vector2::ZERO; heightData_.Reset(); } // Remove old patch nodes which are not needed PODVector<Node*> oldPatchNodes; node_->GetChildrenWithComponent<TerrainPatch>(oldPatchNodes); for (PODVector<Node*>::Iterator i = oldPatchNodes.Begin(); i != oldPatchNodes.End(); ++i) { bool nodeOk = false; Vector<String> coords = (*i)->GetName().Substring(6).Split('_'); if (coords.Size() == 2) { int x = ToInt(coords[0]); int z = ToInt(coords[1]); if (x < numPatches_.x_ && z < numPatches_.y_) nodeOk = true; } if (!nodeOk) node_->RemoveChild(*i); } patches_.Clear(); if (heightMap_) { // Copy heightmap data const unsigned char* src = heightMap_->GetData(); float* dest = heightData_; unsigned imgComps = heightMap_->GetComponents(); unsigned imgRow = heightMap_->GetWidth() * imgComps; if (imgComps == 1) { for (int z = 0; z < numVertices_.y_; ++z) { for (int x = 0; x < numVertices_.x_; ++x) *dest++ = (float)src[imgRow * (numVertices_.y_ - 1 - z) + x] * spacing_.y_; } } else { // If more than 1 component, use the green channel for more accuracy for (int z = 0; z < numVertices_.y_; ++z) { for (int x = 0; x < numVertices_.x_; ++x) *dest++ = ((float)src[imgRow * (numVertices_.y_ - 1 - z) + imgComps * x] + (float)src[imgRow * (numVertices_.y_ - 1 - z) + imgComps * x + 1] / 256.0f) * spacing_.y_; } } if (smoothing_) SmoothHeightMap(); patches_.Reserve(numPatches_.x_ * numPatches_.y_); bool enabled = IsEnabledEffective(); // Create patches and set node transforms for (int z = 0; z < numPatches_.y_; ++z) { for (int x = 0; x < numPatches_.x_; ++x) { String nodeName = "Patch_" + String(x) + "_" + String(z); Node* patchNode = node_->GetChild(nodeName); if (!patchNode) { // Create the patch scene node as local and temporary so that it is not unnecessarily serialized to either // file or replicated over the network patchNode = node_->CreateChild(nodeName, LOCAL); patchNode->SetTemporary(true); } patchNode->SetPosition(Vector3(patchWorldOrigin_.x_ + (float)x * patchWorldSize_.x_, 0.0f, patchWorldOrigin_.y_ + (float)z * patchWorldSize_.y_)); TerrainPatch* patch = patchNode->GetOrCreateComponent<TerrainPatch>(); patch->SetOwner(this); patch->SetCoordinates(IntVector2(x, z)); // Copy initial drawable parameters patch->SetEnabled(enabled); patch->SetMaterial(material_); patch->SetDrawDistance(drawDistance_); patch->SetShadowDistance(shadowDistance_); patch->SetLodBias(lodBias_); patch->SetViewMask(viewMask_); patch->SetLightMask(lightMask_); patch->SetShadowMask(shadowMask_); patch->SetZoneMask(zoneMask_); patch->SetMaxLights(maxLights_); patch->SetCastShadows(castShadows_); patch->SetOccluder(occluder_); patch->SetOccludee(occludee_); patches_.Push(WeakPtr<TerrainPatch>(patch)); } } // Create the shared index data CreateIndexData(); // Create vertex data for patches for (Vector<WeakPtr<TerrainPatch> >::Iterator i = patches_.Begin(); i != patches_.End(); ++i) { CreatePatchGeometry(*i); CalculateLodErrors(*i); SetNeighbors(*i); } } // Send event only if new geometry was generated, or the old was cleared if (patches_.Size() || prevNumPatches) { using namespace TerrainCreated; VariantMap& eventData = GetEventDataMap(); eventData[P_NODE] = node_; node_->SendEvent(E_TERRAINCREATED, eventData); } }