|
|
@@ -0,0 +1,897 @@
|
|
|
+//
|
|
|
+// Copyright (c) 2008-2016 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 "../Graphics/RibbonTrail.h"
|
|
|
+#include "../Scene/Scene.h"
|
|
|
+#include "../Scene/SceneEvents.h"
|
|
|
+#include "../Resource/ResourceCache.h"
|
|
|
+#include "../Graphics/VertexBuffer.h"
|
|
|
+#include "../Graphics/IndexBuffer.h"
|
|
|
+#include "../Scene/Node.h"
|
|
|
+#include "../Graphics/Camera.h"
|
|
|
+#include "../Graphics/Material.h"
|
|
|
+#include "../Graphics/OctreeQuery.h"
|
|
|
+#include "../Graphics/Geometry.h"
|
|
|
+#include "../IO/Log.h"
|
|
|
+
|
|
|
+namespace Urho3D
|
|
|
+{
|
|
|
+
|
|
|
+extern const char* GEOMETRY_CATEGORY;
|
|
|
+static const unsigned MAX_TAIL_COLUMN = 16;
|
|
|
+
|
|
|
+const char* trailTypeNames[] =
|
|
|
+{
|
|
|
+ "Face Camera",
|
|
|
+ "Bone",
|
|
|
+ 0
|
|
|
+};
|
|
|
+
|
|
|
+inline bool CompareTails(Point* lhs, Point* rhs)
|
|
|
+{
|
|
|
+ return lhs->sortDistance_ > rhs->sortDistance_;
|
|
|
+}
|
|
|
+
|
|
|
+RibbonTrail::RibbonTrail(Context* context) :
|
|
|
+ Drawable(context, DRAWABLE_GEOMETRY),
|
|
|
+ geometry_(new Geometry(context_)),
|
|
|
+ animationLodBias_(1.0f),
|
|
|
+ animationLodTimer_(0.0f),
|
|
|
+ vertexBuffer_(new VertexBuffer(context_)),
|
|
|
+ indexBuffer_(new IndexBuffer(context_)),
|
|
|
+ bufferDirty_(true),
|
|
|
+ previousPosition_(Vector3::ZERO),
|
|
|
+ numPoints_(0),
|
|
|
+ lifetime_(1.0f),
|
|
|
+ vertexDistance_(0.1f),
|
|
|
+ width_(0.2f),
|
|
|
+ startScale_(1.0f),
|
|
|
+ endScale_(1.0f),
|
|
|
+ endColor_(Color(1.0f, 1.0f, 1.0f, 0.0f)),
|
|
|
+ startColor_(Color(1.0f, 1.0f, 1.0f, 1.0f)),
|
|
|
+ lastUpdateFrameNumber_(M_MAX_UNSIGNED),
|
|
|
+ needUpdate_(false),
|
|
|
+ sorted_(false),
|
|
|
+ previousOffset_(Vector3::ZERO),
|
|
|
+ forceUpdate_(false),
|
|
|
+ trailType_(TT_FACE_CAMERA),
|
|
|
+ tailColumn_(1),
|
|
|
+ emitting_(true)
|
|
|
+{
|
|
|
+ geometry_->SetVertexBuffer(0, vertexBuffer_);
|
|
|
+ geometry_->SetIndexBuffer(indexBuffer_);
|
|
|
+
|
|
|
+ transforms_ = Matrix3x4::IDENTITY;
|
|
|
+
|
|
|
+ batches_.Resize(1);
|
|
|
+ batches_[0].geometry_ = geometry_;
|
|
|
+ batches_[0].geometryType_ = GEOM_TRAIL_FACE_CAMERA;
|
|
|
+ batches_[0].worldTransform_ = &transforms_;
|
|
|
+ batches_[0].numWorldTransforms_ = 1;
|
|
|
+
|
|
|
+ // for debug
|
|
|
+ ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
|
+ SetMaterial(cache->GetResource<Material>("Materials/TailGenerator.xml"));
|
|
|
+}
|
|
|
+
|
|
|
+RibbonTrail::~RibbonTrail()
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::RegisterObject(Context* context)
|
|
|
+{
|
|
|
+ context->RegisterFactory<RibbonTrail>(GEOMETRY_CATEGORY);
|
|
|
+
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
|
|
|
+ URHO3D_COPY_BASE_ATTRIBUTES(Drawable);
|
|
|
+ URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()), AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Emittting", IsEmitting, SetEmitting, bool, true, AM_DEFAULT);
|
|
|
+ URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Trail Type", GetTrailType, SetTrailType, TrailType, trailTypeNames, TT_FACE_CAMERA, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Tail Lifetime", GetLifetime, SetLifetime, float, 1.0f, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Tail Column", GetTailColumn, SetTailColumn, unsigned, 0, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Vertex Distance", GetVertexDistance, SetVertexDistance, float, 0.1f, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Width", GetWidth, SetWidth, float, 0.2f, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Start Scale", GetStartScale, SetStartScale, float, 1.0f, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("End Scale", GetEndScale, SetEndScale, float, 1.0f, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Start Color", GetStartColor, SetStartColor, Color, Color::WHITE, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("End Color", GetEndColor, SetEndColor, Color, Color(1.0f, 1.0f, 1.0f, 0.0f), AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Animation LOD Bias", GetAnimationLodBias, SetAnimationLodBias, float, 1.0f, AM_DEFAULT);
|
|
|
+ URHO3D_ACCESSOR_ATTRIBUTE("Sort By Distance", IsSorted, SetSorted, bool, false, AM_DEFAULT);
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results)
|
|
|
+{
|
|
|
+ // If no billboard-level testing, use the Drawable test
|
|
|
+ if (query.level_ < RAY_TRIANGLE)
|
|
|
+ {
|
|
|
+ Drawable::ProcessRayQuery(query, results);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check ray hit distance to AABB before proceeding with trail-level tests
|
|
|
+ if (query.ray_.HitDistance(GetWorldBoundingBox()) >= query.maxDistance_)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Approximate the tails as spheres for raycasting
|
|
|
+ for (unsigned i = 0; i < points_.Size() - 1; ++i)
|
|
|
+ {
|
|
|
+ Vector3 center = (points_[i].position_ + points_[i+1].position_) * 0.5f;
|
|
|
+ Vector3 scale = width_ * Vector3::ONE;
|
|
|
+ // Tail should be represented in cylinder shape, but we don't have this yet on Urho,
|
|
|
+ // so this implementation will use bounding box instead (hopefully only temporarily)
|
|
|
+ float distance = query.ray_.HitDistance(BoundingBox(center - scale, center + scale));
|
|
|
+ if (distance < query.maxDistance_)
|
|
|
+ {
|
|
|
+ // If the code reaches here then we have a hit
|
|
|
+ RayQueryResult result;
|
|
|
+ result.position_ = query.ray_.origin_ + distance * query.ray_.direction_;
|
|
|
+ result.normal_ = -query.ray_.direction_;
|
|
|
+ result.distance_ = distance;
|
|
|
+ result.drawable_ = this;
|
|
|
+ result.node_ = node_;
|
|
|
+ result.subObject_ = i;
|
|
|
+ results.Push(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::OnSetEnabled()
|
|
|
+{
|
|
|
+ Drawable::OnSetEnabled();
|
|
|
+
|
|
|
+ previousPosition_ = node_->GetWorldPosition();
|
|
|
+
|
|
|
+ Scene* scene = GetScene();
|
|
|
+ if (scene)
|
|
|
+ {
|
|
|
+ if (IsEnabledEffective())
|
|
|
+ SubscribeToEvent(scene, E_SCENEPOSTUPDATE, URHO3D_HANDLER(RibbonTrail, HandleScenePostUpdate));
|
|
|
+ else
|
|
|
+ UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
|
|
|
+{
|
|
|
+ using namespace ScenePostUpdate;
|
|
|
+ lastTimeStep_ = eventData[P_TIMESTEP].GetFloat();
|
|
|
+
|
|
|
+ // Update if frame has changed
|
|
|
+ if (viewFrameNumber_ != lastUpdateFrameNumber_)
|
|
|
+ {
|
|
|
+ // Reset if ribbon trail is too small and too much difference in frame
|
|
|
+ if(points_.Size() < 3 && viewFrameNumber_ - lastUpdateFrameNumber_ > 1)
|
|
|
+ {
|
|
|
+ previousPosition_ = node_->GetWorldPosition();
|
|
|
+ points_.Erase(0, points_.Size());
|
|
|
+ }
|
|
|
+
|
|
|
+ lastUpdateFrameNumber_ = viewFrameNumber_;
|
|
|
+ needUpdate_ = true;
|
|
|
+ MarkForUpdate();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::Update(const FrameInfo &frame)
|
|
|
+{
|
|
|
+ Drawable::Update(frame);
|
|
|
+
|
|
|
+ if (!needUpdate_)
|
|
|
+ return;
|
|
|
+
|
|
|
+ UpdateTail();
|
|
|
+ OnMarkedDirty(node_);
|
|
|
+ needUpdate_ = false;
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::UpdateTail()
|
|
|
+{
|
|
|
+ Vector3 worldPosition = node_->GetWorldPosition();
|
|
|
+ float path = (previousPosition_ - worldPosition).Length();
|
|
|
+
|
|
|
+ // Update tails lifetime
|
|
|
+ int expiredIndex = -1;
|
|
|
+ if (points_.Size() > 0)
|
|
|
+ {
|
|
|
+ // No need to update last point
|
|
|
+ for (unsigned i = 0; i < points_.Size() - 1; ++i)
|
|
|
+ {
|
|
|
+ points_[i].lifetime_ += lastTimeStep_;
|
|
|
+
|
|
|
+ // Get point index with expired lifetime
|
|
|
+ if(points_[i].lifetime_ > lifetime_)
|
|
|
+ expiredIndex = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Delete expired points
|
|
|
+ if(expiredIndex != -1)
|
|
|
+ {
|
|
|
+ points_.Erase(0, expiredIndex+1);
|
|
|
+
|
|
|
+ // Update endTail pointer
|
|
|
+ if(points_.Size() > 1)
|
|
|
+ {
|
|
|
+ endTail_.position_ = points_[0].position_;
|
|
|
+ startEndTailTime_ = points_[0].lifetime_;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update previous world position if trail is still zero
|
|
|
+ if (points_.Size() == 0)
|
|
|
+ {
|
|
|
+ previousPosition_ = worldPosition;
|
|
|
+ }
|
|
|
+ // Delete lonely point
|
|
|
+ else if(points_.Size() == 1)
|
|
|
+ {
|
|
|
+ points_.Erase(0, 1);
|
|
|
+ previousPosition_ = worldPosition;
|
|
|
+ }
|
|
|
+ // Update end of trail position using endTail linear interpolation
|
|
|
+ else if (points_.Size() > 1 && points_[0].lifetime_ < lifetime_)
|
|
|
+ {
|
|
|
+ float step = SmoothStep(startEndTailTime_, lifetime_, points_[0].lifetime_);
|
|
|
+ points_[0].position_ = Lerp(endTail_.position_, points_[1].position_, step);
|
|
|
+ bufferDirty_ = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add starting points
|
|
|
+ if(points_.Size() == 0 && path > M_LARGE_EPSILON && emitting_)
|
|
|
+ {
|
|
|
+ Vector3 forwardmotion = (previousPosition_ - worldPosition).Normalized();
|
|
|
+
|
|
|
+ Point startPoint;
|
|
|
+ startPoint.position_ = previousPosition_;
|
|
|
+ startPoint.lifetime_ = 0.0f;
|
|
|
+ startPoint.forward_ = forwardmotion;
|
|
|
+
|
|
|
+ Point nextPoint;
|
|
|
+ nextPoint.position_ = worldPosition;
|
|
|
+ nextPoint.lifetime_ = 0.0f;
|
|
|
+ nextPoint.forward_ = forwardmotion;
|
|
|
+
|
|
|
+ if(node_->GetParent() != 0)
|
|
|
+ {
|
|
|
+ startPoint.parentPos_ = node_->GetParent()->GetWorldPosition();
|
|
|
+ nextPoint.parentPos_ = node_->GetParent()->GetWorldPosition();
|
|
|
+ }
|
|
|
+
|
|
|
+ points_.Push(startPoint);
|
|
|
+ points_.Push(nextPoint);
|
|
|
+
|
|
|
+ // Update endtail
|
|
|
+ endTail_.position_ = startPoint.position_;
|
|
|
+ startEndTailTime_ = 0.0f;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add more points
|
|
|
+ if (points_.Size() > 1 && emitting_)
|
|
|
+ {
|
|
|
+ Vector3 forwardmotion = (previousPosition_ - worldPosition).Normalized();
|
|
|
+
|
|
|
+ // Add more point if path exceeded tail length
|
|
|
+ if(path > vertexDistance_)
|
|
|
+ {
|
|
|
+ Point newPoint;
|
|
|
+ newPoint.position_ = worldPosition;
|
|
|
+ newPoint.lifetime_ = 0.0f;
|
|
|
+ newPoint.forward_ = forwardmotion;
|
|
|
+ if(node_->GetParent() != 0)
|
|
|
+ newPoint.parentPos_ = node_->GetParent()->GetWorldPosition();
|
|
|
+
|
|
|
+ points_.Push(newPoint);
|
|
|
+
|
|
|
+ previousPosition_ = worldPosition;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Updating recent tail
|
|
|
+ points_.Back().position_ = worldPosition;
|
|
|
+ if(forwardmotion != Vector3::ZERO)
|
|
|
+ points_.Back().forward_ = forwardmotion;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update buffer size if size of points different with tail number
|
|
|
+ if (points_.Size() != numPoints_)
|
|
|
+ bufferSizeDirty_ = true;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetEndScale(float endScale)
|
|
|
+{
|
|
|
+ endScale_ = endScale;
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetStartScale(float startScale)
|
|
|
+{
|
|
|
+ startScale_ = startScale;
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetEmitting(bool emitting)
|
|
|
+{
|
|
|
+ if (emitting == emitting_)
|
|
|
+ return;
|
|
|
+
|
|
|
+ emitting_ = emitting;
|
|
|
+
|
|
|
+ // Reset already available points
|
|
|
+ if (emitting == true && points_.Size() > 0)
|
|
|
+ {
|
|
|
+ points_.Clear();
|
|
|
+ bufferSizeDirty_ = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ Drawable::OnMarkedDirty(node_);
|
|
|
+ MarkNetworkUpdate();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetTailColumn(unsigned tailColumn)
|
|
|
+{
|
|
|
+ if(tailColumn > MAX_TAIL_COLUMN)
|
|
|
+ {
|
|
|
+ URHO3D_LOGINFO("Max tail column is " + String(MAX_TAIL_COLUMN));
|
|
|
+ tailColumn_ = MAX_TAIL_COLUMN;
|
|
|
+ }
|
|
|
+ else if (tailColumn < 1)
|
|
|
+ {
|
|
|
+ tailColumn_ = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ tailColumn_ = tailColumn;
|
|
|
+
|
|
|
+ Drawable::OnMarkedDirty(node_);
|
|
|
+ bufferSizeDirty_ = true;
|
|
|
+ MarkNetworkUpdate();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::UpdateBatches(const FrameInfo& frame)
|
|
|
+{
|
|
|
+ // Update information for renderer about this drawable
|
|
|
+ distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
|
|
|
+ batches_[0].distance_ = distance_;
|
|
|
+
|
|
|
+ // Calculate scaled distance for animation LOD
|
|
|
+ float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
|
|
|
+ // If there are no trail, the size becomes zero, and LOD'ed updates no longer happen. Disable LOD in that case
|
|
|
+ if (scale > M_EPSILON)
|
|
|
+ lodDistance_ = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
|
|
|
+ else
|
|
|
+ lodDistance_ = 0.0f;
|
|
|
+
|
|
|
+ Vector3 worldPos = node_->GetWorldPosition();
|
|
|
+ Vector3 offset = (worldPos - frame.camera_->GetNode()->GetWorldPosition());
|
|
|
+ if (sorted_ && offset != previousOffset_)
|
|
|
+ {
|
|
|
+ bufferDirty_ = true;
|
|
|
+ previousOffset_ = offset;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::UpdateGeometry(const FrameInfo& frame)
|
|
|
+{
|
|
|
+ if (bufferSizeDirty_ || indexBuffer_->IsDataLost())
|
|
|
+ UpdateBufferSize();
|
|
|
+
|
|
|
+ if (bufferDirty_ || vertexBuffer_->IsDataLost())
|
|
|
+ UpdateVertexBuffer(frame);
|
|
|
+}
|
|
|
+
|
|
|
+UpdateGeometryType RibbonTrail::GetUpdateGeometryType()
|
|
|
+{
|
|
|
+ if (bufferDirty_ || bufferSizeDirty_ || vertexBuffer_->IsDataLost() || indexBuffer_->IsDataLost())
|
|
|
+ return UPDATE_MAIN_THREAD;
|
|
|
+ else
|
|
|
+ return UPDATE_NONE;
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetMaterial(Material* material)
|
|
|
+{
|
|
|
+ batches_[0].material_ = material;
|
|
|
+ MarkNetworkUpdate();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::OnSceneSet(Scene* scene)
|
|
|
+{
|
|
|
+ Drawable::OnSceneSet(scene);
|
|
|
+
|
|
|
+ if (scene && IsEnabledEffective())
|
|
|
+ SubscribeToEvent(scene, E_SCENEPOSTUPDATE, URHO3D_HANDLER(RibbonTrail, HandleScenePostUpdate));
|
|
|
+ else if (!scene)
|
|
|
+ UnsubscribeFromEvent(E_SCENEPOSTUPDATE);
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::OnWorldBoundingBoxUpdate()
|
|
|
+{
|
|
|
+ BoundingBox worldBox;
|
|
|
+
|
|
|
+ for (unsigned i = 0; i < points_.Size(); ++i)
|
|
|
+ {
|
|
|
+ Vector3 &p = points_[i].position_;
|
|
|
+ Vector3 scale = width_ * Vector3::ONE;
|
|
|
+ worldBox.Merge(BoundingBox(p - scale, p + scale));
|
|
|
+ }
|
|
|
+
|
|
|
+ worldBoundingBox_ = worldBox;
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::UpdateBufferSize()
|
|
|
+{
|
|
|
+ numPoints_ = points_.Size();
|
|
|
+
|
|
|
+ unsigned indexPerSegment = 6 + (tailColumn_ - 1) * 6;
|
|
|
+ unsigned vertexPerSegment = 4 + (tailColumn_ - 1) * 2;
|
|
|
+
|
|
|
+ if(trailType_ == TT_FACE_CAMERA)
|
|
|
+ {
|
|
|
+ batches_[0].geometryType_ = GEOM_TRAIL_FACE_CAMERA;
|
|
|
+ vertexBuffer_->SetSize((numPoints_ * vertexPerSegment),
|
|
|
+ MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1 | MASK_TANGENT, true);
|
|
|
+ }
|
|
|
+ else if(trailType_ == TT_BONE)
|
|
|
+ {
|
|
|
+ batches_[0].geometryType_ = GEOM_TRAIL_BONE;
|
|
|
+ vertexBuffer_->SetSize((numPoints_ * vertexPerSegment),
|
|
|
+ MASK_POSITION | MASK_NORMAL | MASK_COLOR | MASK_TEXCOORD1 | MASK_TANGENT, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (indexBuffer_->GetIndexCount() != ((numPoints_ - 1) * indexPerSegment))
|
|
|
+ indexBuffer_->SetSize(((numPoints_ - 1) * indexPerSegment), false);
|
|
|
+
|
|
|
+ bufferSizeDirty_ = false;
|
|
|
+ bufferDirty_ = true;
|
|
|
+ forceUpdate_ = true;
|
|
|
+
|
|
|
+ if (numPoints_ == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Indices do not change for a given tail generator capacity
|
|
|
+ unsigned short* dest = (unsigned short*)indexBuffer_->Lock(0, ((numPoints_ - 1) * indexPerSegment), true);
|
|
|
+ if (!dest)
|
|
|
+ return;
|
|
|
+
|
|
|
+ unsigned vertexIndex = 0;
|
|
|
+ unsigned stripsLen = numPoints_ - 1;
|
|
|
+
|
|
|
+ while (stripsLen--)
|
|
|
+ {
|
|
|
+ dest[0] = (unsigned short)vertexIndex;
|
|
|
+ dest[1] = (unsigned short)(vertexIndex + 2);
|
|
|
+ dest[2] = (unsigned short)(vertexIndex + 1);
|
|
|
+
|
|
|
+ dest[3] = (unsigned short)(vertexIndex + 1);
|
|
|
+ dest[4] = (unsigned short)(vertexIndex + 2);
|
|
|
+ dest[5] = (unsigned short)(vertexIndex + 3);
|
|
|
+
|
|
|
+ dest += 6;
|
|
|
+ //vertexIndex += 4;
|
|
|
+ vertexIndex += 2;
|
|
|
+
|
|
|
+ for (unsigned i = 0; i < (tailColumn_ - 1); ++i)
|
|
|
+ {
|
|
|
+ dest[0] = (unsigned short)vertexIndex;
|
|
|
+ dest[1] = (unsigned short)(vertexIndex + 2);
|
|
|
+ dest[2] = (unsigned short)(vertexIndex + 1);
|
|
|
+
|
|
|
+ dest[3] = (unsigned short)(vertexIndex + 1);
|
|
|
+ dest[4] = (unsigned short)(vertexIndex + 2);
|
|
|
+ dest[5] = (unsigned short)(vertexIndex + 3);
|
|
|
+
|
|
|
+ dest += 6;
|
|
|
+ vertexIndex += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ vertexIndex += 2;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ indexBuffer_->Unlock();
|
|
|
+ indexBuffer_->ClearDataLost();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::UpdateVertexBuffer(const FrameInfo& frame)
|
|
|
+{
|
|
|
+ // If using animation LOD, accumulate time and see if it is time to update
|
|
|
+ if (animationLodBias_ > 0.0f && lodDistance_ > 0.0f)
|
|
|
+ {
|
|
|
+ animationLodTimer_ += animationLodBias_ * frame.timeStep_ * ANIMATION_LOD_BASESCALE;
|
|
|
+ if (animationLodTimer_ >= lodDistance_)
|
|
|
+ animationLodTimer_ = fmodf(animationLodTimer_, lodDistance_);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // No LOD if immediate update forced
|
|
|
+ if (!forceUpdate_)
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // if tail path is short and nothing to draw, exit
|
|
|
+ if (numPoints_ < 2)
|
|
|
+ {
|
|
|
+ batches_[0].geometry_->SetDrawRange(TRIANGLE_LIST, 0, 0, false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ unsigned indexPerSegment = 6 + (tailColumn_ - 1) * 6;
|
|
|
+ unsigned vertexPerSegment = 4 + (tailColumn_ - 1) * 2;
|
|
|
+
|
|
|
+ // Fill sorted points vector
|
|
|
+ sortedPoints_.Resize(numPoints_);
|
|
|
+ for (unsigned i = 0; i < numPoints_; ++i)
|
|
|
+ {
|
|
|
+ Point& point = points_[i];
|
|
|
+ sortedPoints_[i] = &point;
|
|
|
+ if (sorted_)
|
|
|
+ point.sortDistance_ = frame.camera_->GetDistanceSquared(point.position_);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sort points
|
|
|
+ if (sorted_)
|
|
|
+ Sort(sortedPoints_.Begin(), sortedPoints_.End(), CompareTails);
|
|
|
+
|
|
|
+ // Update individual trail elapsed length
|
|
|
+ float trailLength = 0.0f;
|
|
|
+ for(unsigned i = 0; i < numPoints_; ++i)
|
|
|
+ {
|
|
|
+ float length = i == 0 ? 0.0f : (points_[i].position_ - points_[i-1].position_).Length();
|
|
|
+ trailLength += length;
|
|
|
+ points_[i].elapsedLength_ = trailLength;
|
|
|
+ if(i < numPoints_ - 1)
|
|
|
+ points_[i].next_ = &points_[i+1];
|
|
|
+ }
|
|
|
+
|
|
|
+ batches_[0].geometry_->SetDrawRange(TRIANGLE_LIST, 0, (numPoints_ - 1) * indexPerSegment, false);
|
|
|
+ bufferDirty_ = false;
|
|
|
+ forceUpdate_ = false;
|
|
|
+
|
|
|
+ float* dest = (float*)vertexBuffer_->Lock(0, (numPoints_ - 1) * vertexPerSegment, true);
|
|
|
+ if (!dest)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Generate trail mesh
|
|
|
+ if (trailType_ == TT_FACE_CAMERA)
|
|
|
+ {
|
|
|
+ for (unsigned i = 0; i < numPoints_; ++i)
|
|
|
+ {
|
|
|
+ Point& point = *sortedPoints_[i];
|
|
|
+
|
|
|
+ if (sortedPoints_[i] == &points_.Back()) continue;
|
|
|
+
|
|
|
+ // This point
|
|
|
+ float factor = SmoothStep(0.0f, trailLength, point.elapsedLength_);
|
|
|
+ unsigned c = endColor_.Lerp(startColor_, factor).ToUInt();
|
|
|
+ float width = Lerp(width_ * endScale_, width_ * startScale_, factor);
|
|
|
+
|
|
|
+ // Next point
|
|
|
+ float nextFactor = SmoothStep(0.0f, trailLength, point.next_->elapsedLength_);
|
|
|
+ unsigned nextC = endColor_.Lerp(startColor_, nextFactor).ToUInt();
|
|
|
+ float nextWidth = Lerp(width_ * endScale_, width_ * startScale_, nextFactor);
|
|
|
+
|
|
|
+ // First row
|
|
|
+ dest[0] = point.position_.x_;
|
|
|
+ dest[1] = point.position_.y_;
|
|
|
+ dest[2] = point.position_.z_;
|
|
|
+ ((unsigned&)dest[3]) = c;
|
|
|
+ dest[4] = factor;
|
|
|
+ dest[5] = 0.0f;
|
|
|
+ dest[6] = point.forward_.x_;
|
|
|
+ dest[7] = point.forward_.y_;
|
|
|
+ dest[8] = point.forward_.z_;
|
|
|
+ dest[9] = width;
|
|
|
+
|
|
|
+ dest[10] = point.next_->position_.x_;
|
|
|
+ dest[11] = point.next_->position_.y_;
|
|
|
+ dest[12] = point.next_->position_.z_;
|
|
|
+ ((unsigned&)dest[13]) = nextC;
|
|
|
+ dest[14] = nextFactor;
|
|
|
+ dest[15] = 0.0f;
|
|
|
+ dest[16] = point.next_->forward_.x_;
|
|
|
+ dest[17] = point.next_->forward_.y_;
|
|
|
+ dest[18] = point.next_->forward_.z_;
|
|
|
+ dest[19] = nextWidth;
|
|
|
+
|
|
|
+ dest += 20;
|
|
|
+
|
|
|
+ // Middle rows
|
|
|
+ for (unsigned j = 0; j < (tailColumn_ - 1); ++j)
|
|
|
+ {
|
|
|
+ float elapsed = 1.0f / tailColumn_ * (j + 1);
|
|
|
+ float scale = width_ - elapsed * 2.0f * width_;
|
|
|
+ float midWidth = width - elapsed * 2.0f * width;
|
|
|
+ float nextMidWidth = nextWidth - elapsed * 2.0f * nextWidth;
|
|
|
+
|
|
|
+ dest[0] = point.position_.x_;
|
|
|
+ dest[1] = point.position_.y_;
|
|
|
+ dest[2] = point.position_.z_;
|
|
|
+ ((unsigned&)dest[3]) = c;
|
|
|
+ dest[4] = factor;
|
|
|
+ dest[5] = elapsed;
|
|
|
+ dest[6] = point.forward_.x_;
|
|
|
+ dest[7] = point.forward_.y_;
|
|
|
+ dest[8] = point.forward_.z_;
|
|
|
+ dest[9] = midWidth;
|
|
|
+
|
|
|
+ dest[10] = point.next_->position_.x_;
|
|
|
+ dest[11] = point.next_->position_.y_;
|
|
|
+ dest[12] = point.next_->position_.z_;
|
|
|
+ ((unsigned&)dest[13]) = nextC;
|
|
|
+ dest[14] = nextFactor;
|
|
|
+ dest[15] = elapsed;
|
|
|
+ dest[16] = point.next_->forward_.x_;
|
|
|
+ dest[17] = point.next_->forward_.y_;
|
|
|
+ dest[18] = point.next_->forward_.z_;
|
|
|
+ dest[19] = nextMidWidth;
|
|
|
+
|
|
|
+ dest += 20;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Last row
|
|
|
+ dest[0] = point.position_.x_;
|
|
|
+ dest[1] = point.position_.y_;
|
|
|
+ dest[2] = point.position_.z_;
|
|
|
+ ((unsigned&)dest[3]) = c;
|
|
|
+ dest[4] = factor;
|
|
|
+ dest[5] = 1.0f;
|
|
|
+ dest[6] = point.forward_.x_;
|
|
|
+ dest[7] = point.forward_.y_;
|
|
|
+ dest[8] = point.forward_.z_;
|
|
|
+ dest[9] = -width;
|
|
|
+
|
|
|
+ dest[10] = point.next_->position_.x_;
|
|
|
+ dest[11] = point.next_->position_.y_;
|
|
|
+ dest[12] = point.next_->position_.z_;
|
|
|
+ ((unsigned&)dest[13]) = nextC;
|
|
|
+ dest[14] = nextFactor;
|
|
|
+ dest[15] = 1.0f;
|
|
|
+ dest[16] = point.next_->forward_.x_;
|
|
|
+ dest[17] = point.next_->forward_.y_;
|
|
|
+ dest[18] = point.next_->forward_.z_;
|
|
|
+ dest[19] = -nextWidth;
|
|
|
+
|
|
|
+ dest += 20;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if(trailType_ == TT_BONE)
|
|
|
+ {
|
|
|
+ for (unsigned i = 0; i < numPoints_; ++i)
|
|
|
+ {
|
|
|
+ Point& point = *sortedPoints_[i];
|
|
|
+
|
|
|
+ if (sortedPoints_[i] == &points_.Back()) continue;
|
|
|
+
|
|
|
+ // This point
|
|
|
+ float factor = SmoothStep(0.0f, trailLength, point.elapsedLength_);
|
|
|
+ unsigned c = endColor_.Lerp(startColor_, factor).ToUInt();
|
|
|
+ //float scale = factor
|
|
|
+
|
|
|
+ float rightScale = Lerp(endScale_, startScale_, factor);
|
|
|
+ float shift = (rightScale - 1.0f) / 2.0f;
|
|
|
+ float leftScale = 0.0f - shift;
|
|
|
+
|
|
|
+ // Next point
|
|
|
+ float nextFactor = SmoothStep(0.0f, trailLength, point.next_->elapsedLength_);
|
|
|
+ unsigned nextC = endColor_.Lerp(startColor_, nextFactor).ToUInt();
|
|
|
+
|
|
|
+ float nextRightScale = Lerp(endScale_, startScale_, nextFactor);
|
|
|
+ float nextShift = (nextRightScale - 1.0f) / 2.0f;
|
|
|
+ float nextLeftScale = 0.0f - nextShift;
|
|
|
+
|
|
|
+ // First row
|
|
|
+ dest[0] = point.position_.x_;
|
|
|
+ dest[1] = point.position_.y_;
|
|
|
+ dest[2] = point.position_.z_;
|
|
|
+ dest[3] = point.forward_.x_;
|
|
|
+ dest[4] = point.forward_.y_;
|
|
|
+ dest[5] = point.forward_.z_;
|
|
|
+ ((unsigned&)dest[6]) = c;
|
|
|
+ dest[7] = factor;
|
|
|
+ dest[8] = 0.0f;
|
|
|
+ dest[9] = point.parentPos_.x_;
|
|
|
+ dest[10] = point.parentPos_.y_;
|
|
|
+ dest[11] = point.parentPos_.z_;
|
|
|
+ dest[12] = leftScale;
|
|
|
+
|
|
|
+ dest[13] = point.next_->position_.x_;
|
|
|
+ dest[14] = point.next_->position_.y_;
|
|
|
+ dest[15] = point.next_->position_.z_;
|
|
|
+ dest[16] = point.next_->forward_.x_;
|
|
|
+ dest[17] = point.next_->forward_.y_;
|
|
|
+ dest[18] = point.next_->forward_.z_;
|
|
|
+ ((unsigned&)dest[19]) = nextC;
|
|
|
+ dest[20] = nextFactor;
|
|
|
+ dest[21] = 0.0f;
|
|
|
+ dest[22] = point.next_->parentPos_.x_;
|
|
|
+ dest[23] = point.next_->parentPos_.y_;
|
|
|
+ dest[24] = point.next_->parentPos_.z_;
|
|
|
+ dest[25] = nextLeftScale;
|
|
|
+
|
|
|
+ dest += 26;
|
|
|
+
|
|
|
+ // Middle row
|
|
|
+ for (unsigned j = 0; j < (tailColumn_ - 1); ++j)
|
|
|
+ {
|
|
|
+ float elapsed = 1.0f / tailColumn_ * (j + 1);
|
|
|
+
|
|
|
+ dest[0] = point.position_.x_;
|
|
|
+ dest[1] = point.position_.y_;
|
|
|
+ dest[2] = point.position_.z_;
|
|
|
+ dest[3] = point.forward_.x_;
|
|
|
+ dest[4] = point.forward_.y_;
|
|
|
+ dest[5] = point.forward_.z_;
|
|
|
+ ((unsigned&)dest[6]) = c;
|
|
|
+ dest[7] = factor;
|
|
|
+ dest[8] = elapsed;
|
|
|
+ dest[9] = point.parentPos_.x_;
|
|
|
+ dest[10] = point.parentPos_.y_;
|
|
|
+ dest[11] = point.parentPos_.z_;
|
|
|
+ dest[12] = Lerp(leftScale, rightScale, elapsed);
|
|
|
+
|
|
|
+ dest[13] = point.next_->position_.x_;
|
|
|
+ dest[14] = point.next_->position_.y_;
|
|
|
+ dest[15] = point.next_->position_.z_;
|
|
|
+ dest[16] = point.next_->forward_.x_;
|
|
|
+ dest[17] = point.next_->forward_.y_;
|
|
|
+ dest[18] = point.next_->forward_.z_;
|
|
|
+ ((unsigned&)dest[19]) = nextC;
|
|
|
+ dest[20] = nextFactor;
|
|
|
+ dest[21] = elapsed;
|
|
|
+ dest[22] = point.next_->parentPos_.x_;
|
|
|
+ dest[23] = point.next_->parentPos_.y_;
|
|
|
+ dest[24] = point.next_->parentPos_.z_;
|
|
|
+ dest[25] = Lerp(nextLeftScale, nextRightScale, elapsed);
|
|
|
+
|
|
|
+ dest += 26;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Last row
|
|
|
+ dest[0] = point.position_.x_;
|
|
|
+ dest[1] = point.position_.y_;
|
|
|
+ dest[2] = point.position_.z_;
|
|
|
+ dest[3] = point.forward_.x_;
|
|
|
+ dest[4] = point.forward_.y_;
|
|
|
+ dest[5] = point.forward_.z_;
|
|
|
+ ((unsigned&)dest[6]) = c;
|
|
|
+ dest[7] = factor;
|
|
|
+ dest[8] = 1.0f;
|
|
|
+ dest[9] = point.parentPos_.x_;
|
|
|
+ dest[10] = point.parentPos_.y_;
|
|
|
+ dest[11] = point.parentPos_.z_;
|
|
|
+ dest[12] = rightScale;
|
|
|
+
|
|
|
+ dest[13] = point.next_->position_.x_;
|
|
|
+ dest[14] = point.next_->position_.y_;
|
|
|
+ dest[15] = point.next_->position_.z_;
|
|
|
+ dest[16] = point.next_->forward_.x_;
|
|
|
+ dest[17] = point.next_->forward_.y_;
|
|
|
+ dest[18] = point.next_->forward_.z_;
|
|
|
+ ((unsigned&)dest[19]) = nextC;
|
|
|
+ dest[20] = nextFactor;
|
|
|
+ dest[21] = 1.0f;
|
|
|
+ dest[22] = point.next_->parentPos_.x_;
|
|
|
+ dest[23] = point.next_->parentPos_.y_;
|
|
|
+ dest[24] = point.next_->parentPos_.z_;
|
|
|
+ dest[25] = nextRightScale;
|
|
|
+
|
|
|
+ dest += 26;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ vertexBuffer_->Unlock();
|
|
|
+ vertexBuffer_->ClearDataLost();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetLifetime(float time)
|
|
|
+{
|
|
|
+ lifetime_ = time;
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetVertexDistance(float length)
|
|
|
+{
|
|
|
+ vertexDistance_ = length;
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetEndColor(const Color& c)
|
|
|
+{
|
|
|
+ endColor_ = Color(c.r_, c.g_, c.b_, c.a_);
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetStartColor(const Color& c)
|
|
|
+{
|
|
|
+ startColor_ = Color(c.r_, c.g_, c.b_, c.a_);
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetSorted(bool enable)
|
|
|
+{
|
|
|
+ sorted_ = enable;
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetTrailType(TrailType type)
|
|
|
+{
|
|
|
+ if (trailType_ == type)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (type == TT_BONE && (node_->GetParent() == 0 || node_->GetParent() == node_->GetScene()))
|
|
|
+ {
|
|
|
+ URHO3D_LOGWARNING("No parent node found, revert back to Face Camera type");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ trailType_ = type;
|
|
|
+ //Commit();
|
|
|
+ Drawable::OnMarkedDirty(node_);
|
|
|
+ bufferSizeDirty_ = true;
|
|
|
+ MarkNetworkUpdate();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetMaterialAttr(const ResourceRef& value)
|
|
|
+{
|
|
|
+ ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
|
+ SetMaterial(cache->GetResource<Material>(value.name_));
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetWidth(float width)
|
|
|
+{
|
|
|
+ width_ = width;
|
|
|
+ Commit();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::SetAnimationLodBias(float bias)
|
|
|
+{
|
|
|
+ animationLodBias_ = Max(bias, 0.0f);
|
|
|
+ MarkNetworkUpdate();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::Commit()
|
|
|
+{
|
|
|
+ MarkPositionsDirty();
|
|
|
+ MarkNetworkUpdate();
|
|
|
+}
|
|
|
+
|
|
|
+void RibbonTrail::MarkPositionsDirty()
|
|
|
+{
|
|
|
+ Drawable::OnMarkedDirty(node_);
|
|
|
+ bufferDirty_ = true;
|
|
|
+}
|
|
|
+
|
|
|
+Material* RibbonTrail::GetMaterial() const
|
|
|
+{
|
|
|
+ return batches_[0].material_;
|
|
|
+}
|
|
|
+
|
|
|
+ResourceRef RibbonTrail::GetMaterialAttr() const
|
|
|
+{
|
|
|
+ return GetResourceRef(batches_[0].material_, Material::GetTypeStatic());
|
|
|
+}
|
|
|
+
|
|
|
+}
|