Extra2D/src/scene/node.cpp

602 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <algorithm>
#include <animation/tween.h>
#include <cmath>
#include <graphics/render_command.h>
#include <scene/node.h>
#include <scene/scene.h>
#include <utils/logger.h>
namespace extra2d {
Node::Node() = default;
Node::~Node() { removeAllChildren(); }
void Node::addChild(Ptr<Node> child) {
if (!child || child.get() == this) {
return;
}
child->removeFromParent();
child->parent_ = weak_from_this();
children_.push_back(child);
childrenOrderDirty_ = true;
// 更新索引
if (!child->name().empty()) {
nameIndex_[child->name()] = child;
}
if (child->tag() != -1) {
tagIndex_[child->tag()] = child;
}
if (running_) {
child->onEnter();
if (scene_) {
child->onAttachToScene(scene_);
}
}
}
void Node::addChildren(std::vector<Ptr<Node>> &&children) {
// 预留空间,避免多次扩容
size_t newSize = children_.size() + children.size();
if (newSize > children_.capacity()) {
children_.reserve(newSize);
}
for (auto &child : children) {
if (!child || child.get() == this) {
continue;
}
child->removeFromParent();
child->parent_ = weak_from_this();
children_.push_back(child);
// 更新索引
if (!child->name().empty()) {
nameIndex_[child->name()] = child;
}
if (child->tag() != -1) {
tagIndex_[child->tag()] = child;
}
if (running_) {
child->onEnter();
if (scene_) {
child->onAttachToScene(scene_);
}
}
}
if (!children.empty()) {
childrenOrderDirty_ = true;
}
}
void Node::removeChild(Ptr<Node> child) {
if (!child)
return;
auto it = std::find(children_.begin(), children_.end(), child);
if (it != children_.end()) {
// 始终从空间索引中移除(无论 running_ 状态)
// 这确保节点被正确清理
(*it)->onDetachFromScene();
if (running_) {
(*it)->onExit();
}
// 从索引中移除
if (!(*it)->name().empty()) {
nameIndex_.erase((*it)->name());
}
if ((*it)->tag() != -1) {
tagIndex_.erase((*it)->tag());
}
(*it)->parent_.reset();
children_.erase(it);
}
}
void Node::removeChildByName(const std::string &name) {
auto child = childByName(name);
if (child) {
removeChild(child);
}
}
void Node::removeFromParent() {
auto p = parent_.lock();
if (p) {
// 安全获取 shared_ptr避免在对象未由 shared_ptr 管理时崩溃
Ptr<Node> self;
try {
self = shared_from_this();
} catch (const std::bad_weak_ptr &) {
// 对象不是由 shared_ptr 管理的,直接重置父节点引用
parent_.reset();
return;
}
p->removeChild(self);
}
}
void Node::removeAllChildren() {
for (auto &child : children_) {
if (running_) {
child->onDetachFromScene();
child->onExit();
}
child->parent_.reset();
}
children_.clear();
nameIndex_.clear();
tagIndex_.clear();
}
Ptr<Node> Node::childByName(const std::string &name) const {
// 使用哈希索引O(1) 查找
auto it = nameIndex_.find(name);
if (it != nameIndex_.end()) {
return it->second.lock();
}
return nullptr;
}
Ptr<Node> Node::childByTag(int tag) const {
// 使用哈希索引O(1) 查找
auto it = tagIndex_.find(tag);
if (it != tagIndex_.end()) {
return it->second.lock();
}
return nullptr;
}
void Node::setPosition(const Vec2 &pos) {
position_ = pos;
markTransformDirty();
updateSpatialIndex();
}
void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); }
void Node::setRotation(float degrees) {
rotation_ = degrees;
markTransformDirty();
updateSpatialIndex();
}
void Node::setScale(const Vec2 &scale) {
scale_ = scale;
markTransformDirty();
updateSpatialIndex();
}
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
void Node::setScale(float x, float y) { setScale(Vec2(x, y)); }
void Node::setAnchor(const Vec2 &anchor) {
anchor_ = anchor;
markTransformDirty();
}
void Node::setAnchor(float x, float y) { setAnchor(Vec2(x, y)); }
void Node::setSkew(const Vec2 &skew) {
skew_ = skew;
markTransformDirty();
}
void Node::setSkew(float x, float y) { setSkew(Vec2(x, y)); }
void Node::setOpacity(float opacity) {
opacity_ = std::clamp(opacity, 0.0f, 1.0f);
}
void Node::setVisible(bool visible) { visible_ = visible; }
void Node::setColor(const Color3B &color) { color_ = color; }
void Node::setFlipX(bool flipX) { flipX_ = flipX; }
void Node::setFlipY(bool flipY) { flipY_ = flipY; }
void Node::setZOrder(int zOrder) {
if (zOrder_ != zOrder) {
zOrder_ = zOrder;
childrenOrderDirty_ = true;
}
}
Vec2 Node::convertToWorldSpace(const Vec2 &localPos) const {
glm::vec4 worldPos =
getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
return Vec2(worldPos.x, worldPos.y);
}
Vec2 Node::convertToNodeSpace(const Vec2 &worldPos) const {
glm::mat4 invWorld = glm::inverse(getWorldTransform());
glm::vec4 localPos = invWorld * glm::vec4(worldPos.x, worldPos.y, 0.0f, 1.0f);
return Vec2(localPos.x, localPos.y);
}
glm::mat4 Node::getLocalTransform() const {
if (transformDirty_) {
localTransform_ = glm::mat4(1.0f);
// T - R - S order
localTransform_ = glm::translate(localTransform_,
glm::vec3(position_.x, position_.y, 0.0f));
if (rotation_ != 0.0f) {
localTransform_ = glm::rotate(localTransform_, rotation_ * DEG_TO_RAD,
glm::vec3(0.0f, 0.0f, 1.0f));
}
if (skew_.x != 0.0f || skew_.y != 0.0f) {
glm::mat4 skewMatrix(1.0f);
skewMatrix[1][0] = std::tan(skew_.x * DEG_TO_RAD);
skewMatrix[0][1] = std::tan(skew_.y * DEG_TO_RAD);
localTransform_ *= skewMatrix;
}
localTransform_ =
glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
// 注意:锚点偏移在渲染时处理,不在本地变换中处理
// 这样可以避免锚点偏移被父节点的缩放影响
transformDirty_ = false;
}
return localTransform_;
}
glm::mat4 Node::getWorldTransform() const {
if (worldTransformDirty_) {
// 使用线程局部存储的固定数组,避免每帧内存分配
// 限制最大深度为 256 层,足以覆盖绝大多数场景
thread_local std::array<const Node *, 256> nodeChainCache;
thread_local size_t chainCount = 0;
chainCount = 0;
const Node *current = this;
while (current && chainCount < nodeChainCache.size()) {
nodeChainCache[chainCount++] = current;
auto p = current->parent_.lock();
current = p.get();
}
// 从根节点开始计算
glm::mat4 transform = glm::mat4(1.0f);
for (size_t i = chainCount; i > 0; --i) {
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
}
worldTransform_ = transform;
worldTransformDirty_ = false;
}
return worldTransform_;
}
void Node::markTransformDirty() {
// 避免重复标记,提高性能
if (!transformDirty_ || !worldTransformDirty_) {
transformDirty_ = true;
worldTransformDirty_ = true;
// 递归标记所有子节点
for (auto &child : children_) {
child->markTransformDirty();
}
}
}
void Node::batchUpdateTransforms() {
// 如果本地变换脏了,先计算本地变换
if (transformDirty_) {
(void)getLocalTransform(); // 这会计算并缓存本地变换
}
// 如果世界变换脏了,需要重新计算
if (worldTransformDirty_) {
auto parent = parent_.lock();
if (parent) {
// 使用父节点的世界变换(确保父节点已经更新)
worldTransform_ = parent->getWorldTransform() * localTransform_;
} else {
// 根节点
worldTransform_ = localTransform_;
}
worldTransformDirty_ = false;
}
// 递归更新子节点
for (auto &child : children_) {
child->batchUpdateTransforms();
}
}
void Node::onEnter() {
running_ = true;
for (auto &child : children_) {
child->onEnter();
}
}
void Node::onExit() {
running_ = false;
for (auto &child : children_) {
child->onExit();
}
}
void Node::onUpdate(float dt) {
updateTweens(dt);
onUpdateNode(dt);
// Update children
for (auto &child : children_) {
child->onUpdate(dt);
}
}
void Node::onRender(Renderer &renderer) {
if (!visible_)
return;
onDraw(renderer);
for (auto &child : children_) {
child->onRender(renderer);
}
}
void Node::onAttachToScene(Scene *scene) {
scene_ = scene;
// 添加到场景的空间索引
if (spatialIndexed_ && scene_) {
lastSpatialBounds_ = Rect();
updateSpatialIndex();
}
for (auto &child : children_) {
child->onAttachToScene(scene);
}
}
void Node::onDetachFromScene() {
// 从场景的空间索引移除
// 注意:即使 lastSpatialBounds_ 为空也要尝试移除,
// 因为节点可能通过其他方式被插入到空间索引中
if (spatialIndexed_ && scene_) {
scene_->removeNodeFromSpatialIndex(this);
lastSpatialBounds_ = Rect();
}
scene_ = nullptr;
for (auto &child : children_) {
child->onDetachFromScene();
}
}
Rect Node::boundingBox() const {
// 默认返回一个以位置为中心的点矩形
return Rect(position_.x, position_.y, 0, 0);
}
void Node::updateSpatialIndex() {
if (!spatialIndexed_ || !scene_) {
return;
}
Rect newBounds = boundingBox();
if (newBounds != lastSpatialBounds_) {
scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds);
lastSpatialBounds_ = newBounds;
}
}
void Node::update(float dt) { onUpdate(dt); }
void Node::render(Renderer &renderer) {
if (childrenOrderDirty_) {
sortChildren();
}
onRender(renderer);
}
void Node::sortChildren() {
// 使用插入排序优化小范围更新场景
// 插入排序在大部分已有序的情况下性能接近O(n)
size_t n = children_.size();
if (n <= 1) {
childrenOrderDirty_ = false;
return;
}
// 小数组使用插入排序大数组使用std::sort
if (n < 32) {
// 插入排序
for (size_t i = 1; i < n; ++i) {
auto key = children_[i];
int keyZOrder = key->zOrder();
int j = static_cast<int>(i) - 1;
while (j >= 0 && children_[j]->zOrder() > keyZOrder) {
children_[j + 1] = children_[j];
--j;
}
children_[j + 1] = key;
}
} else {
// 大数组使用标准排序
std::sort(children_.begin(), children_.end(),
[](const Ptr<Node> &a, const Ptr<Node> &b) {
return a->zOrder() < b->zOrder();
});
}
childrenOrderDirty_ = false;
}
void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
int parentZOrder) {
if (!visible_)
return;
// 计算累积 Z 序
int accumulatedZOrder = parentZOrder + zOrder_;
// 生成当前节点的渲染命令
generateRenderCommand(commands, accumulatedZOrder);
// 递归收集子节点的渲染命令
// 注意:这里假设子节点已经按 Z 序排序
for (auto &child : children_) {
child->collectRenderCommands(commands, accumulatedZOrder);
}
}
Tween &Node::tween() {
auto tw = std::make_shared<Tween>(this);
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::tween(const std::string &name) {
auto tw = std::make_shared<Tween>(this, name);
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::moveTo(const Vec2 &pos, float duration, TweenEasing easing) {
TweenProperty props;
props.position = pos;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::moveBy(const Vec2 &delta, float duration, TweenEasing easing) {
TweenProperty props;
props.position = delta;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->by(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::scaleTo(float scale, float duration, TweenEasing easing) {
TweenProperty props;
props.scale = Vec2(scale, scale);
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::scaleBy(float delta, float duration, TweenEasing easing) {
TweenProperty props;
props.scale = Vec2(delta, delta);
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->by(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::rotateTo(float degrees, float duration, TweenEasing easing) {
TweenProperty props;
props.rotation = degrees;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::rotateBy(float degrees, float duration, TweenEasing easing) {
TweenProperty props;
props.rotation = degrees;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->by(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::fadeIn(float duration, TweenEasing easing) {
TweenProperty props;
props.opacity = 1.0f;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::fadeOut(float duration, TweenEasing easing) {
TweenProperty props;
props.opacity = 0.0f;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::fadeTo(float opacity, float duration, TweenEasing easing) {
TweenProperty props;
props.opacity = opacity;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
void Node::stopAllTweens() {
for (auto &tw : tweens_) {
tw->stop();
}
tweens_.clear();
}
void Node::updateTweens(float dt) {
for (auto it = tweens_.begin(); it != tweens_.end();) {
(*it)->update(dt);
if ((*it)->isFinished()) {
it = tweens_.erase(it);
} else {
++it;
}
}
}
} // namespace extra2d