DYTSrouce/src/entities/TrajectoryTraceComponent.cpp

521 lines
15 KiB
C++
Raw Normal View History

2026-03-20 00:23:09 +00:00
#include "entities/TrajectoryTraceComponent.h"
#include <algorithm>
#include <osg/BlendFunc>
#include <osg/Depth>
#include <osg/Geode>
#include <osg/StateSet>
#include <osgEarth/GeoData>
#include "common/SpdLogger.h"
#include "entities/Entity.h"
2026-03-21 01:12:30 +00:00
#include "entities/PathComponent.h"
2026-03-20 00:23:09 +00:00
#include "scene/SceneContent.h"
#include "utils/StringUtils.h"
#include "workspace/WorkSpace.h"
TrajectoryTraceComponent::TrajectoryTraceComponent(SceneComponent* parent)
: SceneComponent(parent) {
}
TrajectoryTraceComponent::~TrajectoryTraceComponent() {
}
std::string TrajectoryTraceComponent::GetTypeName() {
return "TrajectoryTraceComponent";
}
bool TrajectoryTraceComponent::SetAttribute(const char* name, const char* value) {
if (0 == strcmp(name, "color")) {
SetColor(StringUtils::StringToVec4(value));
return true;
} else if (0 == strcmp(name, "lineWidth")) {
SetLineWidth(static_cast<float>(atof(value)));
return true;
} else if (0 == strcmp(name, "sampleInterval")) {
SetSampleInterval(atof(value));
return true;
} else if (0 == strcmp(name, "minMoveDistance")) {
SetMinMoveDistance(atof(value));
return true;
} else if (0 == strcmp(name, "maxPoints")) {
SetMaxPoints(atoi(value));
return true;
} else if (0 == strcmp(name, "tailDuration")) {
SetTailDuration(atof(value));
return true;
} else if (0 == strcmp(name, "visible")) {
SetTraceVisible(0 == strcmp(value, "true"));
return true;
2026-03-21 01:12:30 +00:00
} else if (0 == strcmp(name, "dashed")) {
SetDashed(0 == strcmp(value, "true"));
return true;
} else if (0 == strcmp(name, "dashLength")) {
SetDashLength(atof(value));
return true;
} else if (0 == strcmp(name, "gapLength")) {
SetGapLength(atof(value));
return true;
} else if (0 == strcmp(name, "dashScrollSpeed")) {
SetDashScrollSpeed(atof(value));
return true;
2026-03-20 00:23:09 +00:00
}
return SceneComponent::SetAttribute(name, value);
}
bool TrajectoryTraceComponent::SaveAttribute(tinyxml2::XMLElement* element) {
element->SetAttribute("color", StringUtils::Vec4ToString(color_).c_str());
element->SetAttribute("lineWidth", std::to_string(lineWidth_).c_str());
element->SetAttribute("sampleInterval", std::to_string(sampleInterval_).c_str());
element->SetAttribute("minMoveDistance", std::to_string(minMoveDistance_).c_str());
element->SetAttribute("maxPoints", maxPoints_);
element->SetAttribute("tailDuration", std::to_string(tailDuration_).c_str());
element->SetAttribute("visible", traceVisible_ ? "true" : "false");
2026-03-21 01:12:30 +00:00
element->SetAttribute("dashed", dashed_ ? "true" : "false");
element->SetAttribute("dashLength", std::to_string(dashLength_).c_str());
element->SetAttribute("gapLength", std::to_string(gapLength_).c_str());
element->SetAttribute("dashScrollSpeed", std::to_string(dashScrollSpeed_).c_str());
2026-03-20 00:23:09 +00:00
return SceneComponent::SaveAttribute(element);
}
void TrajectoryTraceComponent::Begin() {
SceneComponent::Begin();
if (mt_.valid()) {
RemoveRender();
}
sampleAccum_ = 0.0;
elapsedTime_ = 0.0;
hasLastSample_ = false;
attachedToScene_ = false;
ClearTrace();
}
void TrajectoryTraceComponent::Update(double dt) {
SceneComponent::Update(dt);
Entity* entity = GetEntity();
if (nullptr == entity || nullptr == entity->GetTransform()) {
return;
}
2026-03-21 01:12:30 +00:00
elapsedTime_ += dt;
2026-03-20 00:23:09 +00:00
sampleAccum_ += dt;
TrimExpiredPoints();
const osg::Vec3d currentPos = entity->GetTransform()->GetLocation();
if (!hasLastSample_) {
2026-03-21 01:12:30 +00:00
osg::Vec3d initialPos = currentPos;
if (PathComponent* pathComponent = entity->GetComponent<PathComponent>()) {
pathComponent->GetFirstPathLocation(&initialPos);
}
EnsureAttachedToScene();
if (!AppendPoint(initialPos)) {
return;
}
if (!AppendPoint(currentPos)) {
return;
}
2026-03-20 00:23:09 +00:00
lastSamplePos_ = currentPos;
hasLastSample_ = true;
2026-03-21 01:12:30 +00:00
sampleAccum_ = 0.0;
2026-03-20 00:23:09 +00:00
return;
}
EnsureAttachedToScene();
if (vertices_->empty()) {
if (!AppendPoint(lastSamplePos_)) {
return;
}
2026-03-21 01:12:30 +00:00
if (!AppendPoint(currentPos)) {
return;
}
} else if (!UpdateTailPoint(currentPos)) {
return;
}
if (sampleAccum_ < sampleInterval_) {
return;
}
const osg::Vec3d delta = currentPos - lastSamplePos_;
if (delta.length2() < minMoveDistance_ * minMoveDistance_) {
return;
2026-03-20 00:23:09 +00:00
}
2026-03-21 01:12:30 +00:00
sampleAccum_ = 0.0;
2026-03-20 00:23:09 +00:00
if (!AppendPoint(currentPos)) {
return;
}
lastSamplePos_ = currentPos;
}
void TrajectoryTraceComponent::AddToRender() {
InitializeGeometry();
if (!attachedToScene_) {
return;
}
if (mt_->getNumParents() > 0) {
return;
}
AttachTraceToScene();
}
void TrajectoryTraceComponent::SetColor(const osg::Vec4& color) {
color_ = color;
UpdateStyle();
}
const osg::Vec4& TrajectoryTraceComponent::GetColor() const {
return color_;
}
void TrajectoryTraceComponent::SetLineWidth(float width) {
lineWidth_ = std::max(1.0f, width);
UpdateStyle();
}
float TrajectoryTraceComponent::GetLineWidth() const {
return lineWidth_;
}
void TrajectoryTraceComponent::SetSampleInterval(double interval) {
const double newInterval = std::max(0.01, interval);
if (sampleInterval_ == newInterval) {
return;
}
sampleInterval_ = newInterval;
// Make editor changes visible immediately instead of waiting for the old accumulated cadence.
sampleAccum_ = 0.0;
}
double TrajectoryTraceComponent::GetSampleInterval() const {
return sampleInterval_;
}
void TrajectoryTraceComponent::SetMinMoveDistance(double distance) {
minMoveDistance_ = std::max(0.0, distance);
}
double TrajectoryTraceComponent::GetMinMoveDistance() const {
return minMoveDistance_;
}
void TrajectoryTraceComponent::SetMaxPoints(int maxPoints) {
maxPoints_ = std::max(2, maxPoints);
if (vertices_.valid() && static_cast<int>(vertices_->size()) > maxPoints_) {
const int overflow = static_cast<int>(vertices_->size()) - maxPoints_;
vertices_->erase(vertices_->begin(), vertices_->begin() + overflow);
drawArrays_->setCount(static_cast<GLsizei>(vertices_->size()));
vertices_->dirty();
geometry_->dirtyBound();
}
}
int TrajectoryTraceComponent::GetMaxPoints() const {
return maxPoints_;
}
void TrajectoryTraceComponent::SetTailDuration(double duration) {
tailDuration_ = std::max(0.0, duration);
TrimExpiredPoints();
}
double TrajectoryTraceComponent::GetTailDuration() const {
return tailDuration_;
}
void TrajectoryTraceComponent::SetTraceVisible(bool visible) {
traceVisible_ = visible;
if (geode_.valid()) {
geode_->setNodeMask(traceVisible_ ? 0xffffffff : 0x0);
}
}
bool TrajectoryTraceComponent::IsTraceVisible() const {
return traceVisible_;
}
2026-03-21 01:12:30 +00:00
void TrajectoryTraceComponent::SetDashed(bool dashed) {
dashed_ = dashed;
UpdateStyle();
}
bool TrajectoryTraceComponent::IsDashed() const {
return dashed_;
}
void TrajectoryTraceComponent::SetDashLength(double length) {
dashLength_ = std::max(1.0, length);
UpdateStyle();
}
double TrajectoryTraceComponent::GetDashLength() const {
return dashLength_;
}
void TrajectoryTraceComponent::SetGapLength(double length) {
gapLength_ = std::max(1.0, length);
UpdateStyle();
}
double TrajectoryTraceComponent::GetGapLength() const {
return gapLength_;
}
void TrajectoryTraceComponent::SetDashScrollSpeed(double speed) {
dashScrollSpeed_ = speed;
}
double TrajectoryTraceComponent::GetDashScrollSpeed() const {
return dashScrollSpeed_;
}
2026-03-20 00:23:09 +00:00
void TrajectoryTraceComponent::ClearTrace() {
InitializeGeometry();
vertices_->clear();
sampleTimes_.clear();
drawArrays_->setCount(0);
vertices_->dirty();
geometry_->dirtyBound();
if (geode_.valid()) {
geode_->dirtyBound();
}
if (mt_.valid()) {
mt_->dirtyBound();
}
}
void TrajectoryTraceComponent::InitializeGeometry() {
if (mt_.valid()) {
return;
}
mt_ = new osg::MatrixTransform;
geode_ = new osg::Geode;
geometry_ = new osg::Geometry;
vertices_ = new osg::Vec3Array;
colors_ = new osg::Vec4Array;
drawArrays_ = new osg::DrawArrays(GL_LINE_STRIP, 0, 0);
lineWidthState_ = new osg::LineWidth(lineWidth_);
2026-03-21 01:12:30 +00:00
lineStippleState_ = new osg::LineStipple(1, 0xFFFF);
2026-03-20 00:23:09 +00:00
geometry_->setDataVariance(osg::Object::DYNAMIC);
geometry_->setUseDisplayList(false);
geometry_->setUseVertexBufferObjects(true);
geometry_->setVertexArray(vertices_.get());
geometry_->addPrimitiveSet(drawArrays_.get());
colors_->push_back(color_);
geometry_->setColorArray(colors_.get(), osg::Array::BIND_OVERALL);
osg::StateSet* stateSet = geode_->getOrCreateStateSet();
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setAttributeAndModes(lineWidthState_.get(), osg::StateAttribute::ON);
stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
stateSet->setAttributeAndModes(new osg::Depth(osg::Depth::LEQUAL, 0.0, 1.0, false));
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
2026-03-21 01:12:30 +00:00
stateSet->setAttributeAndModes(lineStippleState_.get(), osg::StateAttribute::OFF);
2026-03-20 00:23:09 +00:00
geode_->addDrawable(geometry_.get());
mt_->addChild(geode_.get());
geode_->setNodeMask(traceVisible_ ? 0xffffffff : 0x0);
2026-03-21 01:12:30 +00:00
UpdateStyle();
2026-03-20 00:23:09 +00:00
}
void TrajectoryTraceComponent::EnsureAttachedToScene() {
InitializeGeometry();
if (!attachedToScene_) {
attachedToScene_ = true;
}
}
bool TrajectoryTraceComponent::AppendPoint(const osg::Vec3d& geoPoint) {
InitializeGeometry();
osg::Vec3d worldPoint;
if (!ConvertGeoPointToWorld(geoPoint, worldPoint)) {
return false;
}
vertices_->push_back(worldPoint);
sampleTimes_.push_back(elapsedTime_);
if (static_cast<int>(vertices_->size()) > maxPoints_) {
const int overflow = static_cast<int>(vertices_->size()) - maxPoints_;
vertices_->erase(vertices_->begin(), vertices_->begin() + overflow);
for (int i = 0; i < overflow && !sampleTimes_.empty(); ++i) {
sampleTimes_.pop_front();
}
}
drawArrays_->setCount(static_cast<GLsizei>(vertices_->size()));
vertices_->dirty();
geometry_->dirtyBound();
if (geode_.valid()) {
geode_->dirtyBound();
}
if (mt_.valid()) {
mt_->dirtyBound();
}
if (attachedToScene_ && mt_->getNumParents() == 0) {
AttachTraceToScene();
}
return true;
}
2026-03-21 01:12:30 +00:00
bool TrajectoryTraceComponent::UpdateTailPoint(const osg::Vec3d& geoPoint) {
InitializeGeometry();
if (!vertices_.valid() || vertices_->empty()) {
return AppendPoint(geoPoint);
}
osg::Vec3d worldPoint;
if (!ConvertGeoPointToWorld(geoPoint, worldPoint)) {
return false;
}
vertices_->back() = worldPoint;
if (!sampleTimes_.empty()) {
sampleTimes_.back() = elapsedTime_;
}
drawArrays_->setCount(static_cast<GLsizei>(vertices_->size()));
vertices_->dirty();
geometry_->dirtyBound();
if (geode_.valid()) {
geode_->dirtyBound();
}
if (mt_.valid()) {
mt_->dirtyBound();
}
if (attachedToScene_ && mt_->getNumParents() == 0) {
AttachTraceToScene();
}
return true;
}
2026-03-20 00:23:09 +00:00
bool TrajectoryTraceComponent::ConvertGeoPointToWorld(const osg::Vec3d& geoPoint, osg::Vec3d& worldPoint) const {
if (nullptr == g_srs_) {
LOG_WARN("TrajectoryTraceComponent::ConvertGeoPointToWorld - g_srs_ is nullptr");
return false;
}
osgEarth::GeoPoint point(g_srs_, geoPoint);
if (!point.toWorld(worldPoint)) {
LOG_WARN("TrajectoryTraceComponent::ConvertGeoPointToWorld - failed to convert geo point to world");
return false;
}
return true;
}
void TrajectoryTraceComponent::AttachTraceToScene() {
Entity* entity = GetEntity();
if (nullptr == entity) {
LOG_WARN("TrajectoryTraceComponent::AttachTraceToScene - entity is nullptr");
return;
}
WorkSpace* workspace = entity->GetWorkspace();
if (nullptr == workspace) {
LOG_WARN("TrajectoryTraceComponent::AttachTraceToScene - workspace is nullptr");
return;
}
OEScene* scene = workspace->GetActiveScene();
if (nullptr == scene) {
LOG_WARN("TrajectoryTraceComponent::AttachTraceToScene - scene is nullptr");
return;
}
scene->AddToScene(mt_.get());
}
void TrajectoryTraceComponent::TrimExpiredPoints() {
if (!vertices_.valid()) {
return;
}
if (tailDuration_ <= 0.0) {
if (!vertices_->empty()) {
ClearTrace();
}
return;
}
bool removed = false;
while (!sampleTimes_.empty() && (elapsedTime_ - sampleTimes_.front()) > tailDuration_) {
sampleTimes_.pop_front();
if (!vertices_->empty()) {
vertices_->erase(vertices_->begin());
removed = true;
}
}
if (removed) {
drawArrays_->setCount(static_cast<GLsizei>(vertices_->size()));
vertices_->dirty();
geometry_->dirtyBound();
if (geode_.valid()) {
geode_->dirtyBound();
}
if (mt_.valid()) {
mt_->dirtyBound();
}
}
}
void TrajectoryTraceComponent::UpdateStyle() {
if (colors_.valid()) {
colors_->clear();
colors_->push_back(color_);
colors_->dirty();
geometry_->dirtyBound();
}
if (lineWidthState_.valid()) {
lineWidthState_->setWidth(lineWidth_);
}
2026-03-21 01:12:30 +00:00
if (lineStippleState_.valid() && geode_.valid()) {
const double cycle = std::max(1.0, dashLength_ + gapLength_);
double factorValue = cycle / std::max(1.0f, lineWidth_);
if (factorValue < 1.0) {
factorValue = 1.0;
} else if (factorValue > 255.0) {
factorValue = 255.0;
}
int factor = static_cast<int>(factorValue);
int onBits = static_cast<int>(std::round(16.0 * dashLength_ / cycle));
if (onBits < 1) {
onBits = 1;
} else if (onBits > 15) {
onBits = 15;
}
unsigned short pattern = 0;
for (int i = 0; i < 16; ++i) {
if (i < onBits) {
pattern |= static_cast<unsigned short>(1u << i);
}
}
if (!dashed_) {
pattern = 0xFFFF;
geode_->getOrCreateStateSet()->setAttributeAndModes(lineStippleState_.get(), osg::StateAttribute::OFF);
} else {
geode_->getOrCreateStateSet()->setAttributeAndModes(lineStippleState_.get(), osg::StateAttribute::ON);
}
lineStippleState_->setFactor(factor);
lineStippleState_->setPattern(pattern);
}
2026-03-20 00:23:09 +00:00
}