// // Copyright (c) 2008-2017 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "../Precompiled.h" #include "../Core/Context.h" #include "../Core/Profiler.h" #include "../Graphics/AnimatedModel.h" #include "../Graphics/Batch.h" #include "../Graphics/Camera.h" #include "../Graphics/Geometry.h" #include "../Graphics/Material.h" #include "../Graphics/OcclusionBuffer.h" #include "../Graphics/OctreeQuery.h" #include "../Graphics/VertexBuffer.h" #include "../IO/FileSystem.h" #include "../IO/Log.h" #include "../Resource/ResourceCache.h" #include "../Resource/ResourceEvents.h" #include "../DebugNew.h" namespace Atomic { extern const char* GEOMETRY_CATEGORY; StaticModel::StaticModel(Context* context) : Drawable(context, DRAWABLE_GEOMETRY), occlusionLodLevel_(M_MAX_UNSIGNED), materialsAttr_(Material::GetTypeStatic()) { } StaticModel::~StaticModel() { } void StaticModel::RegisterObject(Context* context) { context->RegisterFactory(GEOMETRY_CATEGORY); ATOMIC_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT); ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT); ATOMIC_ACCESSOR_ATTRIBUTE("Material", GetMaterialsAttr, SetMaterialsAttr, ResourceRefList, ResourceRefList(Material::GetTypeStatic()), AM_DEFAULT); ATOMIC_ATTRIBUTE("Is Occluder", bool, occluder_, false, AM_DEFAULT); ATOMIC_ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT); ATOMIC_ATTRIBUTE("Cast Shadows", bool, castShadows_, false, AM_DEFAULT); ATOMIC_ACCESSOR_ATTRIBUTE("Draw Distance", GetDrawDistance, SetDrawDistance, float, 0.0f, AM_DEFAULT); ATOMIC_ACCESSOR_ATTRIBUTE("Shadow Distance", GetShadowDistance, SetShadowDistance, float, 0.0f, AM_DEFAULT); ATOMIC_ACCESSOR_ATTRIBUTE("LOD Bias", GetLodBias, SetLodBias, float, 1.0f, AM_DEFAULT); ATOMIC_COPY_BASE_ATTRIBUTES(Drawable); ATOMIC_ATTRIBUTE("Occlusion LOD Level", int, occlusionLodLevel_, M_MAX_UNSIGNED, AM_DEFAULT); // ATOMIC BEGIN ATOMIC_ACCESSOR_ATTRIBUTE("Geometry Enabled", GetGeometryEnabledAttr, SetGeometryEnabledAttr, VariantVector, Variant::emptyVariantVector, AM_FILE | AM_NOEDIT); // ATOMIC END } void StaticModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector& results) { RayQueryLevel level = query.level_; switch (level) { case RAY_AABB: Drawable::ProcessRayQuery(query, results); break; case RAY_OBB: case RAY_TRIANGLE: case RAY_TRIANGLE_UV: Matrix3x4 inverse(node_->GetWorldTransform().Inverse()); Ray localRay = query.ray_.Transformed(inverse); float distance = localRay.HitDistance(boundingBox_); Vector3 normal = -query.ray_.direction_; Vector2 geometryUV; unsigned hitBatch = M_MAX_UNSIGNED; if (level >= RAY_TRIANGLE && distance < query.maxDistance_) { distance = M_INFINITY; for (unsigned i = 0; i < batches_.Size(); ++i) { Geometry* geometry = batches_[i].geometry_; if (geometry) { Vector3 geometryNormal; float geometryDistance = level == RAY_TRIANGLE ? geometry->GetHitDistance(localRay, &geometryNormal) : geometry->GetHitDistance(localRay, &geometryNormal, &geometryUV); if (geometryDistance < query.maxDistance_ && geometryDistance < distance) { distance = geometryDistance; normal = (node_->GetWorldTransform() * Vector4(geometryNormal, 0.0f)).Normalized(); hitBatch = i; } } } } if (distance < query.maxDistance_) { RayQueryResult result; result.position_ = query.ray_.origin_ + distance * query.ray_.direction_; result.normal_ = normal; result.textureUV_ = geometryUV; result.distance_ = distance; result.drawable_ = this; result.node_ = node_; result.subObject_ = hitBatch; results.Push(result); } break; } } void StaticModel::UpdateBatches(const FrameInfo& frame) { const BoundingBox& worldBoundingBox = GetWorldBoundingBox(); distance_ = frame.camera_->GetDistance(worldBoundingBox.Center()); if (batches_.Size() == 1) batches_[0].distance_ = distance_; else { const Matrix3x4& worldTransform = node_->GetWorldTransform(); for (unsigned i = 0; i < batches_.Size(); ++i) batches_[i].distance_ = frame.camera_->GetDistance(worldTransform * geometryData_[i].center_); } float scale = worldBoundingBox.Size().DotProduct(DOT_SCALE); float newLodDistance = frame.camera_->GetLodDistance(distance_, scale, lodBias_); if (newLodDistance != lodDistance_) { lodDistance_ = newLodDistance; CalculateLodLevels(); } // ATOMIC BEGIN if (geometryDisabled_) UpdateBatchesHideGeometry(); // ATOMIC END } Geometry* StaticModel::GetLodGeometry(unsigned batchIndex, unsigned level) { if (batchIndex >= geometries_.Size()) return 0; // If level is out of range, use visible geometry if (level < geometries_[batchIndex].Size()) return geometries_[batchIndex][level]; else return batches_[batchIndex].geometry_; } unsigned StaticModel::GetNumOccluderTriangles() { unsigned triangles = 0; for (unsigned i = 0; i < batches_.Size(); ++i) { Geometry* geometry = GetLodGeometry(i, occlusionLodLevel_); if (!geometry) continue; // Check that the material is suitable for occlusion (default material always is) Material* mat = batches_[i].material_; if (mat && !mat->GetOcclusion()) continue; triangles += geometry->GetIndexCount() / 3; } return triangles; } bool StaticModel::DrawOcclusion(OcclusionBuffer* buffer) { for (unsigned i = 0; i < batches_.Size(); ++i) { Geometry* geometry = GetLodGeometry(i, occlusionLodLevel_); if (!geometry) continue; // Check that the material is suitable for occlusion (default material always is) and set culling mode Material* material = batches_[i].material_; if (material) { if (!material->GetOcclusion()) continue; buffer->SetCullMode(material->GetCullMode()); } else buffer->SetCullMode(CULL_CCW); const unsigned char* vertexData; unsigned vertexSize; const unsigned char* indexData; unsigned indexSize; const PODVector* elements; geometry->GetRawData(vertexData, vertexSize, indexData, indexSize, elements); // Check for valid geometry data if (!vertexData || !indexData || !elements || VertexBuffer::GetElementOffset(*elements, TYPE_VECTOR3, SEM_POSITION) != 0) continue; unsigned indexStart = geometry->GetIndexStart(); unsigned indexCount = geometry->GetIndexCount(); // Draw and check for running out of triangles if (!buffer->AddTriangles(node_->GetWorldTransform(), vertexData, vertexSize, indexData, indexSize, indexStart, indexCount)) return false; } return true; } void StaticModel::SetModel(Model* model) { if (model == model_) return; // ATOMIC BEGIN // If script erroneously calls StaticModel::SetModel on an AnimatedModel, warn and redirect if (GetType() == AnimatedModel::GetTypeStatic()) { ATOMIC_LOGWARNING("StaticModel::SetModel() called on AnimatedModel. Redirecting to AnimatedModel::SetModel()"); AnimatedModel* animatedModel = static_cast(this); animatedModel->SetModel(model); return; } // ATOMIC END if (!node_) { ATOMIC_LOGERROR("Can not set model while model component is not attached to a scene node"); return; } // Unsubscribe from the reload event of previous model (if any), then subscribe to the new if (model_) UnsubscribeFromEvent(model_, E_RELOADFINISHED); model_ = model; if (model) { SubscribeToEvent(model, E_RELOADFINISHED, ATOMIC_HANDLER(StaticModel, HandleModelReloadFinished)); // Copy the subgeometry & LOD level structure SetNumGeometries(model->GetNumGeometries()); const Vector > >& geometries = model->GetGeometries(); const PODVector& geometryCenters = model->GetGeometryCenters(); const Matrix3x4* worldTransform = node_ ? &node_->GetWorldTransform() : (const Matrix3x4*)0; for (unsigned i = 0; i < geometries.Size(); ++i) { batches_[i].worldTransform_ = worldTransform; geometries_[i] = geometries[i]; geometryData_[i].center_ = geometryCenters[i]; // ATOMIC BEGIN geometryData_[i].enabled_ = true; geometryData_[i].batchGeometry_ = 0; // ATOMIC END } SetBoundingBox(model->GetBoundingBox()); ResetLodLevels(); } else { SetNumGeometries(0); SetBoundingBox(BoundingBox()); } MarkNetworkUpdate(); } void StaticModel::SetMaterial(Material* material) { for (unsigned i = 0; i < batches_.Size(); ++i) batches_[i].material_ = material; MarkNetworkUpdate(); } bool StaticModel::SetMaterial(unsigned index, Material* material) { if (index >= batches_.Size()) { ATOMIC_LOGERROR("Material index out of bounds"); return false; } batches_[index].material_ = material; MarkNetworkUpdate(); return true; } void StaticModel::SetOcclusionLodLevel(unsigned level) { occlusionLodLevel_ = level; MarkNetworkUpdate(); } void StaticModel::ApplyMaterialList(const String& fileName) { String useFileName = fileName; if (useFileName.Trimmed().Empty() && model_) useFileName = ReplaceExtension(model_->GetName(), ".txt"); ResourceCache* cache = GetSubsystem(); SharedPtr file = cache->GetFile(useFileName, false); if (!file) return; unsigned index = 0; while (!file->IsEof() && index < batches_.Size()) { Material* material = cache->GetResource(file->ReadLine()); if (material) SetMaterial(index, material); ++index; } } Material* StaticModel::GetMaterial(unsigned index) const { return index < batches_.Size() ? batches_[index].material_ : (Material*)0; } bool StaticModel::IsInside(const Vector3& point) const { if (!node_) return false; Vector3 localPosition = node_->GetWorldTransform().Inverse() * point; return IsInsideLocal(localPosition); } bool StaticModel::IsInsideLocal(const Vector3& point) const { // Early-out if point is not inside bounding box if (boundingBox_.IsInside(point) == OUTSIDE) return false; Ray localRay(point, Vector3(1.0f, -1.0f, 1.0f)); for (unsigned i = 0; i < batches_.Size(); ++i) { Geometry* geometry = batches_[i].geometry_; if (geometry) { if (geometry->IsInside(localRay)) return true; } } return false; } void StaticModel::SetBoundingBox(const BoundingBox& box) { boundingBox_ = box; OnMarkedDirty(node_); } void StaticModel::SetNumGeometries(unsigned num) { batches_.Resize(num); geometries_.Resize(num); geometryData_.Resize(num); ResetLodLevels(); } void StaticModel::SetModelAttr(const ResourceRef& value) { ResourceCache* cache = GetSubsystem(); SetModel(cache->GetResource(value.name_)); } void StaticModel::SetMaterialsAttr(const ResourceRefList& value) { ResourceCache* cache = GetSubsystem(); for (unsigned i = 0; i < value.names_.Size(); ++i) SetMaterial(i, cache->GetResource(value.names_[i])); } ResourceRef StaticModel::GetModelAttr() const { return GetResourceRef(model_, Model::GetTypeStatic()); } const ResourceRefList& StaticModel::GetMaterialsAttr() const { materialsAttr_.names_.Resize(batches_.Size()); for (unsigned i = 0; i < batches_.Size(); ++i) materialsAttr_.names_[i] = GetResourceName(GetMaterial(i)); return materialsAttr_; } void StaticModel::OnWorldBoundingBoxUpdate() { worldBoundingBox_ = boundingBox_.Transformed(node_->GetWorldTransform()); } void StaticModel::ResetLodLevels() { // Ensure that each subgeometry has at least one LOD level, and reset the current LOD level for (unsigned i = 0; i < batches_.Size(); ++i) { if (!geometries_[i].Size()) geometries_[i].Resize(1); batches_[i].geometry_ = geometries_[i][0]; geometryData_[i].lodLevel_ = 0; } // Find out the real LOD levels on next geometry update lodDistance_ = M_INFINITY; } void StaticModel::CalculateLodLevels() { for (unsigned i = 0; i < batches_.Size(); ++i) { const Vector >& batchGeometries = geometries_[i]; // If only one LOD geometry, no reason to go through the LOD calculation if (batchGeometries.Size() <= 1) continue; unsigned j; for (j = 1; j < batchGeometries.Size(); ++j) { if (batchGeometries[j] && lodDistance_ <= batchGeometries[j]->GetLodDistance()) break; } unsigned newLodLevel = j - 1; if (geometryData_[i].lodLevel_ != newLodLevel) { geometryData_[i].lodLevel_ = newLodLevel; batches_[i].geometry_ = batchGeometries[newLodLevel]; } } } void StaticModel::HandleModelReloadFinished(StringHash eventType, VariantMap& eventData) { Model* currentModel = model_; model_.Reset(); // Set null to allow to be re-set SetModel(currentModel); } // ATOMIC BEGIN bool StaticModel::GetGeometryVisible(const String& name) { if (!model_) return false; const Vector& names = model_->GetGeometryNames(); for (unsigned i = 0; i < names.Size(); i++) { if (name == names[i]) return geometryData_[i].enabled_; } return false; } void StaticModel::ShowGeometry(const String& name) { if (!model_) return; const Vector& names = model_->GetGeometryNames(); for (unsigned i = 0; i < names.Size(); i++) { if (name == names[i]) { if (geometryData_[i].batchGeometry_) batches_[i].geometry_ = geometryData_[i].batchGeometry_; geometryData_[i].batchGeometry_ = 0; geometryData_[i].enabled_ = true; } } geometryDisabled_ = false; for (unsigned i = 0; i < geometryData_.Size(); i++) { if (!geometryData_[i].enabled_) { geometryDisabled_ = true; break; } } } void StaticModel::HideGeometry(const String& name) { if (!model_) return; const Vector& names = model_->GetGeometryNames(); for (unsigned i = 0; i < names.Size(); i++) { if (name == names[i]) { geometryDisabled_ = true; if (batches_[i].geometry_) geometryData_[i].batchGeometry_ = batches_[i].geometry_; geometryData_[i].enabled_ = false; } } } void StaticModel::SetGeometryEnabledAttr(const VariantVector& value) { if (!value.Size() || value.Size() != geometryData_.Size()) { geometryDisabled_ = false; geometryEnabled_.Clear(); return; } bool init = !geometryEnabled_.Size(); geometryEnabled_ = value; for (unsigned i = 0; i < geometryData_.Size(); i++) { geometryData_[i].enabled_ = geometryEnabled_[i].GetBool(); if (!geometryData_[i].enabled_) geometryDisabled_ = true; if (init) geometryData_[i].batchGeometry_ = 0; } } const VariantVector& StaticModel::GetGeometryEnabledAttr() const { geometryEnabled_.Resize(geometryData_.Size()); geometryDisabled_ = false; for (unsigned i = 0; i < geometryData_.Size(); i++) { geometryEnabled_[i] = geometryData_[i].enabled_; if (!geometryData_[i].enabled_) geometryDisabled_ = true; } return geometryEnabled_; } void StaticModel::UpdateBatchesHideGeometry() { if (!geometryDisabled_ || !batches_.Size()) return; for (unsigned i = 0; i < batches_.Size(); ++i) { SourceBatch* batch = &batches_[i]; StaticModelGeometryData* data = &geometryData_[i]; if (batch->geometry_) data->batchGeometry_ = batch->geometry_; if (data->enabled_ && !batch->geometry_) { batch->geometry_ = data->batchGeometry_; } else if (!data->enabled_ && batch->geometry_) { data->batchGeometry_ = batch->geometry_; batch->geometry_ = 0; } } } // ATOMIC END }