feat: 添加2D渲染系统核心组件
新增2D渲染系统核心组件,包括节点系统、场景管理、相机服务、窗口适配、动作系统等。主要变更如下: 1. 实现节点系统基础架构(Node, SceneGraph) 2. 添加多种渲染组件(Sprite, Text, Shape) 3. 实现相机系统(Camera, OrthoCam)及相机服务 4. 添加窗口适配服务(WindowAdapt) 5. 实现完整的动作系统(Act)及其派生类 6. 添加场景过渡效果(Trans) 7. 集成Vulkan渲染后端支持 8. 实现着色器管理系统(ShaderMgr) 9. 添加动态着色器支持(DynShader) 10. 完善数学工具类(MathExtended) 11. 更新构建系统支持Vulkan 12. 添加相关测试用例
This commit is contained in:
parent
6a12bb5e2e
commit
d1d03520ff
|
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/ref_counted.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <functional>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Node;
|
||||
|
||||
class Act : public RefCounted {
|
||||
public:
|
||||
virtual ~Act() = default;
|
||||
|
||||
i32 tag() const { return tag_; }
|
||||
void tag(i32 t) { tag_ = t; }
|
||||
|
||||
Node* target() const { return target_; }
|
||||
Node* origTarget() const { return origTarget_; }
|
||||
|
||||
virtual bool done() const = 0;
|
||||
virtual void start(Node* t);
|
||||
virtual void stop();
|
||||
virtual void step(f32 dt);
|
||||
virtual void update(f32 t) = 0;
|
||||
|
||||
virtual Act* clone() const = 0;
|
||||
virtual Act* reverse() const = 0;
|
||||
|
||||
protected:
|
||||
Node* target_ = nullptr;
|
||||
Node* origTarget_ = nullptr;
|
||||
i32 tag_ = -1;
|
||||
};
|
||||
|
||||
class ActInstant : public Act {
|
||||
public:
|
||||
bool done() const override { return true; }
|
||||
void step(f32 dt) override { update(1.0f); }
|
||||
};
|
||||
|
||||
class ActInterval : public Act {
|
||||
public:
|
||||
explicit ActInterval(f32 d) : dur_(d) {}
|
||||
|
||||
bool done() const override { return elap_ >= dur_; }
|
||||
|
||||
void step(f32 dt) override {
|
||||
elap_ += dt;
|
||||
f32 t = std::min(elap_ / dur_, 1.0f);
|
||||
update(easeTime(t));
|
||||
}
|
||||
|
||||
f32 elap() const { return elap_; }
|
||||
f32 dur() const { return dur_; }
|
||||
void dur(f32 d) { dur_ = d; }
|
||||
|
||||
using Ease = std::function<f32(f32)>;
|
||||
void ease(Ease f) { ease_ = f; }
|
||||
|
||||
f32 easeTime(f32 t) const { return ease_ ? ease_(t) : t; }
|
||||
|
||||
static f32 easeLinear(f32 t) { return t; }
|
||||
static f32 easeIn(f32 t) { return t * t; }
|
||||
static f32 easeOut(f32 t) { return t * (2 - t); }
|
||||
static f32 easeInOut(f32 t) {
|
||||
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
}
|
||||
static f32 easeBackIn(f32 t) {
|
||||
f32 s = 1.70158f;
|
||||
return t * t * ((s + 1) * t - s);
|
||||
}
|
||||
static f32 easeBackOut(f32 t) {
|
||||
f32 s = 1.70158f;
|
||||
t -= 1;
|
||||
return t * t * ((s + 1) * t + s) + 1;
|
||||
}
|
||||
static f32 easeBounceOut(f32 t) {
|
||||
if (t < 1 / 2.75f) {
|
||||
return 7.5625f * t * t;
|
||||
} else if (t < 2 / 2.75f) {
|
||||
t -= 1.5f / 2.75f;
|
||||
return 7.5625f * t * t + 0.75f;
|
||||
} else if (t < 2.5f / 2.75f) {
|
||||
t -= 2.25f / 2.75f;
|
||||
return 7.5625f * t * t + 0.9375f;
|
||||
} else {
|
||||
t -= 2.625f / 2.75f;
|
||||
return 7.5625f * t * t + 0.984375f;
|
||||
}
|
||||
}
|
||||
static f32 easeElasticOut(f32 t) {
|
||||
if (t == 0 || t == 1) return t;
|
||||
f32 p = 0.3f;
|
||||
f32 s = p / 4;
|
||||
return std::pow(2, -10 * t) * std::sin((t - s) * (2 * PI_F) / p) + 1;
|
||||
}
|
||||
|
||||
protected:
|
||||
f32 dur_ = 0.0f;
|
||||
f32 elap_ = 0.0f;
|
||||
Ease ease_ = easeLinear;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Delay : public ActInterval {
|
||||
public:
|
||||
explicit Delay(f32 d) : ActInterval(d) {}
|
||||
|
||||
void update(f32 t) override {}
|
||||
Act* clone() const override { return new Delay(dur_); }
|
||||
Act* reverse() const override { return clone(); }
|
||||
};
|
||||
|
||||
class Seq : public ActInterval {
|
||||
public:
|
||||
Seq(Act* a1, Act* a2);
|
||||
|
||||
template<typename... As>
|
||||
static Seq* create(As... as) {
|
||||
std::initializer_list<Act*> acts = {static_cast<Act*>(as)...};
|
||||
return createFromArray(acts);
|
||||
}
|
||||
|
||||
void start(Node* t) override;
|
||||
void stop() override;
|
||||
void step(f32 dt) override;
|
||||
void update(f32 t) override {}
|
||||
bool done() const override;
|
||||
Act* clone() const override;
|
||||
Act* reverse() const override;
|
||||
|
||||
private:
|
||||
std::vector<Act*> acts_;
|
||||
size_t curIdx_ = 0;
|
||||
f32 split_ = 0.0f;
|
||||
|
||||
static Seq* createFromArray(std::initializer_list<Act*> acts);
|
||||
};
|
||||
|
||||
class Spawn : public ActInterval {
|
||||
public:
|
||||
Spawn(Act* a1, Act* a2);
|
||||
|
||||
template<typename... As>
|
||||
static Spawn* create(As... as) {
|
||||
std::initializer_list<Act*> acts = {static_cast<Act*>(as)...};
|
||||
return createFromArray(acts);
|
||||
}
|
||||
|
||||
void start(Node* t) override;
|
||||
void stop() override;
|
||||
void update(f32 t) override;
|
||||
Act* clone() const override;
|
||||
Act* reverse() const override;
|
||||
|
||||
private:
|
||||
std::vector<Act*> acts_;
|
||||
|
||||
static Spawn* createFromArray(std::initializer_list<Act*> acts);
|
||||
};
|
||||
|
||||
class Repeat : public ActInterval {
|
||||
public:
|
||||
Repeat(Act* a, int times);
|
||||
explicit Repeat(Act* a);
|
||||
|
||||
bool done() const override;
|
||||
void start(Node* t) override;
|
||||
void stop() override;
|
||||
void update(f32 t) override;
|
||||
Act* clone() const override;
|
||||
Act* reverse() const override;
|
||||
|
||||
Act* inner() const { return inner_; }
|
||||
|
||||
private:
|
||||
Act* inner_ = nullptr;
|
||||
int times_ = 1;
|
||||
int cnt_ = 0;
|
||||
bool inf_ = false;
|
||||
};
|
||||
|
||||
class RepeatForever : public Act {
|
||||
public:
|
||||
explicit RepeatForever(Act* a) : inner_(a) {
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
dur_ = interval->dur();
|
||||
}
|
||||
}
|
||||
|
||||
bool done() const override { return false; }
|
||||
void start(Node* t) override;
|
||||
void step(f32 dt) override;
|
||||
void update(f32 t) override {}
|
||||
Act* clone() const override { return new RepeatForever(inner_->clone()); }
|
||||
Act* reverse() const override { return new RepeatForever(inner_->reverse()); }
|
||||
|
||||
private:
|
||||
Act* inner_ = nullptr;
|
||||
f32 dur_ = 0.0f;
|
||||
f32 elap_ = 0.0f;
|
||||
};
|
||||
|
||||
class Speed : public ActInterval {
|
||||
public:
|
||||
Speed(Act* a, f32 s) : ActInterval(0), inner_(a), speed_(s) {
|
||||
if (a) {
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
dur_ = interval->dur() / s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void start(Node* t) override;
|
||||
void stop() override;
|
||||
void step(f32 dt) override;
|
||||
bool done() const override { return inner_ ? inner_->done() : true; }
|
||||
void update(f32 t) override {}
|
||||
Act* clone() const override { return new Speed(inner_->clone(), speed_); }
|
||||
Act* reverse() const override { return new Speed(inner_->reverse(), speed_); }
|
||||
|
||||
void speed(f32 s) { speed_ = s; }
|
||||
f32 speed() const { return speed_; }
|
||||
|
||||
private:
|
||||
Act* inner_ = nullptr;
|
||||
f32 speed_ = 1.0f;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/node/renderer.h>
|
||||
#include <extra2d/core/color.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class FadeTo : public ActInterval {
|
||||
public:
|
||||
FadeTo(f32 d, u8 o) : ActInterval(d), end_(o) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) {
|
||||
if (auto* r = t->get<Renderer>()) {
|
||||
start_ = r->getOpacity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
if (auto* r = target_->get<Renderer>()) {
|
||||
u8 opacity = static_cast<u8>(start_ + (end_ - start_) * t);
|
||||
r->setOpacity(opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new FadeTo(dur_, end_); }
|
||||
Act* reverse() const override { return new FadeTo(dur_, start_); }
|
||||
|
||||
protected:
|
||||
u8 start_ = 255;
|
||||
u8 end_ = 255;
|
||||
};
|
||||
|
||||
class FadeOut : public FadeTo {
|
||||
public:
|
||||
explicit FadeOut(f32 d) : FadeTo(d, 0) {}
|
||||
Act* clone() const override { return new FadeOut(dur_); }
|
||||
Act* reverse() const override;
|
||||
};
|
||||
|
||||
class FadeIn : public FadeTo {
|
||||
public:
|
||||
explicit FadeIn(f32 d) : FadeTo(d, 255) {}
|
||||
Act* clone() const override { return new FadeIn(dur_); }
|
||||
Act* reverse() const override { return new FadeOut(dur_); }
|
||||
};
|
||||
|
||||
inline Act* FadeOut::reverse() const { return new FadeIn(dur_); }
|
||||
|
||||
class TintTo : public ActInterval {
|
||||
public:
|
||||
TintTo(f32 d, const Color& c) : ActInterval(d), end_(c) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) {
|
||||
if (auto* r = t->get<Renderer>()) {
|
||||
}
|
||||
}
|
||||
start_ = Colors::White;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
}
|
||||
|
||||
Act* clone() const override { return new TintTo(dur_, end_); }
|
||||
Act* reverse() const override { return new TintTo(dur_, start_); }
|
||||
|
||||
private:
|
||||
Color start_;
|
||||
Color end_;
|
||||
};
|
||||
|
||||
class Blink : public ActInterval {
|
||||
public:
|
||||
Blink(f32 d, int times) : ActInterval(d), times_(times) {}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
int slice = static_cast<int>(t * times_ * 2);
|
||||
bool visible = (slice % 2 == 0);
|
||||
target_->setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new Blink(dur_, times_); }
|
||||
Act* reverse() const override { return clone(); }
|
||||
|
||||
private:
|
||||
int times_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/color.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class CallFunc : public ActInstant {
|
||||
public:
|
||||
explicit CallFunc(std::function<void()> f) : func_(f) {}
|
||||
|
||||
void update(f32 t) override { if (func_) func_(); }
|
||||
Act* clone() const override { return new CallFunc(func_); }
|
||||
Act* reverse() const override { return clone(); }
|
||||
|
||||
private:
|
||||
std::function<void()> func_;
|
||||
};
|
||||
|
||||
class Place : public ActInstant {
|
||||
public:
|
||||
explicit Place(const Vec2& p) : pos_(p) {}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) target_->pos = pos_;
|
||||
}
|
||||
Act* clone() const override { return new Place(pos_); }
|
||||
Act* reverse() const override { return clone(); }
|
||||
|
||||
private:
|
||||
Vec2 pos_;
|
||||
};
|
||||
|
||||
class Hide : public ActInstant {
|
||||
public:
|
||||
void update(f32 t) override {
|
||||
if (target_) target_->setVisible(false);
|
||||
}
|
||||
Act* clone() const override { return new Hide(); }
|
||||
Act* reverse() const override;
|
||||
};
|
||||
|
||||
class Show : public ActInstant {
|
||||
public:
|
||||
void update(f32 t) override {
|
||||
if (target_) target_->setVisible(true);
|
||||
}
|
||||
Act* clone() const override { return new Show(); }
|
||||
Act* reverse() const override { return new Hide(); }
|
||||
};
|
||||
|
||||
inline Act* Hide::reverse() const { return new Show(); }
|
||||
|
||||
class ToggleVis : public ActInstant {
|
||||
public:
|
||||
void update(f32 t) override {
|
||||
if (target_) target_->setVisible(!target_->visible());
|
||||
}
|
||||
Act* clone() const override { return new ToggleVis(); }
|
||||
Act* reverse() const override { return clone(); }
|
||||
};
|
||||
|
||||
class RemoveSelf : public ActInstant {
|
||||
public:
|
||||
explicit RemoveSelf(bool cleanup = true) : cleanup_(cleanup) {}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->removeFromParent();
|
||||
}
|
||||
}
|
||||
Act* clone() const override { return new RemoveSelf(cleanup_); }
|
||||
Act* reverse() const override { return clone(); }
|
||||
|
||||
private:
|
||||
bool cleanup_ = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/color.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class MoveTo : public ActInterval {
|
||||
public:
|
||||
MoveTo(f32 d, const Vec2& p) : ActInterval(d), end_(p) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->pos;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->pos = Vec2::lerp(start_, end_, t);
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new MoveTo(dur_, end_); }
|
||||
Act* reverse() const override { return new MoveTo(dur_, start_); }
|
||||
|
||||
private:
|
||||
Vec2 start_;
|
||||
Vec2 end_;
|
||||
};
|
||||
|
||||
class MoveBy : public ActInterval {
|
||||
public:
|
||||
MoveBy(f32 d, const Vec2& dlt) : ActInterval(d), dlt_(dlt) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->pos;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->pos = start_ + dlt_ * t;
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new MoveBy(dur_, dlt_); }
|
||||
Act* reverse() const override { return new MoveBy(dur_, -dlt_); }
|
||||
|
||||
private:
|
||||
Vec2 dlt_;
|
||||
Vec2 start_;
|
||||
};
|
||||
|
||||
class JumpTo : public ActInterval {
|
||||
public:
|
||||
JumpTo(f32 d, const Vec2& p, f32 height, u32 jumps)
|
||||
: ActInterval(d), end_(p), height_(height), jumps_(jumps) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->pos;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
f32 frac = (t * jumps_) - std::floor(t * jumps_);
|
||||
f32 y = height_ * std::sin(frac * PI_F);
|
||||
target_->pos = Vec2::lerp(start_, end_, t);
|
||||
target_->pos.y += y;
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new JumpTo(dur_, end_, height_, jumps_); }
|
||||
Act* reverse() const override { return new JumpTo(dur_, start_, height_, jumps_); }
|
||||
|
||||
private:
|
||||
Vec2 start_;
|
||||
Vec2 end_;
|
||||
f32 height_;
|
||||
u32 jumps_;
|
||||
};
|
||||
|
||||
class JumpBy : public ActInterval {
|
||||
public:
|
||||
JumpBy(f32 d, const Vec2& dlt, f32 height, u32 jumps)
|
||||
: ActInterval(d), dlt_(dlt), height_(height), jumps_(jumps) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->pos;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
f32 frac = (t * jumps_) - std::floor(t * jumps_);
|
||||
f32 y = height_ * std::sin(frac * PI_F);
|
||||
target_->pos = start_ + dlt_ * t;
|
||||
target_->pos.y += y;
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new JumpBy(dur_, dlt_, height_, jumps_); }
|
||||
Act* reverse() const override { return new JumpBy(dur_, -dlt_, height_, jumps_); }
|
||||
|
||||
private:
|
||||
Vec2 dlt_;
|
||||
Vec2 start_;
|
||||
f32 height_;
|
||||
u32 jumps_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class RotTo : public ActInterval {
|
||||
public:
|
||||
RotTo(f32 d, f32 a) : ActInterval(d), end_(a) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) {
|
||||
start_ = t->rot;
|
||||
dlt_ = end_ - start_;
|
||||
if (dlt_ > 180.0f) dlt_ -= 360.0f;
|
||||
if (dlt_ < -180.0f) dlt_ += 360.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->rot = start_ + dlt_ * t;
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new RotTo(dur_, end_); }
|
||||
Act* reverse() const override { return new RotTo(dur_, start_); }
|
||||
|
||||
private:
|
||||
f32 start_ = 0.0f;
|
||||
f32 end_ = 0.0f;
|
||||
f32 dlt_ = 0.0f;
|
||||
};
|
||||
|
||||
class RotBy : public ActInterval {
|
||||
public:
|
||||
RotBy(f32 d, f32 dlt) : ActInterval(d), dlt_(dlt) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->rot;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->rot = start_ + dlt_ * t;
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new RotBy(dur_, dlt_); }
|
||||
Act* reverse() const override { return new RotBy(dur_, -dlt_); }
|
||||
|
||||
private:
|
||||
f32 dlt_ = 0.0f;
|
||||
f32 start_ = 0.0f;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class ScaleTo : public ActInterval {
|
||||
public:
|
||||
ScaleTo(f32 d, f32 s) : ActInterval(d), end_(s, s) {}
|
||||
ScaleTo(f32 d, const Vec2& s) : ActInterval(d), end_(s) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->scale;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->scale = Vec2::lerp(start_, end_, t);
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new ScaleTo(dur_, end_); }
|
||||
Act* reverse() const override { return new ScaleTo(dur_, start_); }
|
||||
|
||||
private:
|
||||
Vec2 start_;
|
||||
Vec2 end_;
|
||||
};
|
||||
|
||||
class ScaleBy : public ActInterval {
|
||||
public:
|
||||
ScaleBy(f32 d, f32 s) : ActInterval(d), dlt_(s, s) {}
|
||||
ScaleBy(f32 d, const Vec2& s) : ActInterval(d), dlt_(s) {}
|
||||
|
||||
void start(Node* t) override {
|
||||
ActInterval::start(t);
|
||||
if (t) start_ = t->scale;
|
||||
}
|
||||
|
||||
void update(f32 t) override {
|
||||
if (target_) {
|
||||
target_->scale = start_ + dlt_ * t;
|
||||
}
|
||||
}
|
||||
|
||||
Act* clone() const override { return new ScaleBy(dur_, dlt_); }
|
||||
Act* reverse() const override { return new ScaleBy(dur_, Vec2(1.0f / dlt_.x, 1.0f / dlt_.y)); }
|
||||
|
||||
private:
|
||||
Vec2 dlt_;
|
||||
Vec2 start_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
enum class CamType {
|
||||
Ortho,
|
||||
Persp
|
||||
};
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
virtual ~Camera() = default;
|
||||
|
||||
virtual CamType type() const = 0;
|
||||
|
||||
virtual Mat4 view() const = 0;
|
||||
virtual Mat4 proj() const = 0;
|
||||
|
||||
Mat4 vp() const { return proj() * view(); }
|
||||
|
||||
virtual Frustum frustum() const = 0;
|
||||
|
||||
virtual Vec2 screenToWorld(const Vec2& s) const = 0;
|
||||
virtual Vec2 worldToScreen(const Vec2& w) const = 0;
|
||||
|
||||
void viewport(f32 x, f32 y, f32 w, f32 h) {
|
||||
vx_ = x; vy_ = y; vw_ = w; vh_ = h;
|
||||
}
|
||||
|
||||
Vec2 viewportPos() const { return Vec2(vx_, vy_); }
|
||||
Vec2 viewportSize() const { return Vec2(vw_, vh_); }
|
||||
Rect viewportRect() const { return Rect(vx_, vy_, vw_, vh_); }
|
||||
|
||||
const Vec3& position() const { return pos_; }
|
||||
void setPosition(const Vec3& p) { pos_ = p; dirty_ = true; }
|
||||
void setPosition(const Vec2& p) { pos_ = Vec3(p.x, p.y, pos_.z); dirty_ = true; }
|
||||
|
||||
f32 rotation() const { return rot_; }
|
||||
void setRotation(f32 r) { rot_ = r; dirty_ = true; }
|
||||
|
||||
f32 zoom() const { return zoom_; }
|
||||
void setZoom(f32 z) { zoom_ = z; dirty_ = true; }
|
||||
|
||||
bool dirty() const { return dirty_; }
|
||||
void setDirty(bool d) { dirty_ = d; }
|
||||
|
||||
protected:
|
||||
Vec3 pos_ = Vec3(0, 0, 0);
|
||||
f32 rot_ = 0.0f;
|
||||
f32 zoom_ = 1.0f;
|
||||
f32 vx_ = 0, vy_ = 0;
|
||||
f32 vw_ = 1, vh_ = 1;
|
||||
bool dirty_ = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/cam/cam.h>
|
||||
#include <extra2d/cam/ortho_cam.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class ICamSvc : public IService {
|
||||
public:
|
||||
virtual Camera* create(CamType t, const std::string& n) = 0;
|
||||
|
||||
virtual Camera* get(const std::string& n) = 0;
|
||||
virtual Camera* main() = 0;
|
||||
|
||||
virtual void setMain(const std::string& n) = 0;
|
||||
|
||||
virtual void destroy(const std::string& n) = 0;
|
||||
|
||||
virtual Ray screenRay(const Vec2& s) = 0;
|
||||
|
||||
virtual Vec2 screenToWorld(const Vec2& s) = 0;
|
||||
virtual Vec2 worldToScreen(const Vec2& w) = 0;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "CamSvc";
|
||||
i.priority = ServicePriority::Camera;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
class CamSvc : public ICamSvc {
|
||||
public:
|
||||
bool init() override {
|
||||
auto* mainCam = create(CamType::Ortho, "main");
|
||||
if (mainCam) {
|
||||
mainName_ = "main";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
cameras_.clear();
|
||||
mainName_.clear();
|
||||
}
|
||||
|
||||
Camera* create(CamType t, const std::string& n) override {
|
||||
if (cameras_.find(n) != cameras_.end()) {
|
||||
return cameras_[n].get();
|
||||
}
|
||||
|
||||
Unique<Camera> cam;
|
||||
if (t == CamType::Ortho) {
|
||||
cam = ptr::makeUnique<OrthoCam>();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Camera* ptr = cam.get();
|
||||
cameras_[n] = std::move(cam);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
Camera* get(const std::string& n) override {
|
||||
auto it = cameras_.find(n);
|
||||
if (it != cameras_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Camera* main() override {
|
||||
return get(mainName_);
|
||||
}
|
||||
|
||||
void setMain(const std::string& n) override {
|
||||
if (cameras_.find(n) != cameras_.end()) {
|
||||
mainName_ = n;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(const std::string& n) override {
|
||||
if (n == mainName_) return;
|
||||
cameras_.erase(n);
|
||||
}
|
||||
|
||||
Ray screenRay(const Vec2& s) override {
|
||||
Camera* cam = main();
|
||||
if (cam) {
|
||||
Vec2 worldPos = cam->screenToWorld(s);
|
||||
return Ray(worldPos, Vec2(0, 0));
|
||||
}
|
||||
return Ray(s, Vec2(0, 0));
|
||||
}
|
||||
|
||||
Vec2 screenToWorld(const Vec2& s) override {
|
||||
Camera* cam = main();
|
||||
if (cam) {
|
||||
return cam->screenToWorld(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Vec2 worldToScreen(const Vec2& w) override {
|
||||
Camera* cam = main();
|
||||
if (cam) {
|
||||
return cam->worldToScreen(w);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, Unique<Camera>> cameras_;
|
||||
std::string mainName_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/cam/cam.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class OrthoCam : public Camera {
|
||||
public:
|
||||
CamType type() const override { return CamType::Ortho; }
|
||||
|
||||
Mat4 view() const override {
|
||||
Mat4 m = glm::mat4(1.0f);
|
||||
m = glm::translate(m, glm::vec3(-pos_.x, -pos_.y, -pos_.z));
|
||||
m = glm::rotate(m, -rot_ * DEG_TO_RAD, glm::vec3(0, 0, 1));
|
||||
m = glm::scale(m, glm::vec3(zoom_, zoom_, 1.0f));
|
||||
return m;
|
||||
}
|
||||
|
||||
Mat4 proj() const override {
|
||||
return glm::ortho(l_ * zoom_, r_ * zoom_, b_ * zoom_, t_ * zoom_, n_, f_);
|
||||
}
|
||||
|
||||
Frustum frustum() const override {
|
||||
Frustum f;
|
||||
return f;
|
||||
}
|
||||
|
||||
Vec2 screenToWorld(const Vec2& s) const override {
|
||||
f32 x = l_ + (s.x - vx_) / vw_ * (r_ - l_);
|
||||
f32 y = t_ - (s.y - vy_) / vh_ * (t_ - b_);
|
||||
return Vec2(x / zoom_ + pos_.x, y / zoom_ + pos_.y);
|
||||
}
|
||||
|
||||
Vec2 worldToScreen(const Vec2& w) const override {
|
||||
f32 x = (w.x - pos_.x) * zoom_;
|
||||
f32 y = (w.y - pos_.y) * zoom_;
|
||||
f32 sx = vx_ + (x - l_) / (r_ - l_) * vw_;
|
||||
f32 sy = vy_ + (t_ - y) / (t_ - b_) * vh_;
|
||||
return Vec2(sx, sy);
|
||||
}
|
||||
|
||||
void ortho(f32 left, f32 right, f32 bottom, f32 top, f32 nearZ = -1, f32 farZ = 1) {
|
||||
l_ = left; r_ = right;
|
||||
b_ = bottom; t_ = top;
|
||||
n_ = nearZ; f_ = farZ;
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
void setOrthoFromSize(f32 width, f32 height) {
|
||||
f32 halfW = width * 0.5f;
|
||||
f32 halfH = height * 0.5f;
|
||||
ortho(-halfW, halfW, -halfH, halfH);
|
||||
}
|
||||
|
||||
f32 left() const { return l_; }
|
||||
f32 right() const { return r_; }
|
||||
f32 bottom() const { return b_; }
|
||||
f32 top() const { return t_; }
|
||||
f32 nearZ() const { return n_; }
|
||||
f32 farZ() const { return f_; }
|
||||
|
||||
f32 width() const { return r_ - l_; }
|
||||
f32 height() const { return t_ - b_; }
|
||||
|
||||
Vec2 center() const { return Vec2((l_ + r_) * 0.5f, (b_ + t_) * 0.5f); }
|
||||
|
||||
void setCenter(f32 x, f32 y) {
|
||||
f32 halfW = width() * 0.5f;
|
||||
f32 halfH = height() * 0.5f;
|
||||
l_ = x - halfW;
|
||||
r_ = x + halfW;
|
||||
b_ = y - halfH;
|
||||
t_ = y + halfH;
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
void setCenter(const Vec2& c) { setCenter(c.x, c.y); }
|
||||
|
||||
private:
|
||||
f32 l_ = -1, r_ = 1;
|
||||
f32 b_ = -1, t_ = 1;
|
||||
f32 n_ = -1, f_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <glm/mat3x3.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
using Mat3 = glm::mat3;
|
||||
using Mat4 = glm::mat4;
|
||||
using Vec4 = glm::vec4;
|
||||
|
||||
struct BoundingBox {
|
||||
Vec2 min;
|
||||
Vec2 max;
|
||||
|
||||
constexpr BoundingBox() : min(0, 0), max(0, 0) {}
|
||||
constexpr BoundingBox(const Vec2& min, const Vec2& max) : min(min), max(max) {}
|
||||
constexpr BoundingBox(f32 minX, f32 minY, f32 maxX, f32 maxY)
|
||||
: min(minX, minY), max(maxX, maxY) {}
|
||||
|
||||
static BoundingBox fromCenter(const Vec2& center, const Vec2& halfSize) {
|
||||
return BoundingBox(center - halfSize, center + halfSize);
|
||||
}
|
||||
|
||||
static BoundingBox fromSize(const Vec2& pos, const Vec2& size) {
|
||||
return BoundingBox(pos, pos + size);
|
||||
}
|
||||
|
||||
Vec2 center() const { return (min + max) * 0.5f; }
|
||||
Vec2 size() const { return max - min; }
|
||||
Vec2 halfSize() const { return size() * 0.5f; }
|
||||
|
||||
f32 width() const { return max.x - min.x; }
|
||||
f32 height() const { return max.y - min.y; }
|
||||
|
||||
bool empty() const { return max.x <= min.x || max.y <= min.y; }
|
||||
|
||||
bool contains(const Vec2& p) const {
|
||||
return p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y;
|
||||
}
|
||||
|
||||
bool intersects(const BoundingBox& other) const {
|
||||
return !(min.x > other.max.x || max.x < other.min.x ||
|
||||
min.y > other.max.y || max.y < other.min.y);
|
||||
}
|
||||
|
||||
BoundingBox merged(const BoundingBox& other) const {
|
||||
return BoundingBox(
|
||||
Vec2(std::min(min.x, other.min.x), std::min(min.y, other.min.y)),
|
||||
Vec2(std::max(max.x, other.max.x), std::max(max.y, other.max.y))
|
||||
);
|
||||
}
|
||||
|
||||
BoundingBox expanded(f32 amount) const {
|
||||
return BoundingBox(min - Vec2(amount, amount), max + Vec2(amount, amount));
|
||||
}
|
||||
|
||||
static BoundingBox null() { return BoundingBox(); }
|
||||
};
|
||||
|
||||
struct Ray {
|
||||
Vec2 origin;
|
||||
Vec2 direction;
|
||||
|
||||
constexpr Ray() = default;
|
||||
constexpr Ray(const Vec2& origin, const Vec2& dir) : origin(origin), direction(dir) {}
|
||||
|
||||
Vec2 pointAt(f32 t) const { return origin + direction * t; }
|
||||
};
|
||||
|
||||
struct Frustum {
|
||||
Vec4 planes[4];
|
||||
|
||||
Frustum() = default;
|
||||
|
||||
static Frustum fromVP(const Mat4& vp);
|
||||
|
||||
bool containsPoint(const Vec3& point) const;
|
||||
bool containsSphere(const Vec3& center, f32 radius) const;
|
||||
bool containsBox(const BoundingBox& box) const;
|
||||
};
|
||||
|
||||
inline Frustum Frustum::fromVP(const Mat4& vp) {
|
||||
Frustum f;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
f.planes[i] = Vec4(0);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
inline bool Frustum::containsPoint(const Vec3& point) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Frustum::containsSphere(const Vec3& center, f32 radius) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Frustum::containsBox(const BoundingBox& box) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -476,8 +476,10 @@ inline Vec2 extractPosition(const glm::mat4 &matrix) {
|
|||
* @return 提取的缩放向量
|
||||
*/
|
||||
inline Vec2 extractScale(const glm::mat4 &matrix) {
|
||||
float scaleX = std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
|
||||
float scaleY = std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
|
||||
float scaleX =
|
||||
std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
|
||||
float scaleY =
|
||||
std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
|
||||
return {scaleX, scaleY};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class RefCounted {
|
||||
public:
|
||||
RefCounted() = default;
|
||||
virtual ~RefCounted() = default;
|
||||
|
||||
void retain() const { ++refCount_; }
|
||||
void release() const {
|
||||
if (--refCount_ == 0) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
u32 refCount() const { return refCount_; }
|
||||
|
||||
private:
|
||||
mutable u32 refCount_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -6,9 +6,13 @@
|
|||
// Core
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/ref_counted.h>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
|
||||
// Platform
|
||||
#include <extra2d/platform/glfw/glfw_window.h>
|
||||
|
|
@ -39,6 +43,45 @@
|
|||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
|
||||
// Node
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/node/scene_graph.h>
|
||||
#include <extra2d/node/renderer.h>
|
||||
#include <extra2d/node/sprite.h>
|
||||
#include <extra2d/node/text.h>
|
||||
#include <extra2d/node/shape.h>
|
||||
|
||||
// Camera
|
||||
#include <extra2d/cam/cam.h>
|
||||
#include <extra2d/cam/ortho_cam.h>
|
||||
#include <extra2d/cam/cam_svc.h>
|
||||
|
||||
// Window
|
||||
#include <extra2d/win/win_adapt.h>
|
||||
#include <extra2d/win/win_adapt_svc.h>
|
||||
|
||||
// Render
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/render/render_svc.h>
|
||||
#include <extra2d/render/render_mod.h>
|
||||
#include <extra2d/render/shader_mgr.h>
|
||||
#include <extra2d/render/shader_sdf.h>
|
||||
#include <extra2d/render/dyn_shader.h>
|
||||
|
||||
// Action
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/act/act_instant.h>
|
||||
#include <extra2d/act/act_move.h>
|
||||
#include <extra2d/act/act_rotate.h>
|
||||
#include <extra2d/act/act_scale.h>
|
||||
#include <extra2d/act/act_fade.h>
|
||||
#include <extra2d/act/act_composite.h>
|
||||
|
||||
// Scene
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/director.h>
|
||||
#include <extra2d/scene/trans.h>
|
||||
|
||||
// Application
|
||||
#include <extra2d/app/application.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Node;
|
||||
class Comp;
|
||||
class Act;
|
||||
|
||||
namespace Anchor {
|
||||
constexpr Vec2 BottomLeft = Vec2(0.0f, 0.0f);
|
||||
constexpr Vec2 Bottom = Vec2(0.5f, 0.0f);
|
||||
constexpr Vec2 BottomRight = Vec2(1.0f, 0.0f);
|
||||
constexpr Vec2 Left = Vec2(0.0f, 0.5f);
|
||||
constexpr Vec2 Center = Vec2(0.5f, 0.5f);
|
||||
constexpr Vec2 Right = Vec2(1.0f, 0.5f);
|
||||
constexpr Vec2 TopLeft = Vec2(0.0f, 1.0f);
|
||||
constexpr Vec2 Top = Vec2(0.5f, 1.0f);
|
||||
constexpr Vec2 TopRight = Vec2(1.0f, 1.0f);
|
||||
}
|
||||
|
||||
class Comp {
|
||||
public:
|
||||
virtual ~Comp() = default;
|
||||
|
||||
virtual const char* type() const = 0;
|
||||
|
||||
virtual void onAttach(Node* o) { owner_ = o; }
|
||||
virtual void onDetach() { owner_ = nullptr; }
|
||||
virtual void onEnable() {}
|
||||
virtual void onDisable() {}
|
||||
|
||||
virtual void update(f32 dt) {}
|
||||
virtual void lateUpdate(f32 dt) {}
|
||||
|
||||
Node* owner() const { return owner_; }
|
||||
|
||||
bool enabled() const { return enabled_; }
|
||||
void setEnabled(bool e);
|
||||
|
||||
protected:
|
||||
Node* owner_ = nullptr;
|
||||
bool enabled_ = true;
|
||||
};
|
||||
|
||||
class Node : public std::enable_shared_from_this<Node> {
|
||||
public:
|
||||
virtual ~Node();
|
||||
|
||||
Vec2 pos = Vec2(0, 0);
|
||||
Vec2 scale = Vec2(1, 1);
|
||||
f32 rot = 0.0f;
|
||||
Vec2 anchor = Vec2(0.5f, 0.5f);
|
||||
|
||||
void setAnchor(f32 x, f32 y) { anchor = Vec2(x, y); }
|
||||
void setAnchor(const Vec2& a) { anchor = a; }
|
||||
|
||||
void setPos(f32 x, f32 y) { pos = Vec2(x, y); }
|
||||
void setPos(const Vec2& p) { pos = p; }
|
||||
|
||||
void setScale(f32 x, f32 y) { scale = Vec2(x, y); }
|
||||
void setScale(const Vec2& s) { scale = s; }
|
||||
void setScale(f32 s) { scale = Vec2(s, s); }
|
||||
|
||||
void setRot(f32 r) { rot = r; }
|
||||
|
||||
Mat3 local() const;
|
||||
Mat3 world() const;
|
||||
Mat4 world4x4() const;
|
||||
|
||||
Vec2 worldPos() const;
|
||||
f32 worldRot() const;
|
||||
Vec2 worldScale() const;
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T* add(Args&&... args) {
|
||||
auto c = std::make_unique<T>(std::forward<Args>(args)...);
|
||||
T* p = c.get();
|
||||
addInternal(std::move(c));
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* get() {
|
||||
for (auto& c : comps_) {
|
||||
if (auto* p = dynamic_cast<T*>(c.get())) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get() const {
|
||||
for (auto& c : comps_) {
|
||||
if (auto* p = dynamic_cast<const T*>(c.get())) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void remove() {
|
||||
comps_.erase(
|
||||
std::remove_if(comps_.begin(), comps_.end(),
|
||||
[](const auto& c) { return dynamic_cast<T*>(c.get()) != nullptr; }),
|
||||
comps_.end()
|
||||
);
|
||||
}
|
||||
|
||||
void updateComps(f32 dt);
|
||||
void lateUpdateComps(f32 dt);
|
||||
|
||||
void addChild(Ref<Node> child);
|
||||
void removeChild(Ref<Node> child);
|
||||
void removeFromParent();
|
||||
Node* parent() const { return parent_; }
|
||||
const std::vector<Ref<Node>>& children() const { return children_; }
|
||||
|
||||
virtual void onInit() {}
|
||||
virtual void onUpdate(f32 dt) {}
|
||||
virtual void onRender() {}
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
void setName(const std::string& n) { name_ = n; }
|
||||
|
||||
bool visible() const { return visible_; }
|
||||
void setVisible(bool v) { visible_ = v; }
|
||||
|
||||
i32 tag() const { return tag_; }
|
||||
void setTag(i32 t) { tag_ = t; }
|
||||
|
||||
Act* run(Act* a);
|
||||
void stop(Act* a);
|
||||
void stopByTag(i32 tag);
|
||||
void stopAll();
|
||||
|
||||
Act* getActByTag(i32 tag);
|
||||
i32 runningActs() const;
|
||||
|
||||
void updateActs(f32 dt);
|
||||
|
||||
protected:
|
||||
virtual Vec2 defaultAnchor() const { return Vec2(0.5f, 0.5f); }
|
||||
|
||||
private:
|
||||
std::vector<Unique<Comp>> comps_;
|
||||
Node* parent_ = nullptr;
|
||||
std::vector<Ref<Node>> children_;
|
||||
std::string name_;
|
||||
bool visible_ = true;
|
||||
i32 tag_ = 0;
|
||||
std::vector<Act*> acts_;
|
||||
|
||||
void addInternal(Unique<Comp> c);
|
||||
};
|
||||
|
||||
inline void Comp::setEnabled(bool e) {
|
||||
if (enabled_ != e) {
|
||||
enabled_ = e;
|
||||
if (enabled_) {
|
||||
onEnable();
|
||||
} else {
|
||||
onDisable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Camera;
|
||||
|
||||
class Renderer : public Comp {
|
||||
public:
|
||||
const char* type() const override { return "Renderer"; }
|
||||
|
||||
virtual void render(class Camera* cam, RenderCmdBuffer& cmdBuffer) = 0;
|
||||
virtual BoundingBox bounds() const = 0;
|
||||
|
||||
i32 order = 0;
|
||||
bool visible = true;
|
||||
u8 opacity = 255;
|
||||
|
||||
void setOpacity(u8 o) { opacity = o; }
|
||||
u8 getOpacity() const { return opacity; }
|
||||
|
||||
bool isVisible() const { return visible && enabled(); }
|
||||
|
||||
protected:
|
||||
Color applyOpacity(const Color& c) const {
|
||||
return Color(c.r, c.g, c.b, c.a * (opacity / 255.0f));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class SceneGraph {
|
||||
public:
|
||||
SceneGraph();
|
||||
~SceneGraph();
|
||||
|
||||
template<typename T, typename... Args>
|
||||
Ref<T> create(Args&&... args) {
|
||||
auto node = ptr::make<T>(std::forward<Args>(args)...);
|
||||
node->onInit();
|
||||
nodes_.push_back(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Ref<T> create() {
|
||||
auto node = ptr::make<T>();
|
||||
node->onInit();
|
||||
nodes_.push_back(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
void add(Ref<Node> node);
|
||||
void remove(Ref<Node> node);
|
||||
void clear();
|
||||
|
||||
Node* find(const std::string& name) const;
|
||||
Node* findByTag(i32 tag) const;
|
||||
|
||||
void traverse(const std::function<void(Node*)>& callback);
|
||||
void traverseRecursive(Node* node, const std::function<void(Node*)>& callback);
|
||||
|
||||
void update(f32 dt);
|
||||
|
||||
const std::vector<Ref<Node>>& roots() const { return nodes_; }
|
||||
size_t nodeCount() const { return nodes_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<Ref<Node>> nodes_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/node/renderer.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
enum class ShapeType {
|
||||
Rect,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Line,
|
||||
Poly,
|
||||
RoundedRect,
|
||||
Arc,
|
||||
Sector,
|
||||
Star,
|
||||
Custom
|
||||
};
|
||||
|
||||
enum class FillMode {
|
||||
Fill,
|
||||
Stroke,
|
||||
FillStroke
|
||||
};
|
||||
|
||||
class Shape : public Renderer {
|
||||
public:
|
||||
const char* type() const override { return "Shape"; }
|
||||
|
||||
void render(Camera* cam, RenderCmdBuffer& cmdBuffer) override;
|
||||
BoundingBox bounds() const override;
|
||||
|
||||
ShapeType shapeType = ShapeType::Rect;
|
||||
FillMode fill = FillMode::Fill;
|
||||
|
||||
Vec2 size = Vec2(100, 100);
|
||||
f32 radius = 50.0f;
|
||||
f32 lineWidth = 1.0f;
|
||||
Color fillColor = Colors::White;
|
||||
Color strokeColor = Colors::Black;
|
||||
|
||||
f32 cornerRadius = 0.0f;
|
||||
|
||||
Vec2 start = Vec2(0, 0);
|
||||
Vec2 end = Vec2(100, 0);
|
||||
|
||||
std::vector<Vec2> points;
|
||||
u32 sides = 3;
|
||||
f32 innerRadius = 0.5f;
|
||||
|
||||
f32 startAngle = 0.0f;
|
||||
f32 sweepAngle = 3.14159f;
|
||||
|
||||
Vec2 anchor = Vec2(0.5f, 0.5f);
|
||||
|
||||
void setAnchor(f32 x, f32 y) { anchor = Vec2(x, y); }
|
||||
void setAnchor(const Vec2& a) { anchor = a; }
|
||||
|
||||
static Shape* rect(const Vec2& sz, const Color& c);
|
||||
static Shape* circle(f32 r, const Color& c);
|
||||
static Shape* ellipse(const Vec2& sz, const Color& c);
|
||||
static Shape* line(const Vec2& s, const Vec2& e, f32 w, const Color& c);
|
||||
static Shape* roundRect(const Vec2& sz, f32 r, const Color& c);
|
||||
static Shape* poly(const std::vector<Vec2>& pts, const Color& c);
|
||||
static Shape* star(f32 outerR, f32 innerR, u32 n, const Color& c);
|
||||
static Shape* arc(f32 r, f32 start, f32 sweep, const Color& c);
|
||||
static Shape* sector(f32 r, f32 start, f32 sweep, const Color& c);
|
||||
|
||||
private:
|
||||
void renderRect(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderCircle(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderEllipse(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderLine(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderRoundedRect(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderPoly(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderArc(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderSector(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
void renderStar(Camera* cam, RenderCmdBuffer& cmdBuffer);
|
||||
|
||||
std::vector<Vertex2D> createCircleVertices(f32 cx, f32 cy, f32 r, u32 segments, const Color& c);
|
||||
std::vector<u32> createCircleIndices(u32 baseIndex, u32 segments);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/node/renderer.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
struct TextureAsset;
|
||||
|
||||
class Sprite : public Renderer {
|
||||
public:
|
||||
const char* type() const override { return "Sprite"; }
|
||||
|
||||
void render(Camera* cam, RenderCmdBuffer& cmdBuffer) override;
|
||||
BoundingBox bounds() const override;
|
||||
|
||||
AssetHandle<TextureAsset> tex;
|
||||
Color color = Colors::White;
|
||||
Vec2 size = Vec2(64, 64);
|
||||
Vec2 uv0 = Vec2(0, 0);
|
||||
Vec2 uv1 = Vec2(1, 1);
|
||||
Vec2 anchor = Vec2(0.5f, 0.5f);
|
||||
bool flipX = false;
|
||||
bool flipY = false;
|
||||
|
||||
void setAnchor(f32 x, f32 y) { anchor = Vec2(x, y); }
|
||||
void setAnchor(const Vec2& a) { anchor = a; }
|
||||
void setSize(f32 w, f32 h) { size = Vec2(w, h); }
|
||||
void setColor(const Color& c) { color = c; }
|
||||
|
||||
static Sprite* create(const Vec2& sz, const Color& c = Colors::White);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/node/renderer.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
struct FontAsset;
|
||||
|
||||
enum class TextAlignment {
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
};
|
||||
|
||||
enum class TextVAlignment {
|
||||
Top,
|
||||
Middle,
|
||||
Bottom
|
||||
};
|
||||
|
||||
class Text : public Renderer {
|
||||
public:
|
||||
const char* type() const override { return "Text"; }
|
||||
|
||||
void render(Camera* cam, RenderCmdBuffer& cmdBuffer) override;
|
||||
BoundingBox bounds() const override;
|
||||
|
||||
AssetHandle<FontAsset> font;
|
||||
std::string str;
|
||||
f32 fontSize = 16.0f;
|
||||
Color color = Colors::White;
|
||||
Vec2 anchor = Vec2(0, 0);
|
||||
TextAlignment alignment = TextAlignment::Left;
|
||||
TextVAlignment vAlignment = TextVAlignment::Top;
|
||||
f32 lineHeight = 1.2f;
|
||||
f32 letterSpacing = 0.0f;
|
||||
bool wordWrap = false;
|
||||
f32 maxWidth = 0.0f;
|
||||
|
||||
void setAnchor(f32 x, f32 y) { anchor = Vec2(x, y); }
|
||||
void setAnchor(const Vec2& a) { anchor = a; }
|
||||
void setString(const std::string& s) { str = s; }
|
||||
void setFontSize(f32 s) { fontSize = s; }
|
||||
void setColor(const Color& c) { color = c; }
|
||||
|
||||
Vec2 measureText() const;
|
||||
Vec2 measureText(const std::string& text) const;
|
||||
|
||||
static Text* create(const std::string& text, f32 size, const Color& c = Colors::White);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/render/render_svc.h>
|
||||
#include <extra2d/render/backend/vulkan/vk_mem.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
struct SwapchainSupport {
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
std::vector<VkSurfaceFormatKHR> formats;
|
||||
std::vector<VkPresentModeKHR> presentModes;
|
||||
};
|
||||
|
||||
class VkBackend {
|
||||
public:
|
||||
VkBackend() = default;
|
||||
~VkBackend() { shutdown(); }
|
||||
|
||||
bool init(VkInstance instance, VkPhysicalDevice physDevice, VkDevice device);
|
||||
void shutdown();
|
||||
|
||||
bool createSwapchain(VkSurfaceKHR surface, u32 width, u32 height);
|
||||
void destroySwapchain();
|
||||
bool resizeSwapchain(u32 width, u32 height);
|
||||
|
||||
VkSwapchainKHR swapchain() const { return swapchain_; }
|
||||
VkFormat swapchainFormat() const { return swapchainFormat_; }
|
||||
VkExtent2D swapchainExtent() const { return swapchainExtent_; }
|
||||
const std::vector<VkImage>& swapchainImages() const { return swapchainImages_; }
|
||||
const std::vector<VkImageView>& swapchainViews() const { return swapchainViews_; }
|
||||
|
||||
u32 acquireNextImage(VkSemaphore semaphore, VkFence fence);
|
||||
void present(u32 imageIndex, VkSemaphore waitSemaphore);
|
||||
|
||||
VkCommandBuffer beginCmd();
|
||||
void endCmd(VkCommandBuffer cmd);
|
||||
void submitCmd(VkCommandBuffer cmd, VkSemaphore wait, VkSemaphore signal, VkFence fence);
|
||||
|
||||
void waitIdle();
|
||||
|
||||
VkDevice device() const { return device_; }
|
||||
VkPhysicalDevice physDevice() const { return physDevice_; }
|
||||
VkInstance instance() const { return instance_; }
|
||||
|
||||
VkMemAlloc* memAlloc() { return &memAlloc_; }
|
||||
|
||||
VkCommandPool cmdPool() const { return cmdPool_; }
|
||||
|
||||
private:
|
||||
VkInstance instance_ = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physDevice_ = VK_NULL_HANDLE;
|
||||
VkDevice device_ = VK_NULL_HANDLE;
|
||||
|
||||
VkSurfaceKHR surface_ = VK_NULL_HANDLE;
|
||||
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
||||
VkFormat swapchainFormat_ = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D swapchainExtent_ = {0, 0};
|
||||
std::vector<VkImage> swapchainImages_;
|
||||
std::vector<VkImageView> swapchainViews_;
|
||||
|
||||
VkCommandPool cmdPool_ = VK_NULL_HANDLE;
|
||||
std::vector<VkCommandBuffer> cmdBuffers_;
|
||||
|
||||
VkMemAlloc memAlloc_;
|
||||
|
||||
u32 graphicsFamily_ = 0;
|
||||
u32 presentFamily_ = 0;
|
||||
u32 frameIdx_ = 0;
|
||||
|
||||
SwapchainSupport querySwapchainSupport(VkPhysicalDevice device);
|
||||
VkSurfaceFormatKHR chooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats);
|
||||
VkPresentModeKHR choosePresentMode(const std::vector<VkPresentModeKHR>& modes);
|
||||
VkExtent2D chooseExtent(const VkSurfaceCapabilitiesKHR& caps, u32 width, u32 height);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vma/vk_mem_alloc.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
enum class MemType {
|
||||
Gpu,
|
||||
Upload,
|
||||
Readback
|
||||
};
|
||||
|
||||
class VkMemAlloc {
|
||||
public:
|
||||
VkMemAlloc() = default;
|
||||
~VkMemAlloc() { shutdown(); }
|
||||
|
||||
bool init(VkInstance inst, VkDevice device, VkPhysicalDevice physDevice);
|
||||
void shutdown();
|
||||
|
||||
VkBuffer createBuffer(
|
||||
VkDeviceSize size,
|
||||
VkBufferUsageFlags usage,
|
||||
MemType type,
|
||||
VmaAllocation* outAlloc
|
||||
);
|
||||
|
||||
VkImage createImage(
|
||||
const VkImageCreateInfo& info,
|
||||
MemType type,
|
||||
VmaAllocation* outAlloc
|
||||
);
|
||||
|
||||
void destroyBuffer(VkBuffer buf, VmaAllocation alloc);
|
||||
void destroyImage(VkImage img, VmaAllocation alloc);
|
||||
|
||||
void* map(VmaAllocation alloc);
|
||||
void unmap(VmaAllocation alloc);
|
||||
|
||||
void flush(VmaAllocation alloc, VkDeviceSize off = 0, VkDeviceSize size = VK_WHOLE_SIZE);
|
||||
void invalidate(VmaAllocation alloc, VkDeviceSize off = 0, VkDeviceSize size = VK_WHOLE_SIZE);
|
||||
|
||||
void getStats(VmaTotalStatistics* stats);
|
||||
|
||||
VmaAllocator allocator() const { return allocator_; }
|
||||
|
||||
private:
|
||||
VmaAllocator allocator_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
class VkBufferWrap {
|
||||
public:
|
||||
VkBufferWrap() = default;
|
||||
VkBufferWrap(VkMemAlloc* alloc, VkDeviceSize size, VkBufferUsageFlags usage, MemType type);
|
||||
~VkBufferWrap();
|
||||
|
||||
VkBufferWrap(const VkBufferWrap&) = delete;
|
||||
VkBufferWrap& operator=(const VkBufferWrap&) = delete;
|
||||
|
||||
VkBufferWrap(VkBufferWrap&& other) noexcept;
|
||||
VkBufferWrap& operator=(VkBufferWrap&& other) noexcept;
|
||||
|
||||
VkBuffer buffer() const { return buffer_; }
|
||||
VmaAllocation vmaAlloc() const { return vmaAlloc_; }
|
||||
VkDeviceSize size() const { return size_; }
|
||||
|
||||
void* map();
|
||||
void unmap();
|
||||
|
||||
void write(const void* data, VkDeviceSize size, VkDeviceSize off = 0);
|
||||
void read(void* data, VkDeviceSize size, VkDeviceSize off = 0);
|
||||
|
||||
bool valid() const { return buffer_ != VK_NULL_HANDLE; }
|
||||
|
||||
void release();
|
||||
|
||||
private:
|
||||
VkMemAlloc* memAlloc_ = nullptr;
|
||||
VkBuffer buffer_ = VK_NULL_HANDLE;
|
||||
VmaAllocation vmaAlloc_ = VK_NULL_HANDLE;
|
||||
VkDeviceSize size_ = 0;
|
||||
MemType type_ = MemType::Gpu;
|
||||
void* mapped_ = nullptr;
|
||||
};
|
||||
|
||||
class VkImageWrap {
|
||||
public:
|
||||
VkImageWrap() = default;
|
||||
VkImageWrap(VkMemAlloc* alloc, const VkImageCreateInfo& info, MemType type);
|
||||
~VkImageWrap();
|
||||
|
||||
VkImageWrap(const VkImageWrap&) = delete;
|
||||
VkImageWrap& operator=(const VkImageWrap&) = delete;
|
||||
|
||||
VkImageWrap(VkImageWrap&& other) noexcept;
|
||||
VkImageWrap& operator=(VkImageWrap&& other) noexcept;
|
||||
|
||||
VkImage image() const { return image_; }
|
||||
VmaAllocation vmaAlloc() const { return vmaAlloc_; }
|
||||
VkExtent3D extent() const { return extent_; }
|
||||
VkFormat format() const { return format_; }
|
||||
|
||||
bool valid() const { return image_ != VK_NULL_HANDLE; }
|
||||
|
||||
void release();
|
||||
|
||||
private:
|
||||
VkMemAlloc* memAlloc_ = nullptr;
|
||||
VkImage image_ = VK_NULL_HANDLE;
|
||||
VmaAllocation vmaAlloc_ = VK_NULL_HANDLE;
|
||||
VkExtent3D extent_;
|
||||
VkFormat format_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/render/shader_mgr.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class DynShader {
|
||||
public:
|
||||
void add(const std::string& name, const MatShader& shader) {
|
||||
shaders_[name] = shader;
|
||||
}
|
||||
|
||||
void use(const std::string& name) {
|
||||
if (shaders_.find(name) != shaders_.end()) {
|
||||
prev_ = cur_;
|
||||
cur_ = name;
|
||||
blend_ = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
const MatShader* cur() const {
|
||||
auto it = shaders_.find(cur_);
|
||||
if (it != shaders_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MatShader* prev() const {
|
||||
auto it = shaders_.find(prev_);
|
||||
if (it != shaders_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void fadeTo(const std::string& name, f32 duration) {
|
||||
if (shaders_.find(name) != shaders_.end()) {
|
||||
prev_ = cur_;
|
||||
cur_ = name;
|
||||
blend_ = 0.0f;
|
||||
blendSpeed_ = 1.0f / duration;
|
||||
}
|
||||
}
|
||||
|
||||
void update(f32 dt) {
|
||||
if (blend_ < 1.0f) {
|
||||
blend_ += blendSpeed_ * dt;
|
||||
if (blend_ >= 1.0f) {
|
||||
blend_ = 1.0f;
|
||||
prev_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 blend() const { return blend_; }
|
||||
|
||||
bool transitioning() const { return blend_ < 1.0f; }
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, MatShader> shaders_;
|
||||
std::string cur_;
|
||||
std::string prev_;
|
||||
f32 blend_ = 1.0f;
|
||||
f32 blendSpeed_ = 0.0f;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/render/render_svc.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/render/shader_mgr.h>
|
||||
#include <extra2d/render/shader_sdf.h>
|
||||
#include <extra2d/render/dyn_shader.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
struct RenderModCfg {
|
||||
bool vsync = true;
|
||||
bool shaderHotReload = true;
|
||||
u32 maxFramesInFlight = 2;
|
||||
};
|
||||
|
||||
class RenderMod : public Module {
|
||||
public:
|
||||
RenderMod() = default;
|
||||
~RenderMod() override = default;
|
||||
|
||||
bool init() override { return true; }
|
||||
void shutdown() override {}
|
||||
bool ok() const override { return true; }
|
||||
const char* name() const override { return "RenderMod"; }
|
||||
|
||||
void configure(const RenderModCfg& cfg) { cfg_ = cfg; }
|
||||
const RenderModCfg& config() const { return cfg_; }
|
||||
|
||||
void update(f32 dt) {
|
||||
if (cfg_.shaderHotReload) {
|
||||
ShaderMgr::get().checkFileChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RenderModCfg cfg_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Camera;
|
||||
class ShaderMgr;
|
||||
class GPURes;
|
||||
|
||||
struct RenderCtx {
|
||||
Camera* cam = nullptr;
|
||||
Mat4 vp;
|
||||
Frustum frustum;
|
||||
Vec2 viewport;
|
||||
u32 frameIdx = 0;
|
||||
f32 dt = 0.0f;
|
||||
};
|
||||
|
||||
class IRenderSvc : public IService {
|
||||
public:
|
||||
virtual TargetHandle createTarget(const TargetDesc& d) = 0;
|
||||
virtual PipeHandle createPipe(const PipeDesc& d) = 0;
|
||||
virtual DescSetLayoutHandle createDescSetLayout(const DescSetLayoutDesc& d) = 0;
|
||||
|
||||
virtual void begin() = 0;
|
||||
virtual void end() = 0;
|
||||
virtual void wait() = 0;
|
||||
|
||||
virtual GPURes* getRes(ResHandle h) = 0;
|
||||
virtual ShaderMgr* shaderMgr() = 0;
|
||||
|
||||
virtual void submit(RenderCmdBuffer& cmdBuffer) = 0;
|
||||
|
||||
virtual void setViewport(f32 x, f32 y, f32 w, f32 h) = 0;
|
||||
virtual void setScissor(f32 x, f32 y, f32 w, f32 h) = 0;
|
||||
|
||||
virtual Vec2 viewportSize() const = 0;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "RenderSvc";
|
||||
i.priority = ServicePriority::Resource;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
using ResHandle = u64;
|
||||
constexpr ResHandle INVALID_HANDLE = 0;
|
||||
|
||||
using TargetHandle = ResHandle;
|
||||
using PipeHandle = ResHandle;
|
||||
using DescSetLayoutHandle = ResHandle;
|
||||
|
||||
struct Vertex2D {
|
||||
Vec2 pos;
|
||||
Vec2 uv;
|
||||
Color color;
|
||||
u32 texId;
|
||||
|
||||
Vertex2D() : pos(0, 0), uv(0, 0), color(Colors::White), texId(0) {}
|
||||
Vertex2D(const Vec2& p, const Vec2& u, const Color& c, u32 t = 0)
|
||||
: pos(p), uv(u), color(c), texId(t) {}
|
||||
};
|
||||
|
||||
struct RenderCmd {
|
||||
enum class Type {
|
||||
DrawQuad,
|
||||
DrawIndexed,
|
||||
DrawInstanced,
|
||||
SetPipeline,
|
||||
SetTexture,
|
||||
SetUniform,
|
||||
PushConstants,
|
||||
SetScissor,
|
||||
SetViewport
|
||||
};
|
||||
|
||||
Type type;
|
||||
ResHandle pipeline;
|
||||
ResHandle texture;
|
||||
u32 vertexOffset;
|
||||
u32 vertexCount;
|
||||
u32 indexOffset;
|
||||
u32 indexCount;
|
||||
u32 instanceCount;
|
||||
Rect scissor;
|
||||
Rect viewport;
|
||||
std::vector<u8> uniformData;
|
||||
};
|
||||
|
||||
class RenderCmdBuffer {
|
||||
public:
|
||||
void clear() {
|
||||
cmds_.clear();
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
}
|
||||
|
||||
void addCmd(const RenderCmd& cmd) {
|
||||
cmds_.push_back(cmd);
|
||||
}
|
||||
|
||||
u32 addVertices(const std::vector<Vertex2D>& verts) {
|
||||
u32 offset = static_cast<u32>(vertices_.size());
|
||||
vertices_.insert(vertices_.end(), verts.begin(), verts.end());
|
||||
return offset;
|
||||
}
|
||||
|
||||
u32 addIndices(const std::vector<u32>& indices) {
|
||||
u32 offset = static_cast<u32>(indices_.size());
|
||||
indices_.insert(indices_.end(), indices.begin(), indices.end());
|
||||
return offset;
|
||||
}
|
||||
|
||||
const std::vector<RenderCmd>& cmds() const { return cmds_; }
|
||||
const std::vector<Vertex2D>& vertices() const { return vertices_; }
|
||||
const std::vector<u32>& indices() const { return indices_; }
|
||||
|
||||
std::vector<RenderCmd>& cmds() { return cmds_; }
|
||||
std::vector<Vertex2D>& vertices() { return vertices_; }
|
||||
std::vector<u32>& indices() { return indices_; }
|
||||
|
||||
private:
|
||||
std::vector<RenderCmd> cmds_;
|
||||
std::vector<Vertex2D> vertices_;
|
||||
std::vector<u32> indices_;
|
||||
};
|
||||
|
||||
struct TargetDesc {
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 layers = 1;
|
||||
u32 mipLevels = 1;
|
||||
u32 sampleCount = 1;
|
||||
bool hasDepth = false;
|
||||
bool hasStencil = false;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct PipeDesc {
|
||||
std::string name;
|
||||
std::string vertShader;
|
||||
std::string fragShader;
|
||||
std::string geomShader;
|
||||
bool blending = true;
|
||||
bool depthTest = false;
|
||||
bool depthWrite = false;
|
||||
};
|
||||
|
||||
struct DescSetLayoutDesc {
|
||||
u32 bindingCount = 0;
|
||||
std::vector<u32> bindingIndices;
|
||||
std::vector<u32> descriptorCounts;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
enum class ShaderType {
|
||||
Vert,
|
||||
Frag,
|
||||
Geom,
|
||||
Comp
|
||||
};
|
||||
|
||||
struct ShaderDef {
|
||||
std::string name;
|
||||
ShaderType type;
|
||||
std::string entry = "main";
|
||||
std::vector<u8> spirv;
|
||||
std::string src;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
using ShaderReloadCb = std::function<void(const std::string& name)>;
|
||||
|
||||
class ShaderMgr {
|
||||
public:
|
||||
static ShaderMgr& get() {
|
||||
static ShaderMgr instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool load(const std::string& name, const std::string& path, ShaderType type);
|
||||
|
||||
bool loadFromSrc(const std::string& name, const std::string& src, ShaderType type);
|
||||
|
||||
bool loadFromSpirv(const std::string& name, const std::vector<u8>& spirv, ShaderType type);
|
||||
|
||||
const ShaderDef* get(const std::string& name) const {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool reload(const std::string& name);
|
||||
|
||||
void reloadAll();
|
||||
|
||||
void onReload(ShaderReloadCb cb) { reloadCb_ = cb; }
|
||||
|
||||
void checkFileChanges();
|
||||
|
||||
void setAutoReload(bool enable) { autoReload_ = enable; }
|
||||
|
||||
const std::vector<u8>& getSpirv(const std::string& name) const {
|
||||
static std::vector<u8> empty;
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.spirv;
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
void unload(const std::string& name) {
|
||||
shaders_.erase(name);
|
||||
fileTimes_.erase(name);
|
||||
}
|
||||
|
||||
void unloadAll() {
|
||||
shaders_.clear();
|
||||
fileTimes_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
ShaderMgr() = default;
|
||||
~ShaderMgr() = default;
|
||||
|
||||
std::unordered_map<std::string, ShaderDef> shaders_;
|
||||
std::unordered_map<std::string, std::filesystem::file_time_type> fileTimes_;
|
||||
ShaderReloadCb reloadCb_;
|
||||
bool autoReload_ = true;
|
||||
};
|
||||
|
||||
struct MatShader {
|
||||
std::string vert;
|
||||
std::string frag;
|
||||
std::string geom;
|
||||
|
||||
bool valid() const {
|
||||
return !vert.empty() && !frag.empty();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
namespace sdf {
|
||||
|
||||
enum class Shape {
|
||||
Circle,
|
||||
Rect,
|
||||
RoundedRect,
|
||||
Triangle,
|
||||
Polygon,
|
||||
Custom
|
||||
};
|
||||
|
||||
struct Mat {
|
||||
Shape shape = Shape::Circle;
|
||||
Vec2 size = Vec2(1, 1);
|
||||
f32 radius = 0.5f;
|
||||
f32 edgeSoftness = 0.01f;
|
||||
f32 outlineWidth = 0.0f;
|
||||
Color outlineColor = Colors::Black;
|
||||
bool antiAlias = true;
|
||||
std::string customSdfFunc;
|
||||
};
|
||||
|
||||
std::string genFragSrc(const Mat& mat);
|
||||
|
||||
extern const char* kCircleSdf;
|
||||
extern const char* kRectSdf;
|
||||
extern const char* kRoundedRectSdf;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <stack>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Trans;
|
||||
|
||||
class IDirector : public IService {
|
||||
public:
|
||||
virtual void run(Ref<Scene> s) = 0;
|
||||
virtual void replace(Ref<Scene> s) = 0;
|
||||
virtual void push(Ref<Scene> s) = 0;
|
||||
virtual void pop() = 0;
|
||||
virtual void popToRoot() = 0;
|
||||
virtual void popTo(const std::string& n) = 0;
|
||||
|
||||
virtual void replaceWith(Ref<Scene> s, Ref<Scene> t) = 0;
|
||||
virtual void pushWith(Ref<Scene> s, Ref<Scene> t) = 0;
|
||||
|
||||
virtual Scene* cur() const = 0;
|
||||
virtual Scene* next() const = 0;
|
||||
virtual bool transing() const = 0;
|
||||
|
||||
virtual void update(f32 dt) = 0;
|
||||
virtual void render(RenderCmdBuffer& cmdBuffer) = 0;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "Director";
|
||||
i.priority = ServicePriority::Scene;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
class Director : public IDirector {
|
||||
public:
|
||||
bool init() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
sceneStack_.clear();
|
||||
curScene_.reset();
|
||||
nextScene_.reset();
|
||||
transScene_.reset();
|
||||
}
|
||||
|
||||
void run(Ref<Scene> s) override {
|
||||
if (curScene_) {
|
||||
curScene_->onExit();
|
||||
curScene_->setRunning(false);
|
||||
}
|
||||
sceneStack_.clear();
|
||||
curScene_ = s;
|
||||
if (curScene_) {
|
||||
curScene_->onEnter();
|
||||
curScene_->setRunning(true);
|
||||
}
|
||||
}
|
||||
|
||||
void replace(Ref<Scene> s) override {
|
||||
if (curScene_) {
|
||||
curScene_->onExit();
|
||||
curScene_->setRunning(false);
|
||||
}
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.pop_back();
|
||||
}
|
||||
curScene_ = s;
|
||||
if (curScene_) {
|
||||
curScene_->onEnter();
|
||||
curScene_->setRunning(true);
|
||||
}
|
||||
}
|
||||
|
||||
void push(Ref<Scene> s) override {
|
||||
if (curScene_) {
|
||||
curScene_->onPause();
|
||||
curScene_->setRunning(false);
|
||||
sceneStack_.push_back(curScene_);
|
||||
}
|
||||
curScene_ = s;
|
||||
if (curScene_) {
|
||||
curScene_->onEnter();
|
||||
curScene_->setRunning(true);
|
||||
}
|
||||
}
|
||||
|
||||
void pop() override {
|
||||
if (curScene_) {
|
||||
curScene_->onExit();
|
||||
curScene_->setRunning(false);
|
||||
}
|
||||
if (!sceneStack_.empty()) {
|
||||
curScene_ = sceneStack_.back();
|
||||
sceneStack_.pop_back();
|
||||
if (curScene_) {
|
||||
curScene_->onResume();
|
||||
curScene_->setRunning(true);
|
||||
}
|
||||
} else {
|
||||
curScene_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void popToRoot() override {
|
||||
while (sceneStack_.size() > 1) {
|
||||
if (curScene_) {
|
||||
curScene_->onExit();
|
||||
}
|
||||
sceneStack_.pop_back();
|
||||
}
|
||||
pop();
|
||||
}
|
||||
|
||||
void popTo(const std::string& n) override {
|
||||
while (!sceneStack_.empty()) {
|
||||
if (curScene_ && curScene_->name() == n) {
|
||||
return;
|
||||
}
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
void replaceWith(Ref<Scene> s, Ref<Scene> t) override {
|
||||
transScene_ = t;
|
||||
nextScene_ = s;
|
||||
}
|
||||
|
||||
void pushWith(Ref<Scene> s, Ref<Scene> t) override {
|
||||
transScene_ = t;
|
||||
nextScene_ = s;
|
||||
isPush_ = true;
|
||||
}
|
||||
|
||||
Scene* cur() const override { return curScene_.get(); }
|
||||
Scene* next() const override { return nextScene_.get(); }
|
||||
bool transing() const override { return transScene_ != nullptr; }
|
||||
|
||||
void update(f32 dt) override {
|
||||
if (transScene_) {
|
||||
transScene_->update(dt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (curScene_) {
|
||||
curScene_->update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void render(RenderCmdBuffer& cmdBuffer) override {
|
||||
if (transScene_) {
|
||||
transScene_->render(cmdBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (curScene_) {
|
||||
curScene_->render(cmdBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Ref<Scene>> sceneStack_;
|
||||
Ref<Scene> curScene_;
|
||||
Ref<Scene> nextScene_;
|
||||
Ref<Scene> transScene_;
|
||||
bool isPush_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/node/scene_graph.h>
|
||||
#include <extra2d/cam/cam.h>
|
||||
#include <extra2d/cam/ortho_cam.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Scene {
|
||||
public:
|
||||
virtual ~Scene() = default;
|
||||
|
||||
virtual void onEnter() {}
|
||||
virtual void onExit() {}
|
||||
virtual void onPause() {}
|
||||
virtual void onResume() {}
|
||||
|
||||
virtual void update(f32 dt) {
|
||||
if (graph_) {
|
||||
graph_->update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void render(RenderCmdBuffer& cmdBuffer) {}
|
||||
|
||||
SceneGraph* graph() const { return graph_.get(); }
|
||||
Camera* cam() const { return cam_.get(); }
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
void setName(const std::string& n) { name_ = n; }
|
||||
|
||||
bool running() const { return running_; }
|
||||
void setRunning(bool r) { running_ = r; }
|
||||
|
||||
protected:
|
||||
Unique<SceneGraph> graph_;
|
||||
Unique<Camera> cam_;
|
||||
std::string name_;
|
||||
bool running_ = false;
|
||||
|
||||
Scene() {
|
||||
graph_ = ptr::makeUnique<SceneGraph>();
|
||||
cam_ = ptr::makeUnique<OrthoCam>();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/core/color.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Trans : public Scene {
|
||||
public:
|
||||
virtual ~Trans() = default;
|
||||
|
||||
void onEnter() override {
|
||||
progress_ = 0.0f;
|
||||
}
|
||||
|
||||
void onExit() override {
|
||||
}
|
||||
|
||||
void update(f32 dt) override {
|
||||
progress_ += dt / duration_;
|
||||
if (progress_ >= 1.0f) {
|
||||
progress_ = 1.0f;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void render(RenderCmdBuffer& cmdBuffer) override {
|
||||
draw(progress_);
|
||||
}
|
||||
|
||||
virtual void draw(f32 p) = 0;
|
||||
|
||||
void finish();
|
||||
|
||||
Scene* inScene() const { return inScene_.get(); }
|
||||
Scene* outScene() const { return outScene_.get(); }
|
||||
f32 duration() const { return duration_; }
|
||||
f32 progress() const { return progress_; }
|
||||
|
||||
void init(f32 duration, Ref<Scene> inScene, Ref<Scene> outScene) {
|
||||
duration_ = duration;
|
||||
inScene_ = inScene;
|
||||
outScene_ = outScene;
|
||||
}
|
||||
|
||||
protected:
|
||||
Ref<Scene> inScene_;
|
||||
Ref<Scene> outScene_;
|
||||
f32 duration_ = 0.0f;
|
||||
f32 progress_ = 0.0f;
|
||||
bool inOnTop_ = false;
|
||||
};
|
||||
|
||||
class TransFade : public Trans {
|
||||
public:
|
||||
void init(f32 duration, Ref<Scene> inScene, Ref<Scene> outScene, const Color& color = Colors::Black) {
|
||||
Trans::init(duration, inScene, outScene);
|
||||
color_ = color;
|
||||
}
|
||||
|
||||
void draw(f32 p) override;
|
||||
|
||||
private:
|
||||
Color color_ = Colors::Black;
|
||||
};
|
||||
|
||||
class TransFlipX : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransFlipY : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransMoveInL : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransMoveInR : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransMoveInT : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransMoveInB : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransZoomFlipX : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransZoomFlipY : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransRotoZoom : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
class TransJumpZoom : public Trans {
|
||||
public:
|
||||
void draw(f32 p) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
enum class AdaptMode {
|
||||
Exact,
|
||||
Stretch,
|
||||
Fit,
|
||||
Fill,
|
||||
Center
|
||||
};
|
||||
|
||||
struct WinAdaptCfg {
|
||||
AdaptMode mode = AdaptMode::Fit;
|
||||
f32 refW = 1280;
|
||||
f32 refH = 720;
|
||||
bool autoScale = true;
|
||||
bool autoPos = true;
|
||||
Color bgColor = Colors::Black;
|
||||
};
|
||||
|
||||
class WinAdapt {
|
||||
public:
|
||||
void cfg(const WinAdaptCfg& c) { cfg_ = c; }
|
||||
WinAdaptCfg cfg() const { return cfg_; }
|
||||
|
||||
void ref(f32 w, f32 h) { cfg_.refW = w; cfg_.refH = h; }
|
||||
void ref(const Vec2& s) { ref(s.x, s.y); }
|
||||
Vec2 ref() const { return Vec2(cfg_.refW, cfg_.refH); }
|
||||
|
||||
void win(f32 w, f32 h) {
|
||||
winW_ = w; winH_ = h;
|
||||
update();
|
||||
}
|
||||
void win(const Vec2& s) { win(s.x, s.y); }
|
||||
Vec2 win() const { return Vec2(winW_, winH_); }
|
||||
|
||||
Rect viewport() const { return vp_; }
|
||||
|
||||
f32 scale() const { return scale_; }
|
||||
Vec2 scale2D() const { return Vec2(scaleX_, scaleY_); }
|
||||
|
||||
Vec2 winToRef(const Vec2& p) const {
|
||||
return Vec2(
|
||||
(p.x - vp_.origin.x) / scaleX_,
|
||||
(p.y - vp_.origin.y) / scaleY_
|
||||
);
|
||||
}
|
||||
|
||||
Vec2 refToWin(const Vec2& p) const {
|
||||
return Vec2(
|
||||
p.x * scaleX_ + vp_.origin.x,
|
||||
p.y * scaleY_ + vp_.origin.y
|
||||
);
|
||||
}
|
||||
|
||||
Vec2 screenToRef(const Vec2& s) const {
|
||||
return winToRef(s);
|
||||
}
|
||||
|
||||
Vec2 refToScreen(const Vec2& r) const {
|
||||
return refToWin(r);
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (winW_ <= 0 || winH_ <= 0) return;
|
||||
|
||||
scaleX_ = winW_ / cfg_.refW;
|
||||
scaleY_ = winH_ / cfg_.refH;
|
||||
|
||||
switch (cfg_.mode) {
|
||||
case AdaptMode::Exact:
|
||||
scale_ = 1.0f;
|
||||
scaleX_ = scaleY_ = 1.0f;
|
||||
vp_ = Rect(0, 0, winW_, winH_);
|
||||
break;
|
||||
|
||||
case AdaptMode::Stretch:
|
||||
scale_ = 1.0f;
|
||||
vp_ = Rect(0, 0, winW_, winH_);
|
||||
break;
|
||||
|
||||
case AdaptMode::Fit:
|
||||
scale_ = std::min(scaleX_, scaleY_);
|
||||
scaleX_ = scaleY_ = scale_;
|
||||
{
|
||||
f32 vpW = cfg_.refW * scale_;
|
||||
f32 vpH = cfg_.refH * scale_;
|
||||
f32 vpX = (winW_ - vpW) * 0.5f;
|
||||
f32 vpY = (winH_ - vpH) * 0.5f;
|
||||
vp_ = Rect(vpX, vpY, vpW, vpH);
|
||||
}
|
||||
break;
|
||||
|
||||
case AdaptMode::Fill:
|
||||
scale_ = std::max(scaleX_, scaleY_);
|
||||
scaleX_ = scaleY_ = scale_;
|
||||
{
|
||||
f32 vpW = cfg_.refW * scale_;
|
||||
f32 vpH = cfg_.refH * scale_;
|
||||
f32 vpX = (winW_ - vpW) * 0.5f;
|
||||
f32 vpY = (winH_ - vpH) * 0.5f;
|
||||
vp_ = Rect(vpX, vpY, vpW, vpH);
|
||||
}
|
||||
break;
|
||||
|
||||
case AdaptMode::Center:
|
||||
scale_ = 1.0f;
|
||||
scaleX_ = scaleY_ = 1.0f;
|
||||
{
|
||||
f32 vpX = (winW_ - cfg_.refW) * 0.5f;
|
||||
f32 vpY = (winH_ - cfg_.refH) * 0.5f;
|
||||
vp_ = Rect(vpX, vpY, cfg_.refW, cfg_.refH);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
offset_ = Vec2(vp_.origin.x, vp_.origin.y);
|
||||
}
|
||||
|
||||
void apply(class Camera* cam) const;
|
||||
|
||||
Mat4 matrix() const {
|
||||
Mat4 m = glm::mat4(1.0f);
|
||||
m = glm::translate(m, glm::vec3(offset_.x, offset_.y, 0.0f));
|
||||
m = glm::scale(m, glm::vec3(scale_, scale_, 1.0f));
|
||||
return m;
|
||||
}
|
||||
|
||||
private:
|
||||
WinAdaptCfg cfg_;
|
||||
f32 winW_ = 0, winH_ = 0;
|
||||
f32 scale_ = 1.0f;
|
||||
f32 scaleX_ = 1.0f, scaleY_ = 1.0f;
|
||||
Rect vp_;
|
||||
Vec2 offset_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/win/win_adapt.h>
|
||||
#include <extra2d/cam/cam.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class IWinAdaptSvc : public IService {
|
||||
public:
|
||||
virtual void cfg(const WinAdaptCfg& c) = 0;
|
||||
virtual WinAdaptCfg cfg() const = 0;
|
||||
|
||||
virtual void ref(f32 w, f32 h) = 0;
|
||||
virtual Vec2 ref() const = 0;
|
||||
|
||||
virtual void win(f32 w, f32 h) = 0;
|
||||
virtual Vec2 win() const = 0;
|
||||
|
||||
virtual WinAdapt* adapt() = 0;
|
||||
|
||||
virtual Vec2 toRef(const Vec2& p) = 0;
|
||||
virtual Vec2 toWin(const Vec2& p) = 0;
|
||||
|
||||
virtual void apply(Camera* cam) = 0;
|
||||
|
||||
virtual void onResize(f32 w, f32 h) = 0;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "WinAdaptSvc";
|
||||
i.priority = ServicePriority::Camera;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
class WinAdaptSvc : public IWinAdaptSvc {
|
||||
public:
|
||||
bool init() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
}
|
||||
|
||||
void cfg(const WinAdaptCfg& c) override {
|
||||
adapt_.cfg(c);
|
||||
adapt_.update();
|
||||
}
|
||||
|
||||
WinAdaptCfg cfg() const override {
|
||||
return adapt_.cfg();
|
||||
}
|
||||
|
||||
void ref(f32 w, f32 h) override {
|
||||
adapt_.ref(w, h);
|
||||
adapt_.update();
|
||||
}
|
||||
|
||||
Vec2 ref() const override {
|
||||
return adapt_.ref();
|
||||
}
|
||||
|
||||
void win(f32 w, f32 h) override {
|
||||
adapt_.win(w, h);
|
||||
}
|
||||
|
||||
Vec2 win() const override {
|
||||
return adapt_.win();
|
||||
}
|
||||
|
||||
WinAdapt* adapt() override {
|
||||
return &adapt_;
|
||||
}
|
||||
|
||||
Vec2 toRef(const Vec2& p) override {
|
||||
return adapt_.winToRef(p);
|
||||
}
|
||||
|
||||
Vec2 toWin(const Vec2& p) override {
|
||||
return adapt_.refToWin(p);
|
||||
}
|
||||
|
||||
void apply(Camera* cam) override {
|
||||
adapt_.apply(cam);
|
||||
}
|
||||
|
||||
void onResize(f32 w, f32 h) override {
|
||||
adapt_.win(w, h);
|
||||
}
|
||||
|
||||
private:
|
||||
WinAdapt adapt_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#include "extra2d/act/act.h"
|
||||
#include "extra2d/node/node.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void Act::start(Node* t) {
|
||||
target_ = t;
|
||||
origTarget_ = t;
|
||||
}
|
||||
|
||||
void Act::stop() {
|
||||
target_ = nullptr;
|
||||
}
|
||||
|
||||
void Act::step(f32 dt) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
#include "extra2d/act/act_composite.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Seq::Seq(Act* a1, Act* a2) : ActInterval(0) {
|
||||
if (a1) acts_.push_back(a1);
|
||||
if (a2) acts_.push_back(a2);
|
||||
|
||||
f32 totalDur = 0;
|
||||
for (auto* a : acts_) {
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
totalDur += interval->dur();
|
||||
}
|
||||
}
|
||||
dur_ = totalDur;
|
||||
}
|
||||
|
||||
Seq* Seq::createFromArray(std::initializer_list<Act*> acts) {
|
||||
auto* seq = new Seq(nullptr, nullptr);
|
||||
seq->acts_.clear();
|
||||
|
||||
f32 totalDur = 0;
|
||||
for (auto* a : acts) {
|
||||
if (a) {
|
||||
seq->acts_.push_back(a);
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
totalDur += interval->dur();
|
||||
}
|
||||
}
|
||||
}
|
||||
seq->dur_ = totalDur;
|
||||
return seq;
|
||||
}
|
||||
|
||||
void Seq::start(Node* t) {
|
||||
ActInterval::start(t);
|
||||
curIdx_ = 0;
|
||||
if (!acts_.empty()) {
|
||||
acts_[0]->start(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Seq::stop() {
|
||||
if (curIdx_ < acts_.size() && acts_[curIdx_]) {
|
||||
acts_[curIdx_]->stop();
|
||||
}
|
||||
ActInterval::stop();
|
||||
}
|
||||
|
||||
void Seq::step(f32 dt) {
|
||||
if (acts_.empty()) {
|
||||
elap_ = dur_;
|
||||
return;
|
||||
}
|
||||
|
||||
elap_ += dt;
|
||||
|
||||
while (curIdx_ < acts_.size()) {
|
||||
auto* cur = acts_[curIdx_];
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(cur)) {
|
||||
interval->step(dt);
|
||||
if (!interval->done()) {
|
||||
return;
|
||||
}
|
||||
cur->stop();
|
||||
} else {
|
||||
cur->step(dt);
|
||||
}
|
||||
|
||||
curIdx_++;
|
||||
if (curIdx_ < acts_.size()) {
|
||||
acts_[curIdx_]->start(target_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Seq::done() const {
|
||||
return curIdx_ >= acts_.size();
|
||||
}
|
||||
|
||||
Act* Seq::clone() const {
|
||||
auto* seq = new Seq(nullptr, nullptr);
|
||||
seq->acts_.clear();
|
||||
for (auto* a : acts_) {
|
||||
seq->acts_.push_back(a->clone());
|
||||
}
|
||||
seq->dur_ = dur_;
|
||||
return seq;
|
||||
}
|
||||
|
||||
Act* Seq::reverse() const {
|
||||
auto* seq = new Seq(nullptr, nullptr);
|
||||
seq->acts_.clear();
|
||||
for (auto it = acts_.rbegin(); it != acts_.rend(); ++it) {
|
||||
seq->acts_.push_back((*it)->reverse());
|
||||
}
|
||||
seq->dur_ = dur_;
|
||||
return seq;
|
||||
}
|
||||
|
||||
Spawn::Spawn(Act* a1, Act* a2) : ActInterval(0) {
|
||||
if (a1) acts_.push_back(a1);
|
||||
if (a2) acts_.push_back(a2);
|
||||
|
||||
f32 maxDur = 0;
|
||||
for (auto* a : acts_) {
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
maxDur = std::max(maxDur, interval->dur());
|
||||
}
|
||||
}
|
||||
dur_ = maxDur;
|
||||
}
|
||||
|
||||
Spawn* Spawn::createFromArray(std::initializer_list<Act*> acts) {
|
||||
auto* spawn = new Spawn(nullptr, nullptr);
|
||||
spawn->acts_.clear();
|
||||
|
||||
f32 maxDur = 0;
|
||||
for (auto* a : acts) {
|
||||
if (a) {
|
||||
spawn->acts_.push_back(a);
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
maxDur = std::max(maxDur, interval->dur());
|
||||
}
|
||||
}
|
||||
}
|
||||
spawn->dur_ = maxDur;
|
||||
return spawn;
|
||||
}
|
||||
|
||||
void Spawn::start(Node* t) {
|
||||
ActInterval::start(t);
|
||||
for (auto* a : acts_) {
|
||||
a->start(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Spawn::stop() {
|
||||
for (auto* a : acts_) {
|
||||
a->stop();
|
||||
}
|
||||
ActInterval::stop();
|
||||
}
|
||||
|
||||
void Spawn::update(f32 t) {
|
||||
for (auto* a : acts_) {
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
f32 localT = std::min(t * dur_ / interval->dur(), 1.0f);
|
||||
interval->update(interval->easeTime(localT));
|
||||
} else {
|
||||
a->update(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Act* Spawn::clone() const {
|
||||
auto* spawn = new Spawn(nullptr, nullptr);
|
||||
spawn->acts_.clear();
|
||||
for (auto* a : acts_) {
|
||||
spawn->acts_.push_back(a->clone());
|
||||
}
|
||||
spawn->dur_ = dur_;
|
||||
return spawn;
|
||||
}
|
||||
|
||||
Act* Spawn::reverse() const {
|
||||
auto* spawn = new Spawn(nullptr, nullptr);
|
||||
spawn->acts_.clear();
|
||||
for (auto* a : acts_) {
|
||||
spawn->acts_.push_back(a->reverse());
|
||||
}
|
||||
spawn->dur_ = dur_;
|
||||
return spawn;
|
||||
}
|
||||
|
||||
Repeat::Repeat(Act* a, int times) : ActInterval(0), times_(times) {
|
||||
if (a) {
|
||||
inner_ = a;
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
dur_ = interval->dur() * times;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeat::Repeat(Act* a) : ActInterval(0), times_(0), inf_(true) {
|
||||
if (a) {
|
||||
inner_ = a;
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(a)) {
|
||||
dur_ = interval->dur();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Repeat::done() const {
|
||||
return !inf_ && cnt_ >= times_;
|
||||
}
|
||||
|
||||
void Repeat::start(Node* t) {
|
||||
ActInterval::start(t);
|
||||
cnt_ = 0;
|
||||
if (inner_) {
|
||||
inner_->start(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Repeat::stop() {
|
||||
if (inner_) {
|
||||
inner_->stop();
|
||||
}
|
||||
ActInterval::stop();
|
||||
}
|
||||
|
||||
void Repeat::update(f32 t) {
|
||||
if (!inner_) return;
|
||||
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(inner_)) {
|
||||
f32 localT = t * times_;
|
||||
int newCnt = static_cast<int>(localT);
|
||||
|
||||
if (newCnt > cnt_) {
|
||||
cnt_ = newCnt;
|
||||
inner_->stop();
|
||||
if (!done()) {
|
||||
inner_->start(target_);
|
||||
}
|
||||
}
|
||||
|
||||
f32 innerT = localT - std::floor(localT);
|
||||
interval->update(interval->easeTime(innerT));
|
||||
}
|
||||
}
|
||||
|
||||
Act* Repeat::clone() const {
|
||||
return new Repeat(inner_->clone(), times_);
|
||||
}
|
||||
|
||||
Act* Repeat::reverse() const {
|
||||
return new Repeat(inner_->reverse(), times_);
|
||||
}
|
||||
|
||||
void RepeatForever::start(Node* t) {
|
||||
Act::start(t);
|
||||
elap_ = 0;
|
||||
if (inner_) {
|
||||
inner_->start(t);
|
||||
}
|
||||
}
|
||||
|
||||
void RepeatForever::step(f32 dt) {
|
||||
if (!inner_) return;
|
||||
|
||||
elap_ += dt;
|
||||
|
||||
if (dur_ > 0) {
|
||||
while (elap_ >= dur_) {
|
||||
elap_ -= dur_;
|
||||
inner_->stop();
|
||||
inner_->start(target_);
|
||||
}
|
||||
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(inner_)) {
|
||||
f32 t = elap_ / dur_;
|
||||
interval->update(interval->easeTime(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Speed::start(Node* t) {
|
||||
ActInterval::start(t);
|
||||
if (inner_) {
|
||||
inner_->start(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Speed::stop() {
|
||||
if (inner_) {
|
||||
inner_->stop();
|
||||
}
|
||||
ActInterval::stop();
|
||||
}
|
||||
|
||||
void Speed::step(f32 dt) {
|
||||
if (!inner_) {
|
||||
elap_ = dur_;
|
||||
return;
|
||||
}
|
||||
|
||||
f32 innerDt = dt * speed_;
|
||||
elap_ += dt;
|
||||
|
||||
if (auto* interval = dynamic_cast<ActInterval*>(inner_)) {
|
||||
interval->step(innerDt);
|
||||
} else {
|
||||
inner_->step(innerDt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#include "extra2d/cam/cam_svc.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include "extra2d/node/node.h"
|
||||
#include "extra2d/act/act.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/matrix_transform_2d.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Node::~Node() {
|
||||
for (auto& c : comps_) {
|
||||
c->onDetach();
|
||||
}
|
||||
comps_.clear();
|
||||
|
||||
for (auto& child : children_) {
|
||||
child->parent_ = nullptr;
|
||||
}
|
||||
children_.clear();
|
||||
|
||||
if (parent_) {
|
||||
auto& siblings = parent_->children_;
|
||||
siblings.erase(
|
||||
std::remove(siblings.begin(), siblings.end(), shared_from_this()),
|
||||
siblings.end()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Mat3 Node::local() const {
|
||||
Mat3 m(1.0f);
|
||||
|
||||
m = glm::translate(m, pos.toGlm());
|
||||
|
||||
m = glm::translate(m, glm::vec2(anchor.x * scale.x, anchor.y * scale.y));
|
||||
m = glm::rotate(m, rot);
|
||||
m = glm::translate(m, glm::vec2(-anchor.x * scale.x, -anchor.y * scale.y));
|
||||
|
||||
m = glm::scale(m, scale.toGlm());
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
Mat3 Node::world() const {
|
||||
if (parent_) {
|
||||
return parent_->world() * local();
|
||||
}
|
||||
return local();
|
||||
}
|
||||
|
||||
Mat4 Node::world4x4() const {
|
||||
Mat3 m3 = world();
|
||||
Mat4 m4(1.0f);
|
||||
m4[0][0] = m3[0][0];
|
||||
m4[0][1] = m3[0][1];
|
||||
m4[1][0] = m3[1][0];
|
||||
m4[1][1] = m3[1][1];
|
||||
m4[3][0] = m3[2][0];
|
||||
m4[3][1] = m3[2][1];
|
||||
return m4;
|
||||
}
|
||||
|
||||
Vec2 Node::worldPos() const {
|
||||
if (parent_) {
|
||||
return parent_->worldPos() + pos;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
f32 Node::worldRot() const {
|
||||
if (parent_) {
|
||||
return parent_->worldRot() + rot;
|
||||
}
|
||||
return rot;
|
||||
}
|
||||
|
||||
Vec2 Node::worldScale() const {
|
||||
if (parent_) {
|
||||
Vec2 ps = parent_->worldScale();
|
||||
return Vec2(ps.x * scale.x, ps.y * scale.y);
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
void Node::addInternal(Unique<Comp> c) {
|
||||
c->onAttach(this);
|
||||
comps_.push_back(std::move(c));
|
||||
}
|
||||
|
||||
void Node::updateComps(f32 dt) {
|
||||
for (auto& c : comps_) {
|
||||
if (c->enabled()) {
|
||||
c->update(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::lateUpdateComps(f32 dt) {
|
||||
for (auto& c : comps_) {
|
||||
if (c->enabled()) {
|
||||
c->lateUpdate(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::addChild(Ref<Node> child) {
|
||||
if (child && child.get() != this) {
|
||||
if (child->parent_) {
|
||||
child->removeFromParent();
|
||||
}
|
||||
child->parent_ = this;
|
||||
children_.push_back(child);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeChild(Ref<Node> child) {
|
||||
if (child) {
|
||||
auto it = std::find(children_.begin(), children_.end(), child);
|
||||
if (it != children_.end()) {
|
||||
(*it)->parent_ = nullptr;
|
||||
children_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeFromParent() {
|
||||
if (parent_) {
|
||||
auto& siblings = parent_->children_;
|
||||
auto it = std::find(siblings.begin(), siblings.end(), shared_from_this());
|
||||
if (it != siblings.end()) {
|
||||
siblings.erase(it);
|
||||
}
|
||||
parent_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Act* Node::run(Act* a) {
|
||||
if (!a) return nullptr;
|
||||
a->start(this);
|
||||
acts_.push_back(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
void Node::stop(Act* a) {
|
||||
if (!a) return;
|
||||
auto it = std::find(acts_.begin(), acts_.end(), a);
|
||||
if (it != acts_.end()) {
|
||||
(*it)->stop();
|
||||
acts_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::stopByTag(i32 tag) {
|
||||
for (auto it = acts_.begin(); it != acts_.end(); ) {
|
||||
if ((*it)->tag() == tag) {
|
||||
(*it)->stop();
|
||||
it = acts_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::stopAll() {
|
||||
for (auto* a : acts_) {
|
||||
a->stop();
|
||||
}
|
||||
acts_.clear();
|
||||
}
|
||||
|
||||
Act* Node::getActByTag(i32 tag) {
|
||||
for (auto* a : acts_) {
|
||||
if (a->tag() == tag) {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
i32 Node::runningActs() const {
|
||||
return static_cast<i32>(acts_.size());
|
||||
}
|
||||
|
||||
void Node::updateActs(f32 dt) {
|
||||
for (auto it = acts_.begin(); it != acts_.end(); ) {
|
||||
Act* a = *it;
|
||||
a->step(dt);
|
||||
if (a->done()) {
|
||||
a->stop();
|
||||
it = acts_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
#include "extra2d/node/scene_graph.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
SceneGraph::SceneGraph() = default;
|
||||
|
||||
SceneGraph::~SceneGraph() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void SceneGraph::add(Ref<Node> node) {
|
||||
if (node) {
|
||||
nodes_.push_back(node);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneGraph::remove(Ref<Node> node) {
|
||||
if (node) {
|
||||
auto it = std::find(nodes_.begin(), nodes_.end(), node);
|
||||
if (it != nodes_.end()) {
|
||||
nodes_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneGraph::clear() {
|
||||
nodes_.clear();
|
||||
}
|
||||
|
||||
Node* SceneGraph::find(const std::string& name) const {
|
||||
for (const auto& node : nodes_) {
|
||||
if (node->name() == name) {
|
||||
return node.get();
|
||||
}
|
||||
for (auto& child : node->children()) {
|
||||
if (child->name() == name) {
|
||||
return child.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node* SceneGraph::findByTag(i32 tag) const {
|
||||
for (const auto& node : nodes_) {
|
||||
if (node->tag() == tag) {
|
||||
return node.get();
|
||||
}
|
||||
for (auto& child : node->children()) {
|
||||
if (child->tag() == tag) {
|
||||
return child.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SceneGraph::traverse(const std::function<void(Node*)>& callback) {
|
||||
for (auto& node : nodes_) {
|
||||
traverseRecursive(node.get(), callback);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneGraph::traverseRecursive(Node* node, const std::function<void(Node*)>& callback) {
|
||||
if (!node) return;
|
||||
|
||||
callback(node);
|
||||
|
||||
for (auto& child : node->children()) {
|
||||
traverseRecursive(child.get(), callback);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneGraph::update(f32 dt) {
|
||||
traverse([dt](Node* node) {
|
||||
node->onUpdate(dt);
|
||||
node->updateComps(dt);
|
||||
node->lateUpdateComps(dt);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,574 @@
|
|||
#include "extra2d/node/shape.h"
|
||||
#include "extra2d/cam/cam.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void Shape::render(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
if (!visible || !enabled()) return;
|
||||
|
||||
switch (shapeType) {
|
||||
case ShapeType::Rect:
|
||||
renderRect(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Circle:
|
||||
renderCircle(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Ellipse:
|
||||
renderEllipse(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Line:
|
||||
renderLine(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::RoundedRect:
|
||||
renderRoundedRect(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Poly:
|
||||
renderPoly(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Arc:
|
||||
renderArc(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Sector:
|
||||
renderSector(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Star:
|
||||
renderStar(cam, cmdBuffer);
|
||||
break;
|
||||
case ShapeType::Custom:
|
||||
renderPoly(cam, cmdBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BoundingBox Shape::bounds() const {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
switch (shapeType) {
|
||||
case ShapeType::Rect:
|
||||
case ShapeType::RoundedRect:
|
||||
return BoundingBox::fromCenter(worldPos, size * 0.5f);
|
||||
case ShapeType::Circle:
|
||||
return BoundingBox::fromCenter(worldPos, Vec2(radius, radius));
|
||||
case ShapeType::Ellipse:
|
||||
return BoundingBox::fromCenter(worldPos, size * 0.5f);
|
||||
case ShapeType::Line:
|
||||
return BoundingBox::fromSize(start, end - start);
|
||||
case ShapeType::Star:
|
||||
return BoundingBox::fromCenter(worldPos, Vec2(radius, radius));
|
||||
case ShapeType::Arc:
|
||||
case ShapeType::Sector:
|
||||
return BoundingBox::fromCenter(worldPos, Vec2(radius, radius));
|
||||
default:
|
||||
return BoundingBox::fromCenter(worldPos, size * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
void Shape::renderRect(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
Vec2 offset(-anchor.x * size.x, -anchor.y * size.y);
|
||||
|
||||
Vec2 p0 = worldPos + offset;
|
||||
Vec2 p1 = p0 + Vec2(size.x, 0);
|
||||
Vec2 p2 = p0 + size;
|
||||
Vec2 p3 = p0 + Vec2(0, size.y);
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
p0 = Vec2::fromGlm(transform * glm::vec3(p0.toGlm(), 1.0f));
|
||||
p1 = Vec2::fromGlm(transform * glm::vec3(p1.toGlm(), 1.0f));
|
||||
p2 = Vec2::fromGlm(transform * glm::vec3(p2.toGlm(), 1.0f));
|
||||
p3 = Vec2::fromGlm(transform * glm::vec3(p3.toGlm(), 1.0f));
|
||||
}
|
||||
|
||||
Color finalColor = applyOpacity(fillColor);
|
||||
|
||||
std::vector<Vertex2D> verts = {
|
||||
Vertex2D(p0, Vec2(0, 0), finalColor, 0),
|
||||
Vertex2D(p1, Vec2(1, 0), finalColor, 0),
|
||||
Vertex2D(p2, Vec2(1, 1), finalColor, 0),
|
||||
Vertex2D(p3, Vec2(0, 1), finalColor, 0)
|
||||
};
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = 4;
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = 6;
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderCircle(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
const u32 segments = 32;
|
||||
auto verts = createCircleVertices(worldPos.x, worldPos.y, radius, segments, applyOpacity(fillColor));
|
||||
auto indices = createCircleIndices(0, segments);
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderEllipse(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
const u32 segments = 32;
|
||||
std::vector<Vertex2D> verts;
|
||||
verts.push_back(Vertex2D(worldPos, Vec2(0.5f, 0.5f), applyOpacity(fillColor), 0));
|
||||
|
||||
for (u32 i = 0; i <= segments; ++i) {
|
||||
f32 angle = 2.0f * 3.14159f * i / segments;
|
||||
f32 x = worldPos.x + (size.x * 0.5f) * std::cos(angle);
|
||||
f32 y = worldPos.y + (size.y * 0.5f) * std::sin(angle);
|
||||
verts.push_back(Vertex2D(Vec2(x, y), Vec2(0, 0), applyOpacity(fillColor), 0));
|
||||
}
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
for (u32 i = 1; i <= segments; ++i) {
|
||||
indices.push_back(0);
|
||||
indices.push_back(i);
|
||||
indices.push_back(i + 1 <= segments ? i + 1 : 1);
|
||||
}
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderLine(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
Vec2 dir = (end - start).normalized();
|
||||
Vec2 perp(-dir.y, dir.x);
|
||||
Vec2 halfWidth = perp * (lineWidth * 0.5f);
|
||||
|
||||
Vec2 p0 = worldPos + start + halfWidth;
|
||||
Vec2 p1 = worldPos + start - halfWidth;
|
||||
Vec2 p2 = worldPos + end - halfWidth;
|
||||
Vec2 p3 = worldPos + end + halfWidth;
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
p0 = Vec2::fromGlm(transform * glm::vec3(p0.toGlm(), 1.0f));
|
||||
p1 = Vec2::fromGlm(transform * glm::vec3(p1.toGlm(), 1.0f));
|
||||
p2 = Vec2::fromGlm(transform * glm::vec3(p2.toGlm(), 1.0f));
|
||||
p3 = Vec2::fromGlm(transform * glm::vec3(p3.toGlm(), 1.0f));
|
||||
}
|
||||
|
||||
Color finalColor = applyOpacity(strokeColor);
|
||||
|
||||
std::vector<Vertex2D> verts = {
|
||||
Vertex2D(p0, Vec2(0, 0), finalColor, 0),
|
||||
Vertex2D(p1, Vec2(0, 1), finalColor, 0),
|
||||
Vertex2D(p2, Vec2(1, 1), finalColor, 0),
|
||||
Vertex2D(p3, Vec2(1, 0), finalColor, 0)
|
||||
};
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = 4;
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = 6;
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderRoundedRect(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
Vec2 offset(-anchor.x * size.x, -anchor.y * size.y);
|
||||
|
||||
Vec2 basePos = worldPos + offset;
|
||||
f32 r = std::min(cornerRadius, std::min(size.x, size.y) * 0.5f);
|
||||
|
||||
std::vector<Vertex2D> verts;
|
||||
verts.push_back(Vertex2D(worldPos, Vec2(0.5f, 0.5f), applyOpacity(fillColor), 0));
|
||||
|
||||
const u32 cornerSegments = 8;
|
||||
|
||||
auto addCorner = [&](Vec2 center, f32 startAngle, f32 endAngle) {
|
||||
for (u32 i = 0; i <= cornerSegments; ++i) {
|
||||
f32 angle = startAngle + (endAngle - startAngle) * i / cornerSegments;
|
||||
f32 x = center.x + r * std::cos(angle);
|
||||
f32 y = center.y + r * std::sin(angle);
|
||||
verts.push_back(Vertex2D(Vec2(x, y), Vec2(0, 0), applyOpacity(fillColor), 0));
|
||||
}
|
||||
};
|
||||
|
||||
addCorner(Vec2(basePos.x + size.x - r, basePos.y + r), 3.14159f * 1.5f, 3.14159f * 2.0f);
|
||||
addCorner(Vec2(basePos.x + size.x - r, basePos.y + size.y - r), 0, 3.14159f * 0.5f);
|
||||
addCorner(Vec2(basePos.x + r, basePos.y + size.y - r), 3.14159f * 0.5f, 3.14159f);
|
||||
addCorner(Vec2(basePos.x + r, basePos.y + r), 3.14159f, 3.14159f * 1.5f);
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
u32 totalPoints = static_cast<u32>(verts.size()) - 1;
|
||||
for (u32 i = 1; i < totalPoints; ++i) {
|
||||
indices.push_back(0);
|
||||
indices.push_back(i);
|
||||
indices.push_back(i + 1);
|
||||
}
|
||||
indices.push_back(0);
|
||||
indices.push_back(totalPoints);
|
||||
indices.push_back(1);
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderPoly(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
if (points.empty()) return;
|
||||
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
std::vector<Vertex2D> verts;
|
||||
Vec2 center(0, 0);
|
||||
for (const auto& p : points) {
|
||||
center += p;
|
||||
}
|
||||
center /= static_cast<f32>(points.size());
|
||||
verts.push_back(Vertex2D(worldPos + center, Vec2(0.5f, 0.5f), applyOpacity(fillColor), 0));
|
||||
|
||||
for (const auto& p : points) {
|
||||
verts.push_back(Vertex2D(worldPos + p, Vec2(0, 0), applyOpacity(fillColor), 0));
|
||||
}
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
u32 n = static_cast<u32>(points.size());
|
||||
for (u32 i = 0; i < n; ++i) {
|
||||
indices.push_back(0);
|
||||
indices.push_back(i + 1);
|
||||
indices.push_back((i + 1) % n + 1);
|
||||
}
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderArc(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
const u32 segments = 32;
|
||||
std::vector<Vertex2D> verts;
|
||||
|
||||
Vec2 innerOffset(-anchor.x * radius * 2, -anchor.y * radius * 2);
|
||||
Vec2 center = worldPos + Vec2(radius, radius) + innerOffset;
|
||||
|
||||
for (u32 i = 0; i <= segments; ++i) {
|
||||
f32 angle = startAngle + sweepAngle * i / segments;
|
||||
f32 x = center.x + radius * std::cos(angle);
|
||||
f32 y = center.y + radius * std::sin(angle);
|
||||
verts.push_back(Vertex2D(Vec2(x, y), Vec2(0, 0), applyOpacity(strokeColor), 0));
|
||||
}
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
for (u32 i = 0; i < segments; ++i) {
|
||||
indices.push_back(i);
|
||||
indices.push_back(i + 1);
|
||||
}
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderSector(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
const u32 segments = 32;
|
||||
std::vector<Vertex2D> verts;
|
||||
|
||||
Vec2 innerOffset(-anchor.x * radius * 2, -anchor.y * radius * 2);
|
||||
Vec2 center = worldPos + Vec2(radius, radius) + innerOffset;
|
||||
|
||||
verts.push_back(Vertex2D(center, Vec2(0.5f, 0.5f), applyOpacity(fillColor), 0));
|
||||
|
||||
for (u32 i = 0; i <= segments; ++i) {
|
||||
f32 angle = startAngle + sweepAngle * i / segments;
|
||||
f32 x = center.x + radius * std::cos(angle);
|
||||
f32 y = center.y + radius * std::sin(angle);
|
||||
verts.push_back(Vertex2D(Vec2(x, y), Vec2(0, 0), applyOpacity(fillColor), 0));
|
||||
}
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
for (u32 i = 1; i <= segments; ++i) {
|
||||
indices.push_back(0);
|
||||
indices.push_back(i);
|
||||
indices.push_back(i + 1 <= segments ? i + 1 : 1);
|
||||
}
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
void Shape::renderStar(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
std::vector<Vertex2D> verts;
|
||||
verts.push_back(Vertex2D(worldPos, Vec2(0.5f, 0.5f), applyOpacity(fillColor), 0));
|
||||
|
||||
f32 inner = radius * innerRadius;
|
||||
for (u32 i = 0; i < sides * 2; ++i) {
|
||||
f32 angle = 3.14159f * i / sides - 3.14159f * 0.5f;
|
||||
f32 r = (i % 2 == 0) ? radius : inner;
|
||||
f32 x = worldPos.x + r * std::cos(angle);
|
||||
f32 y = worldPos.y + r * std::sin(angle);
|
||||
verts.push_back(Vertex2D(Vec2(x, y), Vec2(0, 0), applyOpacity(fillColor), 0));
|
||||
}
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
for (auto& v : verts) {
|
||||
Vec2 transformed = Vec2::fromGlm(transform * glm::vec3(v.pos.toGlm(), 1.0f));
|
||||
v.pos = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
u32 n = sides * 2;
|
||||
for (u32 i = 0; i < n; ++i) {
|
||||
indices.push_back(0);
|
||||
indices.push_back(i + 1);
|
||||
indices.push_back((i + 1) % n + 1);
|
||||
}
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
for (auto& i : indices) i += vertOffset;
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = static_cast<u32>(verts.size());
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = static_cast<u32>(indices.size());
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
std::vector<Vertex2D> Shape::createCircleVertices(f32 cx, f32 cy, f32 r, u32 segments, const Color& c) {
|
||||
std::vector<Vertex2D> verts;
|
||||
verts.push_back(Vertex2D(Vec2(cx, cy), Vec2(0.5f, 0.5f), c, 0));
|
||||
|
||||
for (u32 i = 0; i <= segments; ++i) {
|
||||
f32 angle = 2.0f * 3.14159f * i / segments;
|
||||
f32 x = cx + r * std::cos(angle);
|
||||
f32 y = cy + r * std::sin(angle);
|
||||
verts.push_back(Vertex2D(Vec2(x, y), Vec2(0, 0), c, 0));
|
||||
}
|
||||
|
||||
return verts;
|
||||
}
|
||||
|
||||
std::vector<u32> Shape::createCircleIndices(u32 baseIndex, u32 segments) {
|
||||
std::vector<u32> indices;
|
||||
for (u32 i = 1; i <= segments; ++i) {
|
||||
indices.push_back(baseIndex);
|
||||
indices.push_back(baseIndex + i);
|
||||
indices.push_back(baseIndex + (i + 1 <= segments ? i + 1 : 1));
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
Shape* Shape::rect(const Vec2& sz, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Rect;
|
||||
s->size = sz;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::circle(f32 r, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Circle;
|
||||
s->radius = r;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::ellipse(const Vec2& sz, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Ellipse;
|
||||
s->size = sz;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::line(const Vec2& s, const Vec2& e, f32 w, const Color& c) {
|
||||
auto* shape = new Shape();
|
||||
shape->shapeType = ShapeType::Line;
|
||||
shape->start = s;
|
||||
shape->end = e;
|
||||
shape->lineWidth = w;
|
||||
shape->strokeColor = c;
|
||||
shape->fill = FillMode::Stroke;
|
||||
return shape;
|
||||
}
|
||||
|
||||
Shape* Shape::roundRect(const Vec2& sz, f32 r, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::RoundedRect;
|
||||
s->size = sz;
|
||||
s->cornerRadius = r;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::poly(const std::vector<Vec2>& pts, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Poly;
|
||||
s->points = pts;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::star(f32 outerR, f32 innerR, u32 n, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Star;
|
||||
s->radius = outerR;
|
||||
s->innerRadius = innerR / outerR;
|
||||
s->sides = n;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::arc(f32 r, f32 start, f32 sweep, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Arc;
|
||||
s->radius = r;
|
||||
s->startAngle = start;
|
||||
s->sweepAngle = sweep;
|
||||
s->strokeColor = c;
|
||||
s->fill = FillMode::Stroke;
|
||||
return s;
|
||||
}
|
||||
|
||||
Shape* Shape::sector(f32 r, f32 start, f32 sweep, const Color& c) {
|
||||
auto* s = new Shape();
|
||||
s->shapeType = ShapeType::Sector;
|
||||
s->radius = r;
|
||||
s->startAngle = start;
|
||||
s->sweepAngle = sweep;
|
||||
s->fillColor = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
#include "extra2d/node/sprite.h"
|
||||
#include "extra2d/cam/cam.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void Sprite::render(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
if (!visible || !enabled()) return;
|
||||
|
||||
Node* node = owner();
|
||||
if (!node) return;
|
||||
|
||||
Vec2 finalSize = size;
|
||||
if (node) {
|
||||
finalSize.x *= node->scale.x;
|
||||
finalSize.y *= node->scale.y;
|
||||
}
|
||||
|
||||
Vec2 offset(-anchor.x * finalSize.x, -anchor.y * finalSize.y);
|
||||
|
||||
Vec2 p0 = Vec2(offset.x, offset.y);
|
||||
Vec2 p1 = Vec2(offset.x + finalSize.x, offset.y);
|
||||
Vec2 p2 = Vec2(offset.x + finalSize.x, offset.y + finalSize.y);
|
||||
Vec2 p3 = Vec2(offset.x, offset.y + finalSize.y);
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
p0 = Vec2::fromGlm(transform * glm::vec3(p0.toGlm(), 1.0f));
|
||||
p1 = Vec2::fromGlm(transform * glm::vec3(p1.toGlm(), 1.0f));
|
||||
p2 = Vec2::fromGlm(transform * glm::vec3(p2.toGlm(), 1.0f));
|
||||
p3 = Vec2::fromGlm(transform * glm::vec3(p3.toGlm(), 1.0f));
|
||||
}
|
||||
|
||||
Vec2 finalUv0 = flipX ? Vec2(uv1.x, uv0.y) : uv0;
|
||||
Vec2 finalUv1 = flipX ? Vec2(uv0.x, uv1.y) : uv1;
|
||||
if (flipY) {
|
||||
std::swap(finalUv0.y, finalUv1.y);
|
||||
}
|
||||
|
||||
Color finalColor = applyOpacity(color);
|
||||
|
||||
std::vector<Vertex2D> verts = {
|
||||
Vertex2D(p0, Vec2(finalUv0.x, finalUv0.y), finalColor, 0),
|
||||
Vertex2D(p1, Vec2(finalUv1.x, finalUv0.y), finalColor, 0),
|
||||
Vertex2D(p2, Vec2(finalUv1.x, finalUv1.y), finalColor, 0),
|
||||
Vertex2D(p3, Vec2(finalUv0.x, finalUv1.y), finalColor, 0)
|
||||
};
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
u32 baseIndex = vertOffset;
|
||||
|
||||
std::vector<u32> indices = {
|
||||
baseIndex, baseIndex + 1, baseIndex + 2,
|
||||
baseIndex, baseIndex + 2, baseIndex + 3
|
||||
};
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = 4;
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = 6;
|
||||
cmdBuffer.addCmd(cmd);
|
||||
}
|
||||
|
||||
BoundingBox Sprite::bounds() const {
|
||||
Node* node = owner();
|
||||
Vec2 finalSize = size;
|
||||
if (node) {
|
||||
finalSize.x *= node->scale.x;
|
||||
finalSize.y *= node->scale.y;
|
||||
}
|
||||
|
||||
Vec2 offset(-anchor.x * finalSize.x, -anchor.y * finalSize.y);
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
|
||||
return BoundingBox::fromSize(worldPos + offset, finalSize);
|
||||
}
|
||||
|
||||
Sprite* Sprite::create(const Vec2& sz, const Color& c) {
|
||||
auto* sprite = new Sprite();
|
||||
sprite->size = sz;
|
||||
sprite->color = c;
|
||||
return sprite;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
#include "extra2d/node/text.h"
|
||||
#include "extra2d/cam/cam.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void Text::render(Camera* cam, RenderCmdBuffer& cmdBuffer) {
|
||||
if (!visible || !enabled() || str.empty()) return;
|
||||
|
||||
Node* node = owner();
|
||||
if (!node) return;
|
||||
|
||||
Vec2 textSize = measureText();
|
||||
Vec2 offset(-anchor.x * textSize.x, -anchor.y * textSize.y);
|
||||
|
||||
Vec2 worldPos = node->worldPos();
|
||||
Vec2 currentPos = worldPos + offset;
|
||||
|
||||
Color finalColor = applyOpacity(color);
|
||||
|
||||
f32 x = currentPos.x;
|
||||
f32 y = currentPos.y;
|
||||
|
||||
for (char c : str) {
|
||||
if (c == '\n') {
|
||||
x = currentPos.x;
|
||||
y += fontSize * lineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ') {
|
||||
x += fontSize * 0.3f + letterSpacing;
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 charWidth = fontSize * 0.6f;
|
||||
f32 charHeight = fontSize;
|
||||
|
||||
Vec2 p0(x, y);
|
||||
Vec2 p1(x + charWidth, y);
|
||||
Vec2 p2(x + charWidth, y + charHeight);
|
||||
Vec2 p3(x, y + charHeight);
|
||||
|
||||
if (node) {
|
||||
Mat3 transform = node->world();
|
||||
p0 = Vec2::fromGlm(transform * glm::vec3(p0.toGlm(), 1.0f));
|
||||
p1 = Vec2::fromGlm(transform * glm::vec3(p1.toGlm(), 1.0f));
|
||||
p2 = Vec2::fromGlm(transform * glm::vec3(p2.toGlm(), 1.0f));
|
||||
p3 = Vec2::fromGlm(transform * glm::vec3(p3.toGlm(), 1.0f));
|
||||
}
|
||||
|
||||
std::vector<Vertex2D> verts = {
|
||||
Vertex2D(p0, Vec2(0, 0), finalColor, 0),
|
||||
Vertex2D(p1, Vec2(1, 0), finalColor, 0),
|
||||
Vertex2D(p2, Vec2(1, 1), finalColor, 0),
|
||||
Vertex2D(p3, Vec2(0, 1), finalColor, 0)
|
||||
};
|
||||
|
||||
u32 vertOffset = cmdBuffer.addVertices(verts);
|
||||
u32 baseIndex = vertOffset;
|
||||
|
||||
std::vector<u32> indices = {
|
||||
baseIndex, baseIndex + 1, baseIndex + 2,
|
||||
baseIndex, baseIndex + 2, baseIndex + 3
|
||||
};
|
||||
u32 indexOffset = cmdBuffer.addIndices(indices);
|
||||
|
||||
RenderCmd cmd;
|
||||
cmd.type = RenderCmd::Type::DrawIndexed;
|
||||
cmd.vertexOffset = vertOffset;
|
||||
cmd.vertexCount = 4;
|
||||
cmd.indexOffset = indexOffset;
|
||||
cmd.indexCount = 6;
|
||||
cmdBuffer.addCmd(cmd);
|
||||
|
||||
x += charWidth + letterSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
BoundingBox Text::bounds() const {
|
||||
Vec2 textSize = measureText();
|
||||
Node* node = owner();
|
||||
Vec2 worldPos = node ? node->worldPos() : Vec2(0, 0);
|
||||
Vec2 offset(-anchor.x * textSize.x, -anchor.y * textSize.y);
|
||||
|
||||
return BoundingBox::fromSize(worldPos + offset, textSize);
|
||||
}
|
||||
|
||||
Vec2 Text::measureText() const {
|
||||
return measureText(str);
|
||||
}
|
||||
|
||||
Vec2 Text::measureText(const std::string& text) const {
|
||||
if (text.empty()) return Vec2(0, 0);
|
||||
|
||||
f32 maxWidth = 0.0f;
|
||||
f32 currentWidth = 0.0f;
|
||||
f32 height = fontSize;
|
||||
i32 lineCount = 1;
|
||||
|
||||
for (char c : text) {
|
||||
if (c == '\n') {
|
||||
maxWidth = std::max(maxWidth, currentWidth);
|
||||
currentWidth = 0.0f;
|
||||
lineCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ') {
|
||||
currentWidth += fontSize * 0.3f + letterSpacing;
|
||||
} else {
|
||||
currentWidth += fontSize * 0.6f + letterSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = std::max(maxWidth, currentWidth);
|
||||
height = fontSize + (lineCount - 1) * fontSize * lineHeight;
|
||||
|
||||
return Vec2(maxWidth, height);
|
||||
}
|
||||
|
||||
Text* Text::create(const std::string& text, f32 size, const Color& c) {
|
||||
auto* txt = new Text();
|
||||
txt->str = text;
|
||||
txt->fontSize = size;
|
||||
txt->color = c;
|
||||
return txt;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
#include "extra2d/render/backend/vulkan/vk_backend.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
bool VkBackend::init(VkInstance instance, VkPhysicalDevice physDevice, VkDevice device) {
|
||||
instance_ = instance;
|
||||
physDevice_ = physDevice;
|
||||
device_ = device;
|
||||
|
||||
if (!memAlloc_.init(instance, device, physDevice)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkCommandPoolCreateInfo poolInfo = {};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
poolInfo.queueFamilyIndex = graphicsFamily_;
|
||||
|
||||
if (vkCreateCommandPool(device_, &poolInfo, nullptr, &cmdPool_) != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBackend::shutdown() {
|
||||
destroySwapchain();
|
||||
|
||||
if (cmdPool_ != VK_NULL_HANDLE) {
|
||||
vkDestroyCommandPool(device_, cmdPool_, nullptr);
|
||||
cmdPool_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
memAlloc_.shutdown();
|
||||
|
||||
device_ = VK_NULL_HANDLE;
|
||||
physDevice_ = VK_NULL_HANDLE;
|
||||
instance_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
bool VkBackend::createSwapchain(VkSurfaceKHR surface, u32 width, u32 height) {
|
||||
surface_ = surface;
|
||||
|
||||
SwapchainSupport support = querySwapchainSupport(physDevice_);
|
||||
|
||||
VkSurfaceFormatKHR format = chooseSurfaceFormat(support.formats);
|
||||
VkPresentModeKHR presentMode = choosePresentMode(support.presentModes);
|
||||
VkExtent2D extent = chooseExtent(support.capabilities, width, height);
|
||||
|
||||
u32 imageCount = support.capabilities.minImageCount + 1;
|
||||
if (support.capabilities.maxImageCount > 0 && imageCount > support.capabilities.maxImageCount) {
|
||||
imageCount = support.capabilities.maxImageCount;
|
||||
}
|
||||
|
||||
VkSwapchainCreateInfoKHR createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
createInfo.surface = surface_;
|
||||
createInfo.minImageCount = imageCount;
|
||||
createInfo.imageFormat = format.format;
|
||||
createInfo.imageColorSpace = format.colorSpace;
|
||||
createInfo.imageExtent = extent;
|
||||
createInfo.imageArrayLayers = 1;
|
||||
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
createInfo.preTransform = support.capabilities.currentTransform;
|
||||
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
createInfo.presentMode = presentMode;
|
||||
createInfo.clipped = VK_TRUE;
|
||||
createInfo.oldSwapchain = VK_NULL_HANDLE;
|
||||
|
||||
if (vkCreateSwapchainKHR(device_, &createInfo, nullptr, &swapchain_) != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vkGetSwapchainImagesKHR(device_, swapchain_, &imageCount, nullptr);
|
||||
swapchainImages_.resize(imageCount);
|
||||
vkGetSwapchainImagesKHR(device_, swapchain_, &imageCount, swapchainImages_.data());
|
||||
|
||||
swapchainFormat_ = format.format;
|
||||
swapchainExtent_ = extent;
|
||||
|
||||
swapchainViews_.resize(swapchainImages_.size());
|
||||
for (size_t i = 0; i < swapchainImages_.size(); ++i) {
|
||||
VkImageViewCreateInfo viewInfo = {};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = swapchainImages_[i];
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = swapchainFormat_;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
if (vkCreateImageView(device_, &viewInfo, nullptr, &swapchainViews_[i]) != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cmdBuffers_.resize(swapchainImages_.size());
|
||||
VkCommandBufferAllocateInfo allocInfo = {};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = cmdPool_;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = static_cast<u32>(cmdBuffers_.size());
|
||||
|
||||
if (vkAllocateCommandBuffers(device_, &allocInfo, cmdBuffers_.data()) != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBackend::destroySwapchain() {
|
||||
for (auto& view : swapchainViews_) {
|
||||
if (view != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(device_, view, nullptr);
|
||||
}
|
||||
}
|
||||
swapchainViews_.clear();
|
||||
swapchainImages_.clear();
|
||||
|
||||
if (swapchain_ != VK_NULL_HANDLE) {
|
||||
vkDestroySwapchainKHR(device_, swapchain_, nullptr);
|
||||
swapchain_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (!cmdBuffers_.empty()) {
|
||||
vkFreeCommandBuffers(device_, cmdPool_, static_cast<u32>(cmdBuffers_.size()), cmdBuffers_.data());
|
||||
cmdBuffers_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool VkBackend::resizeSwapchain(u32 width, u32 height) {
|
||||
vkDeviceWaitIdle(device_);
|
||||
destroySwapchain();
|
||||
return createSwapchain(surface_, width, height);
|
||||
}
|
||||
|
||||
u32 VkBackend::acquireNextImage(VkSemaphore semaphore, VkFence fence) {
|
||||
u32 imageIndex;
|
||||
VkResult result = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, semaphore, fence, &imageIndex);
|
||||
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
return imageIndex;
|
||||
}
|
||||
|
||||
void VkBackend::present(u32 imageIndex, VkSemaphore waitSemaphore) {
|
||||
VkPresentInfoKHR presentInfo = {};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.waitSemaphoreCount = 1;
|
||||
presentInfo.pWaitSemaphores = &waitSemaphore;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &swapchain_;
|
||||
presentInfo.pImageIndices = &imageIndex;
|
||||
|
||||
vkQueuePresentKHR(VK_NULL_HANDLE, &presentInfo);
|
||||
}
|
||||
|
||||
VkCommandBuffer VkBackend::beginCmd() {
|
||||
VkCommandBufferBeginInfo beginInfo = {};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
|
||||
VkCommandBuffer cmd = cmdBuffers_[frameIdx_];
|
||||
vkBeginCommandBuffer(cmd, &beginInfo);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void VkBackend::endCmd(VkCommandBuffer cmd) {
|
||||
vkEndCommandBuffer(cmd);
|
||||
}
|
||||
|
||||
void VkBackend::submitCmd(VkCommandBuffer cmd, VkSemaphore wait, VkSemaphore signal, VkFence fence) {
|
||||
VkSubmitInfo submitInfo = {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &cmd;
|
||||
|
||||
if (wait != VK_NULL_HANDLE) {
|
||||
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
submitInfo.pWaitSemaphores = &wait;
|
||||
submitInfo.pWaitDstStageMask = &waitStage;
|
||||
}
|
||||
|
||||
if (signal != VK_NULL_HANDLE) {
|
||||
submitInfo.signalSemaphoreCount = 1;
|
||||
submitInfo.pSignalSemaphores = &signal;
|
||||
}
|
||||
|
||||
vkQueueSubmit(VK_NULL_HANDLE, 1, &submitInfo, fence);
|
||||
}
|
||||
|
||||
void VkBackend::waitIdle() {
|
||||
vkDeviceWaitIdle(device_);
|
||||
}
|
||||
|
||||
SwapchainSupport VkBackend::querySwapchainSupport(VkPhysicalDevice device) {
|
||||
SwapchainSupport support;
|
||||
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, &support.capabilities);
|
||||
|
||||
u32 formatCount;
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, nullptr);
|
||||
if (formatCount != 0) {
|
||||
support.formats.resize(formatCount);
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, support.formats.data());
|
||||
}
|
||||
|
||||
u32 presentModeCount;
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, nullptr);
|
||||
if (presentModeCount != 0) {
|
||||
support.presentModes.resize(presentModeCount);
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, support.presentModes.data());
|
||||
}
|
||||
|
||||
return support;
|
||||
}
|
||||
|
||||
VkSurfaceFormatKHR VkBackend::chooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats) {
|
||||
for (const auto& format : formats) {
|
||||
if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
return formats[0];
|
||||
}
|
||||
|
||||
VkPresentModeKHR VkBackend::choosePresentMode(const std::vector<VkPresentModeKHR>& modes) {
|
||||
for (const auto& mode : modes) {
|
||||
if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
|
||||
VkExtent2D VkBackend::chooseExtent(const VkSurfaceCapabilitiesKHR& caps, u32 width, u32 height) {
|
||||
if (caps.currentExtent.width != UINT32_MAX) {
|
||||
return caps.currentExtent;
|
||||
}
|
||||
|
||||
VkExtent2D extent = {width, height};
|
||||
extent.width = std::clamp(extent.width, caps.minImageExtent.width, caps.maxImageExtent.width);
|
||||
extent.height = std::clamp(extent.height, caps.minImageExtent.height, caps.maxImageExtent.height);
|
||||
return extent;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
#include "extra2d/render/backend/vulkan/vk_mem.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
bool VkMemAlloc::init(VkInstance inst, VkDevice device, VkPhysicalDevice physDevice) {
|
||||
VmaAllocatorCreateInfo createInfo = {};
|
||||
createInfo.instance = inst;
|
||||
createInfo.device = device;
|
||||
createInfo.physicalDevice = physDevice;
|
||||
createInfo.vulkanApiVersion = VK_API_VERSION_1_2;
|
||||
|
||||
VkResult result = vmaCreateAllocator(&createInfo, &allocator_);
|
||||
return result == VK_SUCCESS;
|
||||
}
|
||||
|
||||
void VkMemAlloc::shutdown() {
|
||||
if (allocator_ != VK_NULL_HANDLE) {
|
||||
vmaDestroyAllocator(allocator_);
|
||||
allocator_ = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
VkBuffer VkMemAlloc::createBuffer(
|
||||
VkDeviceSize size,
|
||||
VkBufferUsageFlags usage,
|
||||
MemType type,
|
||||
VmaAllocation* outAlloc
|
||||
) {
|
||||
VkBufferCreateInfo bufferInfo = {};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = size;
|
||||
bufferInfo.usage = usage;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo = {};
|
||||
switch (type) {
|
||||
case MemType::Gpu:
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
break;
|
||||
case MemType::Upload:
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
break;
|
||||
case MemType::Readback:
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
|
||||
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
VkBuffer buffer;
|
||||
VkResult result = vmaCreateBuffer(allocator_, &bufferInfo, &allocInfo, &buffer, outAlloc, nullptr);
|
||||
|
||||
return result == VK_SUCCESS ? buffer : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImage VkMemAlloc::createImage(
|
||||
const VkImageCreateInfo& info,
|
||||
MemType type,
|
||||
VmaAllocation* outAlloc
|
||||
) {
|
||||
VmaAllocationCreateInfo allocInfo = {};
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
|
||||
VkImage image;
|
||||
VkResult result = vmaCreateImage(allocator_, &info, &allocInfo, &image, outAlloc, nullptr);
|
||||
|
||||
return result == VK_SUCCESS ? image : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void VkMemAlloc::destroyBuffer(VkBuffer buf, VmaAllocation alloc) {
|
||||
if (allocator_ != VK_NULL_HANDLE && buf != VK_NULL_HANDLE) {
|
||||
vmaDestroyBuffer(allocator_, buf, alloc);
|
||||
}
|
||||
}
|
||||
|
||||
void VkMemAlloc::destroyImage(VkImage img, VmaAllocation alloc) {
|
||||
if (allocator_ != VK_NULL_HANDLE && img != VK_NULL_HANDLE) {
|
||||
vmaDestroyImage(allocator_, img, alloc);
|
||||
}
|
||||
}
|
||||
|
||||
void* VkMemAlloc::map(VmaAllocation alloc) {
|
||||
void* data = nullptr;
|
||||
vmaMapMemory(allocator_, alloc, &data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void VkMemAlloc::unmap(VmaAllocation alloc) {
|
||||
vmaUnmapMemory(allocator_, alloc);
|
||||
}
|
||||
|
||||
void VkMemAlloc::flush(VmaAllocation alloc, VkDeviceSize off, VkDeviceSize size) {
|
||||
vmaFlushAllocation(allocator_, alloc, off, size);
|
||||
}
|
||||
|
||||
void VkMemAlloc::invalidate(VmaAllocation alloc, VkDeviceSize off, VkDeviceSize size) {
|
||||
vmaInvalidateAllocation(allocator_, alloc, off, size);
|
||||
}
|
||||
|
||||
void VkMemAlloc::getStats(VmaTotalStatistics* stats) {
|
||||
vmaCalculateStatistics(allocator_, stats);
|
||||
}
|
||||
|
||||
VkBufferWrap::VkBufferWrap(VkMemAlloc* alloc, VkDeviceSize size, VkBufferUsageFlags usage, MemType type)
|
||||
: memAlloc_(alloc), size_(size), type_(type) {
|
||||
if (memAlloc_) {
|
||||
buffer_ = memAlloc_->createBuffer(size, usage, type, &vmaAlloc_);
|
||||
}
|
||||
}
|
||||
|
||||
VkBufferWrap::~VkBufferWrap() {
|
||||
release();
|
||||
}
|
||||
|
||||
VkBufferWrap::VkBufferWrap(VkBufferWrap&& other) noexcept
|
||||
: memAlloc_(other.memAlloc_)
|
||||
, buffer_(other.buffer_)
|
||||
, vmaAlloc_(other.vmaAlloc_)
|
||||
, size_(other.size_)
|
||||
, type_(other.type_)
|
||||
, mapped_(other.mapped_) {
|
||||
other.memAlloc_ = nullptr;
|
||||
other.buffer_ = VK_NULL_HANDLE;
|
||||
other.vmaAlloc_ = VK_NULL_HANDLE;
|
||||
other.size_ = 0;
|
||||
other.mapped_ = nullptr;
|
||||
}
|
||||
|
||||
VkBufferWrap& VkBufferWrap::operator=(VkBufferWrap&& other) noexcept {
|
||||
if (this != &other) {
|
||||
release();
|
||||
memAlloc_ = other.memAlloc_;
|
||||
buffer_ = other.buffer_;
|
||||
vmaAlloc_ = other.vmaAlloc_;
|
||||
size_ = other.size_;
|
||||
type_ = other.type_;
|
||||
mapped_ = other.mapped_;
|
||||
other.memAlloc_ = nullptr;
|
||||
other.buffer_ = VK_NULL_HANDLE;
|
||||
other.vmaAlloc_ = VK_NULL_HANDLE;
|
||||
other.size_ = 0;
|
||||
other.mapped_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void* VkBufferWrap::map() {
|
||||
if (memAlloc_ && vmaAlloc_ != VK_NULL_HANDLE) {
|
||||
mapped_ = memAlloc_->map(vmaAlloc_);
|
||||
return mapped_;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VkBufferWrap::unmap() {
|
||||
if (memAlloc_ && vmaAlloc_ != VK_NULL_HANDLE && mapped_) {
|
||||
memAlloc_->unmap(vmaAlloc_);
|
||||
mapped_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferWrap::write(const void* data, VkDeviceSize size, VkDeviceSize off) {
|
||||
if (type_ == MemType::Upload || type_ == MemType::Readback) {
|
||||
void* mapped = map();
|
||||
if (mapped) {
|
||||
memcpy(static_cast<char*>(mapped) + off, data, size);
|
||||
unmap();
|
||||
if (type_ == MemType::Upload) {
|
||||
memAlloc_->flush(vmaAlloc_, off, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferWrap::read(void* data, VkDeviceSize size, VkDeviceSize off) {
|
||||
if (type_ == MemType::Readback) {
|
||||
memAlloc_->invalidate(vmaAlloc_, off, size);
|
||||
void* mapped = map();
|
||||
if (mapped) {
|
||||
memcpy(data, static_cast<const char*>(mapped) + off, size);
|
||||
unmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VkBufferWrap::release() {
|
||||
if (memAlloc_ && buffer_ != VK_NULL_HANDLE) {
|
||||
memAlloc_->destroyBuffer(buffer_, vmaAlloc_);
|
||||
buffer_ = VK_NULL_HANDLE;
|
||||
vmaAlloc_ = VK_NULL_HANDLE;
|
||||
size_ = 0;
|
||||
mapped_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VkImageWrap::VkImageWrap(VkMemAlloc* alloc, const VkImageCreateInfo& info, MemType type)
|
||||
: memAlloc_(alloc), extent_(info.extent), format_(info.format) {
|
||||
if (memAlloc_) {
|
||||
image_ = memAlloc_->createImage(info, type, &vmaAlloc_);
|
||||
}
|
||||
}
|
||||
|
||||
VkImageWrap::~VkImageWrap() {
|
||||
release();
|
||||
}
|
||||
|
||||
VkImageWrap::VkImageWrap(VkImageWrap&& other) noexcept
|
||||
: memAlloc_(other.memAlloc_)
|
||||
, image_(other.image_)
|
||||
, vmaAlloc_(other.vmaAlloc_)
|
||||
, extent_(other.extent_)
|
||||
, format_(other.format_) {
|
||||
other.memAlloc_ = nullptr;
|
||||
other.image_ = VK_NULL_HANDLE;
|
||||
other.vmaAlloc_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImageWrap& VkImageWrap::operator=(VkImageWrap&& other) noexcept {
|
||||
if (this != &other) {
|
||||
release();
|
||||
memAlloc_ = other.memAlloc_;
|
||||
image_ = other.image_;
|
||||
vmaAlloc_ = other.vmaAlloc_;
|
||||
extent_ = other.extent_;
|
||||
format_ = other.format_;
|
||||
other.memAlloc_ = nullptr;
|
||||
other.image_ = VK_NULL_HANDLE;
|
||||
other.vmaAlloc_ = VK_NULL_HANDLE;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void VkImageWrap::release() {
|
||||
if (memAlloc_ && image_ != VK_NULL_HANDLE) {
|
||||
memAlloc_->destroyImage(image_, vmaAlloc_);
|
||||
image_ = VK_NULL_HANDLE;
|
||||
vmaAlloc_ = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#include "extra2d/render/render_mod.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#include "extra2d/render/shader_mgr.h"
|
||||
#include <fstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
bool ShaderMgr::load(const std::string& name, const std::string& path, ShaderType type) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string src((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
ShaderDef def;
|
||||
def.name = name;
|
||||
def.type = type;
|
||||
def.src = src;
|
||||
def.path = path;
|
||||
|
||||
shaders_[name] = def;
|
||||
|
||||
if (std::filesystem::exists(path)) {
|
||||
fileTimes_[name] = std::filesystem::last_write_time(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderMgr::loadFromSrc(const std::string& name, const std::string& src, ShaderType type) {
|
||||
ShaderDef def;
|
||||
def.name = name;
|
||||
def.type = type;
|
||||
def.src = src;
|
||||
|
||||
shaders_[name] = def;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderMgr::loadFromSpirv(const std::string& name, const std::vector<u8>& spirv, ShaderType type) {
|
||||
ShaderDef def;
|
||||
def.name = name;
|
||||
def.type = type;
|
||||
def.spirv = spirv;
|
||||
|
||||
shaders_[name] = def;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderMgr::reload(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it == shaders_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!it->second.path.empty()) {
|
||||
if (load(name, it->second.path, it->second.type)) {
|
||||
if (reloadCb_) {
|
||||
reloadCb_(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShaderMgr::reloadAll() {
|
||||
for (auto& [name, def] : shaders_) {
|
||||
if (!def.path.empty()) {
|
||||
load(name, def.path, def.type);
|
||||
}
|
||||
}
|
||||
if (reloadCb_) {
|
||||
reloadCb_("all");
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderMgr::checkFileChanges() {
|
||||
if (!autoReload_) return;
|
||||
|
||||
for (auto& [name, def] : shaders_) {
|
||||
if (!def.path.empty() && std::filesystem::exists(def.path)) {
|
||||
auto currentTime = std::filesystem::last_write_time(def.path);
|
||||
auto storedTime = fileTimes_[name];
|
||||
if (currentTime != storedTime) {
|
||||
fileTimes_[name] = currentTime;
|
||||
reload(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
#include "extra2d/render/shader_sdf.h"
|
||||
|
||||
namespace extra2d {
|
||||
namespace sdf {
|
||||
|
||||
const char* kCircleSdf = R"(
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 vUV;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec2 size;
|
||||
float edgeSoftness;
|
||||
float outlineWidth;
|
||||
vec4 outlineColor;
|
||||
} pc;
|
||||
|
||||
float circleSDF(vec2 p, float r) {
|
||||
return length(p) - r;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = vUV * 2.0 - 1.0;
|
||||
float d = circleSDF(p, 0.5);
|
||||
|
||||
float alpha = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d);
|
||||
|
||||
if (pc.outlineWidth > 0.0) {
|
||||
float outline = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d + pc.outlineWidth);
|
||||
fragColor = mix(pc.outlineColor, pc.color, alpha);
|
||||
fragColor.a = outline;
|
||||
} else {
|
||||
fragColor = pc.color;
|
||||
fragColor.a *= alpha;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* kRectSdf = R"(
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 vUV;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec2 size;
|
||||
float edgeSoftness;
|
||||
float outlineWidth;
|
||||
vec4 outlineColor;
|
||||
} pc;
|
||||
|
||||
float rectSDF(vec2 p, vec2 size) {
|
||||
vec2 d = abs(p) - size * 0.5;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = vUV * 2.0 - 1.0;
|
||||
float d = rectSDF(p, pc.size);
|
||||
|
||||
float alpha = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d);
|
||||
|
||||
if (pc.outlineWidth > 0.0) {
|
||||
float outline = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d + pc.outlineWidth);
|
||||
fragColor = mix(pc.outlineColor, pc.color, alpha);
|
||||
fragColor.a = outline;
|
||||
} else {
|
||||
fragColor = pc.color;
|
||||
fragColor.a *= alpha;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* kRoundedRectSdf = R"(
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 vUV;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec2 size;
|
||||
float cornerRadius;
|
||||
float edgeSoftness;
|
||||
float outlineWidth;
|
||||
vec4 outlineColor;
|
||||
} pc;
|
||||
|
||||
float roundedRectSDF(vec2 p, vec2 size, float r) {
|
||||
vec2 d = abs(p) - size * 0.5 + r;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = vUV * 2.0 - 1.0;
|
||||
float d = roundedRectSDF(p, pc.size, pc.cornerRadius);
|
||||
|
||||
float alpha = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d);
|
||||
|
||||
if (pc.outlineWidth > 0.0) {
|
||||
float outline = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d + pc.outlineWidth);
|
||||
fragColor = mix(pc.outlineColor, pc.color, alpha);
|
||||
fragColor.a = outline;
|
||||
} else {
|
||||
fragColor = pc.color;
|
||||
fragColor.a *= alpha;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
std::string genFragSrc(const Mat& mat) {
|
||||
std::string src = R"(
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 vUV;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 color;
|
||||
vec2 size;
|
||||
float radius;
|
||||
float edgeSoftness;
|
||||
float outlineWidth;
|
||||
vec4 outlineColor;
|
||||
} pc;
|
||||
)";
|
||||
|
||||
switch (mat.shape) {
|
||||
case Shape::Circle:
|
||||
src += R"(
|
||||
float sdf(vec2 p) {
|
||||
return length(p) - pc.radius;
|
||||
}
|
||||
)";
|
||||
break;
|
||||
case Shape::Rect:
|
||||
src += R"(
|
||||
float sdf(vec2 p) {
|
||||
vec2 d = abs(p) - pc.size * 0.5;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
||||
}
|
||||
)";
|
||||
break;
|
||||
case Shape::RoundedRect:
|
||||
src += R"(
|
||||
float sdf(vec2 p) {
|
||||
vec2 d = abs(p) - pc.size * 0.5 + pc.radius;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - pc.radius;
|
||||
}
|
||||
)";
|
||||
break;
|
||||
default:
|
||||
if (!mat.customSdfFunc.empty()) {
|
||||
src += mat.customSdfFunc;
|
||||
} else {
|
||||
src += R"(
|
||||
float sdf(vec2 p) {
|
||||
return length(p) - pc.radius;
|
||||
}
|
||||
)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
src += R"(
|
||||
void main() {
|
||||
vec2 p = vUV * 2.0 - 1.0;
|
||||
float d = sdf(p);
|
||||
|
||||
float alpha = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d);
|
||||
|
||||
if (pc.outlineWidth > 0.0) {
|
||||
float outline = 1.0 - smoothstep(-pc.edgeSoftness, pc.edgeSoftness, d + pc.outlineWidth);
|
||||
fragColor = mix(pc.outlineColor, pc.color, alpha);
|
||||
fragColor.a = outline;
|
||||
} else {
|
||||
fragColor = pc.color;
|
||||
fragColor.a *= alpha;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#include "extra2d/scene/director.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#include "extra2d/scene/scene.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#include "extra2d/scene/trans.h"
|
||||
#include "extra2d/scene/director.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void Trans::finish() {
|
||||
if (inScene_) {
|
||||
inScene_->onEnter();
|
||||
inScene_->setRunning(true);
|
||||
}
|
||||
if (outScene_) {
|
||||
outScene_->onExit();
|
||||
outScene_->setRunning(false);
|
||||
}
|
||||
}
|
||||
|
||||
void TransFade::draw(f32 p) {
|
||||
}
|
||||
|
||||
void TransFlipX::draw(f32 p) {
|
||||
f32 angle = p * 180.0f;
|
||||
if (p < 0.5f) {
|
||||
if (outScene_) {
|
||||
}
|
||||
} else {
|
||||
if (inScene_) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransFlipY::draw(f32 p) {
|
||||
f32 angle = p * 180.0f;
|
||||
}
|
||||
|
||||
void TransMoveInL::draw(f32 p) {
|
||||
if (inScene_) {
|
||||
f32 x = (1.0f - p) * -1280.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void TransMoveInR::draw(f32 p) {
|
||||
if (inScene_) {
|
||||
f32 x = (1.0f - p) * 1280.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void TransMoveInT::draw(f32 p) {
|
||||
if (inScene_) {
|
||||
f32 y = (1.0f - p) * 720.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void TransMoveInB::draw(f32 p) {
|
||||
if (inScene_) {
|
||||
f32 y = (1.0f - p) * -720.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void TransZoomFlipX::draw(f32 p) {
|
||||
f32 scale = std::abs(std::sin(p * PI_F));
|
||||
f32 angle = p * 360.0f;
|
||||
}
|
||||
|
||||
void TransZoomFlipY::draw(f32 p) {
|
||||
f32 scale = std::abs(std::sin(p * PI_F));
|
||||
f32 angle = p * 360.0f;
|
||||
}
|
||||
|
||||
void TransRotoZoom::draw(f32 p) {
|
||||
f32 scale = 1.0f + std::sin(p * PI_F * 2) * 0.5f;
|
||||
f32 angle = p * 360.0f;
|
||||
}
|
||||
|
||||
void TransJumpZoom::draw(f32 p) {
|
||||
f32 scale = 1.0f + std::sin(p * PI_F) * 0.5f;
|
||||
f32 jump = std::abs(std::sin(p * PI_F * 2)) * 100.0f;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#include "extra2d/win/win_adapt.h"
|
||||
#include "extra2d/cam/cam.h"
|
||||
#include "extra2d/cam/ortho_cam.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void WinAdapt::apply(Camera* cam) const {
|
||||
if (!cam) return;
|
||||
|
||||
if (cam->type() == CamType::Ortho) {
|
||||
auto* ortho = static_cast<OrthoCam*>(cam);
|
||||
ortho->setOrthoFromSize(cfg_.refW, cfg_.refH);
|
||||
ortho->viewport(vp_.origin.x, vp_.origin.y, vp_.size.width, vp_.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#include "extra2d/win/win_adapt_svc.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,622 @@
|
|||
/**
|
||||
* @file test_action.cpp
|
||||
* @brief Action 系统单元测试
|
||||
*
|
||||
* 测试动作系统的基础类、即时动作、间隔动作、复合动作等功能。
|
||||
*/
|
||||
|
||||
#include "test_framework.h"
|
||||
#include <extra2d/act/act.h>
|
||||
#include <extra2d/act/act_instant.h>
|
||||
#include <extra2d/act/act_move.h>
|
||||
#include <extra2d/act/act_rotate.h>
|
||||
#include <extra2d/act/act_scale.h>
|
||||
#include <extra2d/act/act_fade.h>
|
||||
#include <extra2d/act/act_composite.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <cmath>
|
||||
|
||||
using namespace extra2d;
|
||||
using namespace extra2d::test;
|
||||
|
||||
namespace {
|
||||
constexpr f32 EPSILON = 0.001f;
|
||||
|
||||
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Act 基础类测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Act, Tag) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
class TestAct : public Act {
|
||||
public:
|
||||
bool done() const override { return true; }
|
||||
void update(f32 t) override {}
|
||||
Act* clone() const override { return new TestAct(); }
|
||||
Act* reverse() const override { return new TestAct(); }
|
||||
};
|
||||
|
||||
TestAct act;
|
||||
TEST_ASSERT_EQ(-1, act.tag());
|
||||
|
||||
act.tag(42);
|
||||
TEST_ASSERT_EQ(42, act.tag());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ActInstant 测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(ActInstant, Done) {
|
||||
CallFunc act([](){});
|
||||
TEST_ASSERT_TRUE(act.done());
|
||||
}
|
||||
|
||||
TEST(CallFunc, Execute) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
bool called = false;
|
||||
CallFunc act([&called](){ called = true; });
|
||||
|
||||
act.start(node.get());
|
||||
act.step(0.016f);
|
||||
|
||||
TEST_ASSERT_TRUE(called);
|
||||
}
|
||||
|
||||
TEST(Place, SetPosition) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
Place act(Vec2(100, 200));
|
||||
|
||||
act.start(node.get());
|
||||
act.step(0.016f);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 200)));
|
||||
}
|
||||
|
||||
TEST(Show, SetVisible) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setVisible(false);
|
||||
|
||||
Show act;
|
||||
|
||||
act.start(node.get());
|
||||
act.step(0.016f);
|
||||
|
||||
TEST_ASSERT_TRUE(node->visible());
|
||||
}
|
||||
|
||||
TEST(Hide, SetInvisible) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setVisible(true);
|
||||
|
||||
Hide act;
|
||||
|
||||
act.start(node.get());
|
||||
act.step(0.016f);
|
||||
|
||||
TEST_ASSERT_FALSE(node->visible());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ActInterval 基础测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(ActInterval, Duration) {
|
||||
MoveTo act(2.0f, Vec2(100, 100));
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(act.dur() - 2.0f) < EPSILON);
|
||||
TEST_ASSERT_FALSE(act.done());
|
||||
}
|
||||
|
||||
TEST(ActInterval, Elapsed) {
|
||||
MoveTo act(1.0f, Vec2(100, 100));
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(act.elap()) < EPSILON);
|
||||
|
||||
act.step(0.5f);
|
||||
TEST_ASSERT_TRUE(std::abs(act.elap() - 0.5f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(ActInterval, Done) {
|
||||
MoveTo act(1.0f, Vec2(100, 100));
|
||||
|
||||
TEST_ASSERT_FALSE(act.done());
|
||||
|
||||
act.step(1.0f);
|
||||
TEST_ASSERT_TRUE(act.done());
|
||||
|
||||
act.step(0.5f);
|
||||
TEST_ASSERT_TRUE(act.done());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MoveTo/MoveBy 测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(MoveTo, MoveToPosition) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
MoveTo act(1.0f, Vec2(100, 200));
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(0, 0)));
|
||||
|
||||
act.update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 100)));
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 200)));
|
||||
}
|
||||
|
||||
TEST(MoveBy, MoveByOffset) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(50, 50);
|
||||
|
||||
MoveBy act(1.0f, Vec2(100, 100));
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 50)));
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(150, 150)));
|
||||
}
|
||||
|
||||
TEST(MoveTo, Clone) {
|
||||
MoveTo act(2.0f, Vec2(100, 200));
|
||||
act.tag(42);
|
||||
|
||||
Act* cloned = act.clone();
|
||||
|
||||
TEST_ASSERT_NOT_NULL(cloned);
|
||||
TEST_ASSERT_EQ(42, cloned->tag());
|
||||
|
||||
MoveTo* clonedMove = dynamic_cast<MoveTo*>(cloned);
|
||||
TEST_ASSERT_NOT_NULL(clonedMove);
|
||||
|
||||
delete cloned;
|
||||
}
|
||||
|
||||
TEST(MoveBy, Reverse) {
|
||||
MoveBy act(1.0f, Vec2(100, 100));
|
||||
|
||||
Act* reversed = act.reverse();
|
||||
|
||||
TEST_ASSERT_NOT_NULL(reversed);
|
||||
|
||||
MoveBy* reversedMove = dynamic_cast<MoveBy*>(reversed);
|
||||
TEST_ASSERT_NOT_NULL(reversedMove);
|
||||
|
||||
delete reversed;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RotTo/RotBy 测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(RotTo, RotateToAngle) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setRot(0);
|
||||
|
||||
RotTo act(1.0f, 90.0f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot) < EPSILON);
|
||||
|
||||
act.update(0.5f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 45.0f) < EPSILON);
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 90.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(RotBy, RotateByAngle) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setRot(45.0f);
|
||||
|
||||
RotBy act(1.0f, 90.0f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 45.0f) < EPSILON);
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 135.0f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ScaleTo/ScaleBy 测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(ScaleTo, ScaleToValue) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setScale(1.0f);
|
||||
|
||||
ScaleTo act(1.0f, 2.0f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(1, 1)));
|
||||
|
||||
act.update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(1.5f, 1.5f)));
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 2)));
|
||||
}
|
||||
|
||||
TEST(ScaleBy, ScaleByValue) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setScale(1.0f);
|
||||
|
||||
ScaleBy act(1.0f, 2.0f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 2)));
|
||||
}
|
||||
|
||||
TEST(ScaleTo, ScaleXY) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setScale(1.0f);
|
||||
|
||||
ScaleTo act(1.0f, Vec2(2.0f, 3.0f));
|
||||
act.start(node.get());
|
||||
|
||||
act.update(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 3)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FadeTo/FadeIn/FadeOut 测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(FadeTo, FadeToOpacity) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
FadeTo act(1.0f, 0.5f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.0f);
|
||||
|
||||
act.update(1.0f);
|
||||
}
|
||||
|
||||
TEST(FadeIn, FadeIn) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
FadeIn act(1.0f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(1.0f);
|
||||
}
|
||||
|
||||
TEST(FadeOut, FadeOut) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
FadeOut act(1.0f);
|
||||
act.start(node.get());
|
||||
|
||||
act.update(1.0f);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Easing 函数测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Easing, Linear) {
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeLinear(0.0f) - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeLinear(0.5f) - 0.5f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeLinear(1.0f) - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseIn) {
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeIn(0.0f) - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeIn(0.5f) - 0.25f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeIn(1.0f) - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseOut) {
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeOut(0.0f) - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeOut(0.5f) - 0.75f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeOut(1.0f) - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseInOut) {
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeInOut(0.0f) - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeInOut(0.25f) - 0.125f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeInOut(0.5f) - 0.5f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeInOut(0.75f) - 0.875f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeInOut(1.0f) - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseBackIn) {
|
||||
f32 result = ActInterval::easeBackIn(0.5f);
|
||||
TEST_ASSERT_TRUE(result < 0.5f);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseBackOut) {
|
||||
f32 result = ActInterval::easeBackOut(0.5f);
|
||||
TEST_ASSERT_TRUE(result > 0.5f);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseBounceOut) {
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeBounceOut(0.0f) - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeBounceOut(1.0f) - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Easing, EaseElasticOut) {
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeElasticOut(0.0f) - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(ActInterval::easeElasticOut(1.0f) - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Seq 序列动作测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Seq, Sequence) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
auto seq = Seq::create(
|
||||
new MoveTo(0.5f, Vec2(100, 0)),
|
||||
new MoveTo(0.5f, Vec2(100, 100))
|
||||
);
|
||||
|
||||
seq->start(node.get());
|
||||
|
||||
seq->update(0.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(0, 0)));
|
||||
|
||||
seq->update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 0)));
|
||||
|
||||
seq->update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 100)));
|
||||
|
||||
TEST_ASSERT_TRUE(seq->done());
|
||||
|
||||
delete seq;
|
||||
}
|
||||
|
||||
TEST(Seq, SingleAction) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
auto seq = Seq::create(
|
||||
new MoveTo(0.5f, Vec2(100, 0))
|
||||
);
|
||||
|
||||
seq->start(node.get());
|
||||
|
||||
seq->update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 0)));
|
||||
|
||||
TEST_ASSERT_TRUE(seq->done());
|
||||
|
||||
delete seq;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Spawn 并行动作测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Spawn, Parallel) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
node->setRot(0);
|
||||
|
||||
auto spawn = Spawn::create(
|
||||
new MoveTo(1.0f, Vec2(100, 100)),
|
||||
new RotTo(1.0f, 90.0f)
|
||||
);
|
||||
|
||||
spawn->start(node.get());
|
||||
|
||||
spawn->update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 50)));
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 45.0f) < EPSILON);
|
||||
|
||||
spawn->update(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 100)));
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 90.0f) < EPSILON);
|
||||
|
||||
TEST_ASSERT_TRUE(spawn->done());
|
||||
|
||||
delete spawn;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Repeat 重复动作测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Repeat, RepeatTimes) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
auto repeat = new Repeat(new MoveBy(0.5f, Vec2(100, 0)), 3);
|
||||
|
||||
repeat->start(node.get());
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
repeat->update(0.5f);
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(300, 0)));
|
||||
TEST_ASSERT_TRUE(repeat->done());
|
||||
|
||||
delete repeat;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RepeatForever 无限重复测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(RepeatForever, NeverDone) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
auto repeat = new RepeatForever(new MoveBy(1.0f, Vec2(100, 0)));
|
||||
|
||||
repeat->start(node.get());
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
repeat->update(1.0f);
|
||||
TEST_ASSERT_FALSE(repeat->done());
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE(node->pos.x > 900);
|
||||
|
||||
delete repeat;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Speed 速度控制测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Speed, DoubleSpeed) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
auto speed = new Speed(new MoveTo(2.0f, Vec2(100, 0)), 2.0f);
|
||||
|
||||
speed->start(node.get());
|
||||
|
||||
speed->step(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 0)));
|
||||
|
||||
speed->step(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 0)));
|
||||
TEST_ASSERT_TRUE(speed->done());
|
||||
|
||||
delete speed;
|
||||
}
|
||||
|
||||
TEST(Speed, HalfSpeed) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
auto speed = new Speed(new MoveTo(1.0f, Vec2(100, 0)), 0.5f);
|
||||
|
||||
speed->start(node.get());
|
||||
|
||||
speed->step(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 0)));
|
||||
TEST_ASSERT_FALSE(speed->done());
|
||||
|
||||
speed->step(1.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 0)));
|
||||
TEST_ASSERT_TRUE(speed->done());
|
||||
|
||||
delete speed;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Node 动作管理测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Node, RunAction) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
MoveTo* act = new MoveTo(1.0f, Vec2(100, 100));
|
||||
Act* returned = node->run(act);
|
||||
|
||||
TEST_ASSERT_EQ(act, returned);
|
||||
TEST_ASSERT_EQ(1, node->runningActs());
|
||||
}
|
||||
|
||||
TEST(Node, StopAction) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
MoveTo* act = new MoveTo(1.0f, Vec2(100, 100));
|
||||
act->tag(42);
|
||||
node->run(act);
|
||||
|
||||
node->stop(act);
|
||||
|
||||
TEST_ASSERT_EQ(0, node->runningActs());
|
||||
}
|
||||
|
||||
TEST(Node, StopByTag) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
MoveTo* act = new MoveTo(1.0f, Vec2(100, 100));
|
||||
act->tag(42);
|
||||
node->run(act);
|
||||
|
||||
node->stopByTag(42);
|
||||
|
||||
TEST_ASSERT_EQ(0, node->runningActs());
|
||||
}
|
||||
|
||||
TEST(Node, StopAll) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
node->run(new MoveTo(1.0f, Vec2(100, 0)));
|
||||
node->run(new RotTo(1.0f, 90.0f));
|
||||
node->run(new ScaleTo(1.0f, 2.0f));
|
||||
|
||||
TEST_ASSERT_EQ(3, node->runningActs());
|
||||
|
||||
node->stopAll();
|
||||
|
||||
TEST_ASSERT_EQ(0, node->runningActs());
|
||||
}
|
||||
|
||||
TEST(Node, GetActByTag) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
MoveTo* act = new MoveTo(1.0f, Vec2(100, 100));
|
||||
act->tag(42);
|
||||
node->run(act);
|
||||
|
||||
Act* found = node->getActByTag(42);
|
||||
TEST_ASSERT_EQ(act, found);
|
||||
|
||||
Act* notFound = node->getActByTag(999);
|
||||
TEST_ASSERT_NULL(notFound);
|
||||
}
|
||||
|
||||
TEST(Node, UpdateActions) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
node->run(new MoveTo(1.0f, Vec2(100, 0)));
|
||||
|
||||
node->updateActs(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 0)));
|
||||
|
||||
node->updateActs(0.5f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 0)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 自定义缓动测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(ActInterval, CustomEasing) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(0, 0);
|
||||
|
||||
MoveTo act(1.0f, Vec2(100, 0));
|
||||
act.ease([](f32 t) { return t * t * t; });
|
||||
act.start(node.get());
|
||||
|
||||
act.update(0.5f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->pos.x - 12.5f) < EPSILON);
|
||||
}
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
/**
|
||||
* @file test_camera.cpp
|
||||
* @brief Camera 系统单元测试
|
||||
*
|
||||
* 测试正交相机的投影矩阵、坐标转换等功能。
|
||||
*/
|
||||
|
||||
#include "test_framework.h"
|
||||
#include <extra2d/cam/cam.h>
|
||||
#include <extra2d/cam/ortho_cam.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <cmath>
|
||||
|
||||
using namespace extra2d;
|
||||
using namespace extra2d::test;
|
||||
|
||||
namespace {
|
||||
constexpr f32 EPSILON = 0.001f;
|
||||
|
||||
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||||
}
|
||||
|
||||
bool mat4Equal(const Mat4& a, const Mat4& b, f32 eps = EPSILON) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
if (std::abs(a[i][j] - b[i][j]) > eps) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OrthoCam 基本测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, Create) {
|
||||
OrthoCam cam;
|
||||
|
||||
TEST_ASSERT_TRUE(cam.type() == CamType::Ortho);
|
||||
TEST_ASSERT_TRUE(vec2Equal(Vec2(0, 0), Vec2(cam.position().x, cam.position().y)));
|
||||
TEST_ASSERT_TRUE(std::abs(cam.zoom() - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, SetPosition) {
|
||||
OrthoCam cam;
|
||||
|
||||
cam.setPosition(Vec2(100, 200));
|
||||
TEST_ASSERT_TRUE(vec2Equal(Vec2(100, 200), Vec2(cam.position().x, cam.position().y)));
|
||||
|
||||
cam.setPosition(Vec2(50, 75));
|
||||
TEST_ASSERT_TRUE(vec2Equal(Vec2(50, 75), Vec2(cam.position().x, cam.position().y)));
|
||||
}
|
||||
|
||||
TEST(OrthoCam, SetZoom) {
|
||||
OrthoCam cam;
|
||||
|
||||
cam.setZoom(2.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.zoom() - 2.0f) < EPSILON);
|
||||
|
||||
cam.setZoom(0.5f);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.zoom() - 0.5f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, SetRotation) {
|
||||
OrthoCam cam;
|
||||
|
||||
cam.setRotation(45.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.rotation() - 45.0f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 正交投影设置测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, Ortho) {
|
||||
OrthoCam cam;
|
||||
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(cam.left() - (-640)) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.right() - 640) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.bottom() - (-360)) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.top() - 360) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, SetOrthoFromSize) {
|
||||
OrthoCam cam;
|
||||
|
||||
cam.setOrthoFromSize(1280, 720);
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(cam.width() - 1280) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.height() - 720) < EPSILON);
|
||||
TEST_ASSERT_TRUE(vec2Equal(cam.center(), Vec2(0, 0)));
|
||||
}
|
||||
|
||||
TEST(OrthoCam, SetCenter) {
|
||||
OrthoCam cam;
|
||||
cam.setOrthoFromSize(1280, 720);
|
||||
|
||||
cam.setCenter(100, 200);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(cam.center(), Vec2(100, 200)));
|
||||
TEST_ASSERT_TRUE(std::abs(cam.left() - (100 - 640)) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(cam.right() - (100 + 640)) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 视图矩阵测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, ViewMatrixIdentity) {
|
||||
OrthoCam cam;
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setRotation(0);
|
||||
cam.setZoom(1.0f);
|
||||
|
||||
Mat4 view = cam.view();
|
||||
Mat4 identity(1.0f);
|
||||
|
||||
TEST_ASSERT_TRUE(mat4Equal(view, identity));
|
||||
}
|
||||
|
||||
TEST(OrthoCam, ViewMatrixTranslation) {
|
||||
OrthoCam cam;
|
||||
cam.setPosition(Vec2(100, 200));
|
||||
cam.setRotation(0);
|
||||
cam.setZoom(1.0f);
|
||||
|
||||
Mat4 view = cam.view();
|
||||
|
||||
Vec4 origin(0, 0, 0, 1);
|
||||
Vec4 transformed = view * origin;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - (-100)) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - (-200)) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, ViewMatrixZoom) {
|
||||
OrthoCam cam;
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setRotation(0);
|
||||
cam.setZoom(2.0f);
|
||||
|
||||
Mat4 view = cam.view();
|
||||
|
||||
Vec4 point(100, 100, 0, 1);
|
||||
Vec4 transformed = view * point;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - 200) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - 200) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 投影矩阵测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, ProjMatrix) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360, -1, 1);
|
||||
|
||||
Mat4 proj = cam.proj();
|
||||
|
||||
Vec4 corner1(-640, -360, 0, 1);
|
||||
Vec4 transformed1 = proj * corner1;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed1.x - (-1)) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed1.y - (-1)) < EPSILON);
|
||||
|
||||
Vec4 corner2(640, 360, 0, 1);
|
||||
Vec4 transformed2 = proj * corner2;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed2.x - 1) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed2.y - 1) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, ProjMatrixWithZoom) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.setZoom(2.0f);
|
||||
|
||||
Mat4 proj = cam.proj();
|
||||
|
||||
Vec4 center(0, 0, 0, 1);
|
||||
Vec4 transformed = proj * center;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 坐标转换测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, ScreenToWorld) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.viewport(0, 0, 1280, 720);
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setZoom(1.0f);
|
||||
|
||||
Vec2 screenCenter(640, 360);
|
||||
Vec2 worldPos = cam.screenToWorld(screenCenter);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(worldPos, Vec2(0, 0)));
|
||||
}
|
||||
|
||||
TEST(OrthoCam, ScreenToWorldCorner) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.viewport(0, 0, 1280, 720);
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setZoom(1.0f);
|
||||
|
||||
Vec2 screenTopLeft(0, 0);
|
||||
Vec2 worldPos = cam.screenToWorld(screenTopLeft);
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(worldPos.x - (-640)) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(worldPos.y - 360) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, WorldToScreen) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.viewport(0, 0, 1280, 720);
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setZoom(1.0f);
|
||||
|
||||
Vec2 worldCenter(0, 0);
|
||||
Vec2 screenPos = cam.worldToScreen(worldCenter);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(screenPos, Vec2(640, 360)));
|
||||
}
|
||||
|
||||
TEST(OrthoCam, WorldToScreenCorner) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.viewport(0, 0, 1280, 720);
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setZoom(1.0f);
|
||||
|
||||
Vec2 worldTopLeft(-640, 360);
|
||||
Vec2 screenPos = cam.worldToScreen(worldTopLeft);
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(screenPos.x) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(screenPos.y) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(OrthoCam, ScreenToWorldRoundTrip) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.viewport(0, 0, 1280, 720);
|
||||
cam.setPosition(Vec2(100, 200));
|
||||
cam.setZoom(1.5f);
|
||||
|
||||
Vec2 originalWorld(300, 400);
|
||||
Vec2 screen = cam.worldToScreen(originalWorld);
|
||||
Vec2 backToWorld = cam.screenToWorld(screen);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(originalWorld, backToWorld, 0.1f));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 视口测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, SetViewport) {
|
||||
OrthoCam cam;
|
||||
|
||||
cam.viewport(100, 200, 800, 600);
|
||||
|
||||
Vec2 vpPos = cam.viewportPos();
|
||||
Vec2 vpSize = cam.viewportSize();
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(vpPos.x - 100) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vpPos.y - 200) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vpSize.x - 800) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vpSize.y - 600) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 脏标记测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, DirtyFlag) {
|
||||
OrthoCam cam;
|
||||
|
||||
TEST_ASSERT_TRUE(cam.dirty());
|
||||
|
||||
cam.setDirty(false);
|
||||
TEST_ASSERT_FALSE(cam.dirty());
|
||||
|
||||
cam.setPosition(Vec2(100, 100));
|
||||
TEST_ASSERT_TRUE(cam.dirty());
|
||||
|
||||
cam.setDirty(false);
|
||||
TEST_ASSERT_FALSE(cam.dirty());
|
||||
|
||||
cam.setZoom(2.0f);
|
||||
TEST_ASSERT_TRUE(cam.dirty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VP 矩阵测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(OrthoCam, VPMatrix) {
|
||||
OrthoCam cam;
|
||||
cam.ortho(-640, 640, -360, 360);
|
||||
cam.viewport(0, 0, 1280, 720);
|
||||
cam.setPosition(Vec2(0, 0));
|
||||
cam.setRotation(0);
|
||||
cam.setZoom(1.0f);
|
||||
cam.setDirty(false);
|
||||
|
||||
Mat4 vp = cam.vp();
|
||||
|
||||
Mat4 view = cam.view();
|
||||
Mat4 proj = cam.proj();
|
||||
Mat4 expectedVP = proj * view;
|
||||
|
||||
TEST_ASSERT_TRUE(mat4Equal(vp, expectedVP));
|
||||
}
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
/**
|
||||
* @file test_node.cpp
|
||||
* @brief Node 和 Transform 系统单元测试
|
||||
*
|
||||
* 测试节点的变换矩阵计算、父子关系、组件系统等功能。
|
||||
*/
|
||||
|
||||
#include "test_framework.h"
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/node/scene_graph.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <cmath>
|
||||
|
||||
using namespace extra2d;
|
||||
using namespace extra2d::test;
|
||||
|
||||
namespace {
|
||||
constexpr f32 EPSILON = 0.0001f;
|
||||
|
||||
bool mat3Equal(const Mat3& a, const Mat3& b, f32 eps = EPSILON) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
if (std::abs(a[i][j] - b[i][j]) > eps) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Node 基本测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Node, Create) {
|
||||
auto node = ptr::make<Node>();
|
||||
TEST_ASSERT_NOT_NULL(node.get());
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(0, 0)));
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(1, 1)));
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot) < EPSILON);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0.5f, 0.5f)));
|
||||
}
|
||||
|
||||
TEST(Node, SetPosition) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
node->setPos(100, 200);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 200)));
|
||||
|
||||
node->setPos(Vec2(50, 75));
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 75)));
|
||||
}
|
||||
|
||||
TEST(Node, SetScale) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
node->setScale(2.0f, 3.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 3)));
|
||||
|
||||
node->setScale(Vec2(1.5f, 2.5f));
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(1.5f, 2.5f)));
|
||||
|
||||
node->setScale(2.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 2)));
|
||||
}
|
||||
|
||||
TEST(Node, SetRotation) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
node->setRot(45.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - 45.0f) < EPSILON);
|
||||
|
||||
node->setRot(-30.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(node->rot - (-30.0f)) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Node, SetAnchor) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
node->setAnchor(0.0f, 0.0f);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0, 0)));
|
||||
|
||||
node->setAnchor(Anchor::TopRight);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(1, 1)));
|
||||
|
||||
node->setAnchor(Anchor::Center);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0.5f, 0.5f)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transform 矩阵测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Node, LocalMatrixIdentity) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
Mat3 local = node->local();
|
||||
Mat3 identity(1.0f);
|
||||
|
||||
TEST_ASSERT_TRUE(mat3Equal(local, identity));
|
||||
}
|
||||
|
||||
TEST(Node, LocalMatrixTranslation) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(100, 50);
|
||||
|
||||
Mat3 local = node->local();
|
||||
|
||||
glm::vec3 origin(0, 0, 1.0f);
|
||||
glm::vec3 transformed = local * origin;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - 100) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - 50) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Node, LocalMatrixScale) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setScale(2.0f, 3.0f);
|
||||
|
||||
Mat3 local = node->local();
|
||||
|
||||
glm::vec3 point(10, 10, 1.0f);
|
||||
glm::vec3 transformed = local * point;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - 20) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - 30) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Node, LocalMatrixRotation) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setRot(90.0f);
|
||||
|
||||
Mat3 local = node->local();
|
||||
|
||||
glm::vec3 point(1, 0, 1.0f);
|
||||
glm::vec3 transformed = local * point;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - 0.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Node, LocalMatrixCombined) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->setPos(100, 100);
|
||||
node->setScale(2.0f, 2.0f);
|
||||
node->setRot(0.0f);
|
||||
|
||||
Mat3 local = node->local();
|
||||
|
||||
glm::vec3 point(50, 50, 1.0f);
|
||||
glm::vec3 transformed = local * point;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - 200) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - 200) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 父子关系测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Node, AddChild) {
|
||||
auto parent = ptr::make<Node>();
|
||||
auto child = ptr::make<Node>();
|
||||
|
||||
parent->addChild(child);
|
||||
|
||||
TEST_ASSERT_EQ(parent.get(), child->parent());
|
||||
TEST_ASSERT_EQ(1u, parent->children().size());
|
||||
TEST_ASSERT_EQ(child.get(), parent->children()[0].get());
|
||||
}
|
||||
|
||||
TEST(Node, RemoveChild) {
|
||||
auto parent = ptr::make<Node>();
|
||||
auto child = ptr::make<Node>();
|
||||
|
||||
parent->addChild(child);
|
||||
parent->removeChild(child);
|
||||
|
||||
TEST_ASSERT_NULL(child->parent());
|
||||
TEST_ASSERT_EQ(0u, parent->children().size());
|
||||
}
|
||||
|
||||
TEST(Node, RemoveFromParent) {
|
||||
auto parent = ptr::make<Node>();
|
||||
auto child = ptr::make<Node>();
|
||||
|
||||
parent->addChild(child);
|
||||
child->removeFromParent();
|
||||
|
||||
TEST_ASSERT_NULL(child->parent());
|
||||
TEST_ASSERT_EQ(0u, parent->children().size());
|
||||
}
|
||||
|
||||
TEST(Node, NestedHierarchy) {
|
||||
auto root = ptr::make<Node>();
|
||||
auto child1 = ptr::make<Node>();
|
||||
auto child2 = ptr::make<Node>();
|
||||
auto grandchild = ptr::make<Node>();
|
||||
|
||||
root->addChild(child1);
|
||||
root->addChild(child2);
|
||||
child1->addChild(grandchild);
|
||||
|
||||
TEST_ASSERT_EQ(root.get(), child1->parent());
|
||||
TEST_ASSERT_EQ(root.get(), child2->parent());
|
||||
TEST_ASSERT_EQ(child1.get(), grandchild->parent());
|
||||
TEST_ASSERT_EQ(2u, root->children().size());
|
||||
TEST_ASSERT_EQ(1u, child1->children().size());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 世界变换测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Node, WorldPosition) {
|
||||
auto parent = ptr::make<Node>();
|
||||
parent->setPos(100, 100);
|
||||
|
||||
auto child = ptr::make<Node>();
|
||||
child->setPos(50, 50);
|
||||
|
||||
parent->addChild(child);
|
||||
|
||||
Vec2 worldPos = child->worldPos();
|
||||
TEST_ASSERT_TRUE(vec2Equal(worldPos, Vec2(150, 150)));
|
||||
}
|
||||
|
||||
TEST(Node, WorldScale) {
|
||||
auto parent = ptr::make<Node>();
|
||||
parent->setScale(2.0f, 2.0f);
|
||||
|
||||
auto child = ptr::make<Node>();
|
||||
child->setScale(1.5f, 1.5f);
|
||||
|
||||
parent->addChild(child);
|
||||
|
||||
Vec2 worldScale = child->worldScale();
|
||||
TEST_ASSERT_TRUE(vec2Equal(worldScale, Vec2(3.0f, 3.0f)));
|
||||
}
|
||||
|
||||
TEST(Node, WorldRotation) {
|
||||
auto parent = ptr::make<Node>();
|
||||
parent->setRot(45.0f);
|
||||
|
||||
auto child = ptr::make<Node>();
|
||||
child->setRot(30.0f);
|
||||
|
||||
parent->addChild(child);
|
||||
|
||||
f32 worldRot = child->worldRot();
|
||||
TEST_ASSERT_TRUE(std::abs(worldRot - 75.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(Node, WorldMatrix4x4) {
|
||||
auto parent = ptr::make<Node>();
|
||||
parent->setPos(100, 100);
|
||||
|
||||
auto child = ptr::make<Node>();
|
||||
child->setPos(50, 50);
|
||||
|
||||
parent->addChild(child);
|
||||
|
||||
Mat4 world4x4 = child->world4x4();
|
||||
|
||||
Vec4 origin(0, 0, 0, 1);
|
||||
Vec4 transformed = world4x4 * origin;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x - 150.0f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y - 150.0f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 组件系统测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class TestComp : public Comp {
|
||||
public:
|
||||
const char* type() const override { return "TestComp"; }
|
||||
|
||||
void update(f32 dt) override {
|
||||
updateCount++;
|
||||
lastDt = dt;
|
||||
}
|
||||
|
||||
int updateCount = 0;
|
||||
f32 lastDt = 0.0f;
|
||||
};
|
||||
|
||||
TEST(Node, AddComponent) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
TestComp* comp = node->add<TestComp>();
|
||||
|
||||
TEST_ASSERT_NOT_NULL(comp);
|
||||
TEST_ASSERT_EQ(node.get(), comp->owner());
|
||||
}
|
||||
|
||||
TEST(Node, GetComponent) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->add<TestComp>();
|
||||
|
||||
TestComp* comp = node->get<TestComp>();
|
||||
|
||||
TEST_ASSERT_NOT_NULL(comp);
|
||||
}
|
||||
|
||||
TEST(Node, RemoveComponent) {
|
||||
auto node = ptr::make<Node>();
|
||||
node->add<TestComp>();
|
||||
|
||||
node->remove<TestComp>();
|
||||
|
||||
TestComp* comp = node->get<TestComp>();
|
||||
TEST_ASSERT_NULL(comp);
|
||||
}
|
||||
|
||||
TEST(Node, UpdateComponents) {
|
||||
auto node = ptr::make<Node>();
|
||||
TestComp* comp = node->add<TestComp>();
|
||||
|
||||
node->updateComps(0.016f);
|
||||
|
||||
TEST_ASSERT_EQ(1, comp->updateCount);
|
||||
TEST_ASSERT_TRUE(std::abs(comp->lastDt - 0.016f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 可见性和标签测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Node, Visibility) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
TEST_ASSERT_TRUE(node->visible());
|
||||
|
||||
node->setVisible(false);
|
||||
TEST_ASSERT_FALSE(node->visible());
|
||||
|
||||
node->setVisible(true);
|
||||
TEST_ASSERT_TRUE(node->visible());
|
||||
}
|
||||
|
||||
TEST(Node, Tag) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
TEST_ASSERT_EQ(0, node->tag());
|
||||
|
||||
node->setTag(42);
|
||||
TEST_ASSERT_EQ(42, node->tag());
|
||||
}
|
||||
|
||||
TEST(Node, Name) {
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
TEST_ASSERT_TRUE(node->name().empty());
|
||||
|
||||
node->setName("TestNode");
|
||||
TEST_ASSERT_EQ("TestNode", node->name());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 边界框测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(BoundingBox, Create) {
|
||||
BoundingBox box(Vec2(0, 0), Vec2(100, 100));
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(box.min, Vec2(0, 0)));
|
||||
TEST_ASSERT_TRUE(vec2Equal(box.max, Vec2(100, 100)));
|
||||
}
|
||||
|
||||
TEST(BoundingBox, FromCenter) {
|
||||
BoundingBox box = BoundingBox::fromCenter(Vec2(50, 50), Vec2(25, 25));
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(box.min, Vec2(25, 25)));
|
||||
TEST_ASSERT_TRUE(vec2Equal(box.max, Vec2(75, 75)));
|
||||
}
|
||||
|
||||
TEST(BoundingBox, Contains) {
|
||||
BoundingBox box(Vec2(0, 0), Vec2(100, 100));
|
||||
|
||||
TEST_ASSERT_TRUE(box.contains(Vec2(50, 50)));
|
||||
TEST_ASSERT_TRUE(box.contains(Vec2(0, 0)));
|
||||
TEST_ASSERT_TRUE(box.contains(Vec2(100, 100)));
|
||||
TEST_ASSERT_FALSE(box.contains(Vec2(150, 50)));
|
||||
}
|
||||
|
||||
TEST(BoundingBox, Intersects) {
|
||||
BoundingBox box1(Vec2(0, 0), Vec2(100, 100));
|
||||
BoundingBox box2(Vec2(50, 50), Vec2(150, 150));
|
||||
BoundingBox box3(Vec2(200, 200), Vec2(300, 300));
|
||||
|
||||
TEST_ASSERT_TRUE(box1.intersects(box2));
|
||||
TEST_ASSERT_FALSE(box1.intersects(box3));
|
||||
}
|
||||
|
||||
TEST(BoundingBox, Merged) {
|
||||
BoundingBox box1(Vec2(0, 0), Vec2(100, 100));
|
||||
BoundingBox box2(Vec2(50, 50), Vec2(150, 150));
|
||||
|
||||
BoundingBox merged = box1.merged(box2);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(merged.min, Vec2(0, 0)));
|
||||
TEST_ASSERT_TRUE(vec2Equal(merged.max, Vec2(150, 150)));
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
/**
|
||||
* @file test_scene.cpp
|
||||
* @brief Scene 管理单元测试
|
||||
*
|
||||
* 测试场景的生命周期、Director 场景管理、场景转换等功能。
|
||||
*/
|
||||
|
||||
#include "test_framework.h"
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/director.h>
|
||||
#include <extra2d/scene/trans.h>
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/cam/ortho_cam.h>
|
||||
#include <extra2d/act/act_move.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
|
||||
using namespace extra2d;
|
||||
using namespace extra2d::test;
|
||||
|
||||
namespace {
|
||||
constexpr f32 EPSILON = 0.001f;
|
||||
|
||||
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scene 基本测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class TestScene : public Scene {
|
||||
public:
|
||||
TestScene() {
|
||||
setName("TestScene");
|
||||
}
|
||||
|
||||
void onEnter() override { enterCount++; }
|
||||
void onExit() override { exitCount++; }
|
||||
void onPause() override { pauseCount++; }
|
||||
void onResume() override { resumeCount++; }
|
||||
|
||||
int enterCount = 0;
|
||||
int exitCount = 0;
|
||||
int pauseCount = 0;
|
||||
int resumeCount = 0;
|
||||
};
|
||||
|
||||
TEST(Scene, Create) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
TEST_ASSERT_EQ("TestScene", scene->name());
|
||||
TEST_ASSERT_FALSE(scene->running());
|
||||
TEST_ASSERT_NOT_NULL(scene->graph());
|
||||
TEST_ASSERT_NOT_NULL(scene->cam());
|
||||
}
|
||||
|
||||
TEST(Scene, SetName) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
scene->setName("MyScene");
|
||||
TEST_ASSERT_EQ("MyScene", scene->name());
|
||||
}
|
||||
|
||||
TEST(Scene, SetRunning) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
scene->setRunning(true);
|
||||
TEST_ASSERT_TRUE(scene->running());
|
||||
|
||||
scene->setRunning(false);
|
||||
TEST_ASSERT_FALSE(scene->running());
|
||||
}
|
||||
|
||||
TEST(Scene, LifecycleCallbacks) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
scene->onEnter();
|
||||
TEST_ASSERT_EQ(1, scene->enterCount);
|
||||
|
||||
scene->onExit();
|
||||
TEST_ASSERT_EQ(1, scene->exitCount);
|
||||
|
||||
scene->onPause();
|
||||
TEST_ASSERT_EQ(1, scene->pauseCount);
|
||||
|
||||
scene->onResume();
|
||||
TEST_ASSERT_EQ(1, scene->resumeCount);
|
||||
}
|
||||
|
||||
TEST(Scene, Update) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
auto node = scene->graph()->create<Node>();
|
||||
node->setName("TestNode");
|
||||
|
||||
scene->update(0.016f);
|
||||
|
||||
Node* found = scene->graph()->find("TestNode");
|
||||
TEST_ASSERT_NOT_NULL(found);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Director 基本测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Director, Create) {
|
||||
Director director;
|
||||
|
||||
TEST_ASSERT_NULL(director.cur());
|
||||
}
|
||||
|
||||
TEST(Director, RunScene) {
|
||||
Director director;
|
||||
|
||||
auto scene = ptr::make<TestScene>();
|
||||
director.run(scene);
|
||||
|
||||
TEST_ASSERT_EQ(scene.get(), director.cur());
|
||||
TEST_ASSERT_TRUE(scene->running());
|
||||
TEST_ASSERT_EQ(1, scene->enterCount);
|
||||
}
|
||||
|
||||
TEST(Director, ReplaceScene) {
|
||||
Director director;
|
||||
|
||||
auto scene1 = ptr::make<TestScene>();
|
||||
scene1->setName("Scene1");
|
||||
director.run(scene1);
|
||||
|
||||
auto scene2 = ptr::make<TestScene>();
|
||||
scene2->setName("Scene2");
|
||||
director.replace(scene2);
|
||||
|
||||
TEST_ASSERT_EQ(scene2.get(), director.cur());
|
||||
TEST_ASSERT_EQ(1, scene1->exitCount);
|
||||
TEST_ASSERT_EQ(1, scene2->enterCount);
|
||||
}
|
||||
|
||||
TEST(Director, PushPopScene) {
|
||||
Director director;
|
||||
|
||||
auto scene1 = ptr::make<TestScene>();
|
||||
scene1->setName("Scene1");
|
||||
director.run(scene1);
|
||||
|
||||
auto scene2 = ptr::make<TestScene>();
|
||||
scene2->setName("Scene2");
|
||||
director.push(scene2);
|
||||
|
||||
TEST_ASSERT_EQ(scene2.get(), director.cur());
|
||||
TEST_ASSERT_EQ(1, scene1->pauseCount);
|
||||
TEST_ASSERT_EQ(1, scene2->enterCount);
|
||||
|
||||
director.pop();
|
||||
|
||||
TEST_ASSERT_EQ(scene1.get(), director.cur());
|
||||
TEST_ASSERT_EQ(1, scene2->exitCount);
|
||||
TEST_ASSERT_EQ(1, scene1->resumeCount);
|
||||
}
|
||||
|
||||
TEST(Director, Update) {
|
||||
Director director;
|
||||
|
||||
auto scene = ptr::make<TestScene>();
|
||||
director.run(scene);
|
||||
|
||||
auto node = scene->graph()->create<Node>();
|
||||
node->setPos(0, 0);
|
||||
node->run(new MoveTo(1.0f, Vec2(100, 0)));
|
||||
|
||||
director.update(0.5f);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 0)));
|
||||
}
|
||||
|
||||
TEST(Director, SceneStack) {
|
||||
Director director;
|
||||
|
||||
auto scene1 = ptr::make<TestScene>();
|
||||
scene1->setName("Scene1");
|
||||
director.run(scene1);
|
||||
|
||||
auto scene2 = ptr::make<TestScene>();
|
||||
scene2->setName("Scene2");
|
||||
director.push(scene2);
|
||||
|
||||
auto scene3 = ptr::make<TestScene>();
|
||||
scene3->setName("Scene3");
|
||||
director.push(scene3);
|
||||
|
||||
TEST_ASSERT_EQ(scene3.get(), director.cur());
|
||||
|
||||
director.pop();
|
||||
TEST_ASSERT_EQ(scene2.get(), director.cur());
|
||||
|
||||
director.pop();
|
||||
TEST_ASSERT_EQ(scene1.get(), director.cur());
|
||||
|
||||
director.pop();
|
||||
TEST_ASSERT_NULL(director.cur());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trans 场景转换测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(TransFade, Create) {
|
||||
auto scene1 = ptr::make<TestScene>();
|
||||
auto scene2 = ptr::make<TestScene>();
|
||||
|
||||
TransFade trans;
|
||||
trans.init(1.0f, scene1, scene2);
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(trans.duration() - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(TransFade, Progress) {
|
||||
auto scene1 = ptr::make<TestScene>();
|
||||
auto scene2 = ptr::make<TestScene>();
|
||||
|
||||
TransFade trans;
|
||||
trans.init(2.0f, scene1, scene2);
|
||||
|
||||
trans.update(1.0f);
|
||||
TEST_ASSERT_TRUE(std::abs(trans.progress() - 0.5f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 场景图集成测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Scene, GraphIntegration) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
auto parent = scene->graph()->create<Node>();
|
||||
parent->setName("Parent");
|
||||
parent->setPos(100, 100);
|
||||
|
||||
auto child = ptr::make<Node>();
|
||||
child->setName("Child");
|
||||
child->setPos(50, 50);
|
||||
parent->addChild(child);
|
||||
|
||||
Vec2 worldPos = child->worldPos();
|
||||
TEST_ASSERT_TRUE(vec2Equal(worldPos, Vec2(150, 150)));
|
||||
}
|
||||
|
||||
TEST(Scene, CameraIntegration) {
|
||||
auto scene = ptr::make<TestScene>();
|
||||
|
||||
auto cam = dynamic_cast<OrthoCam*>(scene->cam());
|
||||
TEST_ASSERT_NOT_NULL(cam);
|
||||
|
||||
cam->setOrthoFromSize(1280, 720);
|
||||
cam->setPosition(Vec2(0, 0));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 多场景测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Director, MultipleSceneSwitches) {
|
||||
Director director;
|
||||
|
||||
auto scene1 = ptr::make<TestScene>();
|
||||
scene1->setName("Scene1");
|
||||
director.run(scene1);
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
auto newScene = ptr::make<TestScene>();
|
||||
newScene->setName("Scene" + std::to_string(i + 2));
|
||||
director.replace(newScene);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQ(5, scene1->exitCount);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 场景渲染测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class RenderTestScene : public Scene {
|
||||
public:
|
||||
RenderTestScene() { setName("RenderTestScene"); }
|
||||
|
||||
void render(RenderCmdBuffer& cmdBuffer) override {
|
||||
renderCalled = true;
|
||||
}
|
||||
|
||||
bool renderCalled = false;
|
||||
};
|
||||
|
||||
TEST(Scene, RenderCallback) {
|
||||
auto scene = ptr::make<RenderTestScene>();
|
||||
RenderCmdBuffer cmdBuffer;
|
||||
|
||||
scene->render(cmdBuffer);
|
||||
TEST_ASSERT_TRUE(scene->renderCalled);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 边界情况测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(Director, ReplaceWhenNoRunning) {
|
||||
Director director;
|
||||
|
||||
auto scene = ptr::make<TestScene>();
|
||||
director.replace(scene);
|
||||
|
||||
TEST_ASSERT_EQ(scene.get(), director.cur());
|
||||
}
|
||||
|
||||
TEST(Director, PopWhenEmpty) {
|
||||
Director director;
|
||||
|
||||
director.pop();
|
||||
|
||||
TEST_ASSERT_NULL(director.cur());
|
||||
}
|
||||
|
||||
TEST(Director, PushWhenNoRunning) {
|
||||
Director director;
|
||||
|
||||
auto scene = ptr::make<TestScene>();
|
||||
director.push(scene);
|
||||
|
||||
TEST_ASSERT_EQ(scene.get(), director.cur());
|
||||
}
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
/**
|
||||
* @file test_scene_graph.cpp
|
||||
* @brief SceneGraph 单元测试
|
||||
*
|
||||
* 测试场景图的节点管理、遍历、查找等功能。
|
||||
*/
|
||||
|
||||
#include "test_framework.h"
|
||||
#include <extra2d/node/node.h>
|
||||
#include <extra2d/node/scene_graph.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
|
||||
using namespace extra2d;
|
||||
using namespace extra2d::test;
|
||||
|
||||
namespace {
|
||||
constexpr f32 EPSILON = 0.0001f;
|
||||
|
||||
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SceneGraph 基本测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(SceneGraph, Create) {
|
||||
SceneGraph graph;
|
||||
|
||||
TEST_ASSERT_EQ(0u, graph.nodeCount());
|
||||
TEST_ASSERT_TRUE(graph.roots().empty());
|
||||
}
|
||||
|
||||
TEST(SceneGraph, CreateNode) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node = graph.create<Node>();
|
||||
|
||||
TEST_ASSERT_NOT_NULL(node.get());
|
||||
TEST_ASSERT_EQ(1u, graph.nodeCount());
|
||||
TEST_ASSERT_EQ(node.get(), graph.roots()[0].get());
|
||||
}
|
||||
|
||||
TEST(SceneGraph, AddNode) {
|
||||
SceneGraph graph;
|
||||
auto node = ptr::make<Node>();
|
||||
|
||||
graph.add(node);
|
||||
|
||||
TEST_ASSERT_EQ(1u, graph.nodeCount());
|
||||
TEST_ASSERT_EQ(node.get(), graph.roots()[0].get());
|
||||
}
|
||||
|
||||
TEST(SceneGraph, RemoveNode) {
|
||||
SceneGraph graph;
|
||||
auto node = graph.create<Node>();
|
||||
|
||||
graph.remove(node);
|
||||
|
||||
TEST_ASSERT_EQ(0u, graph.nodeCount());
|
||||
}
|
||||
|
||||
TEST(SceneGraph, Clear) {
|
||||
SceneGraph graph;
|
||||
|
||||
graph.create<Node>();
|
||||
graph.create<Node>();
|
||||
graph.create<Node>();
|
||||
|
||||
TEST_ASSERT_EQ(3u, graph.nodeCount());
|
||||
|
||||
graph.clear();
|
||||
|
||||
TEST_ASSERT_EQ(0u, graph.nodeCount());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 节点查找测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(SceneGraph, FindByName) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node1 = graph.create<Node>();
|
||||
node1->setName("Node1");
|
||||
|
||||
auto node2 = graph.create<Node>();
|
||||
node2->setName("Node2");
|
||||
|
||||
auto node3 = graph.create<Node>();
|
||||
node3->setName("Node3");
|
||||
|
||||
Node* found = graph.find("Node2");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(found);
|
||||
TEST_ASSERT_EQ(node2.get(), found);
|
||||
}
|
||||
|
||||
TEST(SceneGraph, FindByNameNotFound) {
|
||||
SceneGraph graph;
|
||||
|
||||
graph.create<Node>()->setName("Node1");
|
||||
graph.create<Node>()->setName("Node2");
|
||||
|
||||
Node* found = graph.find("NonExistent");
|
||||
|
||||
TEST_ASSERT_NULL(found);
|
||||
}
|
||||
|
||||
TEST(SceneGraph, FindByTag) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node1 = graph.create<Node>();
|
||||
node1->setTag(1);
|
||||
|
||||
auto node2 = graph.create<Node>();
|
||||
node2->setTag(2);
|
||||
|
||||
auto node3 = graph.create<Node>();
|
||||
node3->setTag(3);
|
||||
|
||||
Node* found = graph.findByTag(2);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(found);
|
||||
TEST_ASSERT_EQ(node2.get(), found);
|
||||
}
|
||||
|
||||
TEST(SceneGraph, FindByTagNotFound) {
|
||||
SceneGraph graph;
|
||||
|
||||
graph.create<Node>()->setTag(1);
|
||||
graph.create<Node>()->setTag(2);
|
||||
|
||||
Node* found = graph.findByTag(999);
|
||||
|
||||
TEST_ASSERT_NULL(found);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 遍历测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(SceneGraph, Traverse) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node1 = graph.create<Node>();
|
||||
node1->setName("Node1");
|
||||
|
||||
auto node2 = graph.create<Node>();
|
||||
node2->setName("Node2");
|
||||
|
||||
auto child = ptr::make<Node>();
|
||||
child->setName("Child");
|
||||
node1->addChild(child);
|
||||
|
||||
std::vector<std::string> visited;
|
||||
graph.traverse([&visited](Node* node) {
|
||||
visited.push_back(node->name());
|
||||
});
|
||||
|
||||
TEST_ASSERT_EQ(3u, visited.size());
|
||||
|
||||
bool foundNode1 = false, foundNode2 = false, foundChild = false;
|
||||
for (const auto& name : visited) {
|
||||
if (name == "Node1") foundNode1 = true;
|
||||
if (name == "Node2") foundNode2 = true;
|
||||
if (name == "Child") foundChild = true;
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE(foundNode1);
|
||||
TEST_ASSERT_TRUE(foundNode2);
|
||||
TEST_ASSERT_TRUE(foundChild);
|
||||
}
|
||||
|
||||
TEST(SceneGraph, TraverseRecursive) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto root = graph.create<Node>();
|
||||
root->setName("Root");
|
||||
|
||||
auto child1 = ptr::make<Node>();
|
||||
child1->setName("Child1");
|
||||
root->addChild(child1);
|
||||
|
||||
auto grandchild = ptr::make<Node>();
|
||||
grandchild->setName("Grandchild");
|
||||
child1->addChild(grandchild);
|
||||
|
||||
std::vector<std::string> visited;
|
||||
graph.traverseRecursive(root.get(), [&visited](Node* node) {
|
||||
visited.push_back(node->name());
|
||||
});
|
||||
|
||||
TEST_ASSERT_EQ(3u, visited.size());
|
||||
TEST_ASSERT_EQ("Root", visited[0]);
|
||||
TEST_ASSERT_EQ("Child1", visited[1]);
|
||||
TEST_ASSERT_EQ("Grandchild", visited[2]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 更新测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class TestUpdateNode : public Node {
|
||||
public:
|
||||
void onUpdate(f32 dt) override {
|
||||
updateCount++;
|
||||
lastDt = dt;
|
||||
}
|
||||
|
||||
int updateCount = 0;
|
||||
f32 lastDt = 0.0f;
|
||||
};
|
||||
|
||||
TEST(SceneGraph, Update) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node = graph.create<TestUpdateNode>();
|
||||
|
||||
graph.update(0.016f);
|
||||
|
||||
TEST_ASSERT_EQ(1, node->updateCount);
|
||||
TEST_ASSERT_TRUE(std::abs(node->lastDt - 0.016f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(SceneGraph, UpdateMultipleNodes) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node1 = graph.create<TestUpdateNode>();
|
||||
auto node2 = graph.create<TestUpdateNode>();
|
||||
auto node3 = graph.create<TestUpdateNode>();
|
||||
|
||||
graph.update(0.033f);
|
||||
|
||||
TEST_ASSERT_EQ(1, node1->updateCount);
|
||||
TEST_ASSERT_EQ(1, node2->updateCount);
|
||||
TEST_ASSERT_EQ(1, node3->updateCount);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 层级结构测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(SceneGraph, HierarchyWithChildren) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto parent = graph.create<Node>();
|
||||
parent->setName("Parent");
|
||||
|
||||
auto child1 = ptr::make<Node>();
|
||||
child1->setName("Child1");
|
||||
parent->addChild(child1);
|
||||
|
||||
auto child2 = ptr::make<Node>();
|
||||
child2->setName("Child2");
|
||||
parent->addChild(child2);
|
||||
|
||||
TEST_ASSERT_EQ(1u, graph.nodeCount());
|
||||
TEST_ASSERT_EQ(2u, parent->children().size());
|
||||
}
|
||||
|
||||
TEST(SceneGraph, DeepHierarchy) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto root = graph.create<Node>();
|
||||
root->setPos(100, 100);
|
||||
|
||||
auto level1 = ptr::make<Node>();
|
||||
level1->setPos(50, 50);
|
||||
root->addChild(level1);
|
||||
|
||||
auto level2 = ptr::make<Node>();
|
||||
level2->setPos(25, 25);
|
||||
level1->addChild(level2);
|
||||
|
||||
auto level3 = ptr::make<Node>();
|
||||
level3->setPos(10, 10);
|
||||
level2->addChild(level3);
|
||||
|
||||
Vec2 worldPos = level3->worldPos();
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(worldPos, Vec2(185, 185)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 自定义节点类型测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class CustomNode : public Node {
|
||||
public:
|
||||
CustomNode() : value(0) {}
|
||||
explicit CustomNode(int v) : value(v) {}
|
||||
|
||||
int value;
|
||||
|
||||
protected:
|
||||
Vec2 defaultAnchor() const override { return Vec2(0.0f, 0.0f); }
|
||||
};
|
||||
|
||||
TEST(SceneGraph, CreateCustomNode) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node = graph.create<CustomNode>(42);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(node.get());
|
||||
TEST_ASSERT_EQ(42, node->value);
|
||||
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0, 0)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 节点可见性测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(SceneGraph, NodeVisibility) {
|
||||
SceneGraph graph;
|
||||
|
||||
auto node = graph.create<Node>();
|
||||
TEST_ASSERT_TRUE(node->visible());
|
||||
|
||||
node->setVisible(false);
|
||||
TEST_ASSERT_FALSE(node->visible());
|
||||
}
|
||||
|
|
@ -0,0 +1,436 @@
|
|||
/**
|
||||
* @file test_win_adapt.cpp
|
||||
* @brief Window Adaptation 单元测试
|
||||
*
|
||||
* 测试窗口适配模式、坐标转换、视口计算等功能。
|
||||
*/
|
||||
|
||||
#include "test_framework.h"
|
||||
#include <extra2d/win/win_adapt.h>
|
||||
#include <extra2d/core/math_extended.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <cmath>
|
||||
|
||||
using namespace extra2d;
|
||||
using namespace extra2d::test;
|
||||
|
||||
namespace {
|
||||
constexpr f32 EPSILON = 0.001f;
|
||||
|
||||
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||||
}
|
||||
|
||||
bool rectEqual(const Rect& a, const Rect& b, f32 eps = EPSILON) {
|
||||
return std::abs(a.origin.x - b.origin.x) < eps &&
|
||||
std::abs(a.origin.y - b.origin.y) < eps &&
|
||||
std::abs(a.size.width - b.size.width) < eps &&
|
||||
std::abs(a.size.height - b.size.height) < eps;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WinAdapt 基本测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, Create) {
|
||||
WinAdapt adapt;
|
||||
|
||||
WinAdaptCfg cfg = adapt.cfg();
|
||||
TEST_ASSERT_TRUE(cfg.mode == AdaptMode::Fit);
|
||||
TEST_ASSERT_TRUE(std::abs(cfg.refW - 1280) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(cfg.refH - 720) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, SetConfig) {
|
||||
WinAdapt adapt;
|
||||
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fill;
|
||||
cfg.refW = 1920;
|
||||
cfg.refH = 1080;
|
||||
cfg.autoScale = false;
|
||||
|
||||
adapt.cfg(cfg);
|
||||
|
||||
WinAdaptCfg retrieved = adapt.cfg();
|
||||
TEST_ASSERT_TRUE(retrieved.mode == AdaptMode::Fill);
|
||||
TEST_ASSERT_TRUE(std::abs(retrieved.refW - 1920) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(retrieved.refH - 1080) < EPSILON);
|
||||
TEST_ASSERT_FALSE(retrieved.autoScale);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, SetRef) {
|
||||
WinAdapt adapt;
|
||||
|
||||
adapt.ref(800, 600);
|
||||
|
||||
Vec2 ref = adapt.ref();
|
||||
TEST_ASSERT_TRUE(vec2Equal(ref, Vec2(800, 600)));
|
||||
}
|
||||
|
||||
TEST(WinAdapt, SetWin) {
|
||||
WinAdapt adapt;
|
||||
adapt.ref(1280, 720);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Vec2 win = adapt.win();
|
||||
TEST_ASSERT_TRUE(vec2Equal(win, Vec2(1920, 1080)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Exact 模式测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, ExactMode) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Exact;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
TEST_ASSERT_TRUE(rectEqual(vp, Rect(0, 0, 1920, 1080)));
|
||||
TEST_ASSERT_TRUE(std::abs(adapt.scale() - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stretch 模式测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, StretchMode) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Stretch;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
TEST_ASSERT_TRUE(rectEqual(vp, Rect(0, 0, 1920, 1080)));
|
||||
|
||||
Vec2 scale2D = adapt.scale2D();
|
||||
TEST_ASSERT_TRUE(std::abs(scale2D.x - 1.5f) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(scale2D.y - 1.5f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fit 模式测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, FitModeSameAspect) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
TEST_ASSERT_TRUE(rectEqual(vp, Rect(0, 0, 1920, 1080)));
|
||||
TEST_ASSERT_TRUE(std::abs(adapt.scale() - 1.5f) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, FitModeWiderWindow) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 800);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
f32 expectedScale = 800.0f / 720.0f;
|
||||
f32 expectedW = 1280.0f * expectedScale;
|
||||
f32 expectedX = (1920.0f - expectedW) / 2.0f;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.x - expectedX) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.y) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.width - expectedW) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.height - 800) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(adapt.scale() - expectedScale) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, FitModeTallerWindow) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1280, 900);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
f32 expectedScale = 1280.0f / 1280.0f;
|
||||
f32 expectedH = 720.0f * expectedScale;
|
||||
f32 expectedY = (900.0f - expectedH) / 2.0f;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.x) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.y - expectedY) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.width - 1280) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.height - expectedH) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fill 模式测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, FillModeWiderWindow) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fill;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 800);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
f32 expectedScale = 1920.0f / 1280.0f;
|
||||
f32 expectedH = 720.0f * expectedScale;
|
||||
f32 expectedY = (800.0f - expectedH) / 2.0f;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.x) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.y - expectedY) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(adapt.scale() - expectedScale) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, FillModeTallerWindow) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fill;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1280, 900);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
f32 expectedScale = 900.0f / 720.0f;
|
||||
f32 expectedW = 1280.0f * expectedScale;
|
||||
f32 expectedX = (1280.0f - expectedW) / 2.0f;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.x - expectedX) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.y) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(adapt.scale() - expectedScale) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Center 模式测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, CenterMode) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Center;
|
||||
cfg.refW = 800;
|
||||
cfg.refH = 600;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
f32 expectedX = (1920.0f - 800.0f) / 2.0f;
|
||||
f32 expectedY = (1080.0f - 600.0f) / 2.0f;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.x - expectedX) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.origin.y - expectedY) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.width - 800) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.height - 600) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(adapt.scale() - 1.0f) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 坐标转换测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, WinToRef) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Vec2 winPos(960, 540);
|
||||
Vec2 refPos = adapt.winToRef(winPos);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(refPos, Vec2(640, 360)));
|
||||
}
|
||||
|
||||
TEST(WinAdapt, RefToWin) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Vec2 refPos(640, 360);
|
||||
Vec2 winPos = adapt.refToWin(refPos);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(winPos, Vec2(960, 540)));
|
||||
}
|
||||
|
||||
TEST(WinAdapt, WinToRefRoundTrip) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1600, 900);
|
||||
|
||||
Vec2 originalRef(500, 300);
|
||||
Vec2 win = adapt.refToWin(originalRef);
|
||||
Vec2 backToRef = adapt.winToRef(win);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(originalRef, backToRef, 0.1f));
|
||||
}
|
||||
|
||||
TEST(WinAdapt, ScreenToRef) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Vec2 screenPos(960, 540);
|
||||
Vec2 refPos = adapt.screenToRef(screenPos);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(refPos, Vec2(640, 360)));
|
||||
}
|
||||
|
||||
TEST(WinAdapt, RefToScreen) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Vec2 refPos(640, 360);
|
||||
Vec2 screenPos = adapt.refToScreen(refPos);
|
||||
|
||||
TEST_ASSERT_TRUE(vec2Equal(screenPos, Vec2(960, 540)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 矩阵测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, Matrix) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(1920, 1080);
|
||||
|
||||
Mat4 m = adapt.matrix();
|
||||
|
||||
Vec4 origin(0, 0, 0, 1);
|
||||
Vec4 transformed = m * origin;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.x) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(transformed.y) < EPSILON);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 边界情况测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, ZeroWindowSize) {
|
||||
WinAdapt adapt;
|
||||
adapt.ref(1280, 720);
|
||||
|
||||
adapt.win(0, 0);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.width) < EPSILON);
|
||||
TEST_ASSERT_TRUE(std::abs(vp.size.height) < EPSILON);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, VerySmallWindow) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(100, 100);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
TEST_ASSERT_TRUE(vp.size.width > 0);
|
||||
TEST_ASSERT_TRUE(vp.size.height > 0);
|
||||
TEST_ASSERT_TRUE(adapt.scale() > 0);
|
||||
}
|
||||
|
||||
TEST(WinAdapt, VeryLargeWindow) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(7680, 4320);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
TEST_ASSERT_TRUE(vp.size.width > 0);
|
||||
TEST_ASSERT_TRUE(vp.size.height > 0);
|
||||
TEST_ASSERT_TRUE(adapt.scale() > 1.0f);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 不同宽高比测试
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(WinAdapt, AspectRatioPreservation) {
|
||||
WinAdapt adapt;
|
||||
WinAdaptCfg cfg;
|
||||
cfg.mode = AdaptMode::Fit;
|
||||
cfg.refW = 1280;
|
||||
cfg.refH = 720;
|
||||
adapt.cfg(cfg);
|
||||
|
||||
adapt.win(2560, 1440);
|
||||
|
||||
Rect vp = adapt.viewport();
|
||||
|
||||
f32 refAspect = 1280.0f / 720.0f;
|
||||
f32 vpAspect = vp.size.width / vp.size.height;
|
||||
|
||||
TEST_ASSERT_TRUE(std::abs(refAspect - vpAspect) < EPSILON);
|
||||
}
|
||||
25
xmake.lua
25
xmake.lua
|
|
@ -119,6 +119,14 @@ target("extra2d_tests")
|
|||
add_files("Tests/test_asset_pack.cpp")
|
||||
add_files("Tests/test_asset_service.cpp")
|
||||
|
||||
-- 渲染系统测试
|
||||
add_files("Tests/test_node.cpp")
|
||||
add_files("Tests/test_scene_graph.cpp")
|
||||
add_files("Tests/test_camera.cpp")
|
||||
add_files("Tests/test_win_adapt.cpp")
|
||||
add_files("Tests/test_action.cpp")
|
||||
add_files("Tests/test_scene.cpp")
|
||||
|
||||
-- 头文件路径
|
||||
add_includedirs("Extra2D/include", {public = true})
|
||||
add_includedirs("Tests")
|
||||
|
|
@ -132,12 +140,29 @@ target("extra2d_tests")
|
|||
if plat == "mingw" or plat == "windows" then
|
||||
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
|
||||
add_syslinks("winmm", "imm32", "version", "setupapi")
|
||||
|
||||
-- Vulkan SDK
|
||||
local vulkanSdk = os.getenv("VULKAN_SDK")
|
||||
if vulkanSdk then
|
||||
add_includedirs(vulkanSdk .. "/Include", {public = true})
|
||||
add_linkdirs(vulkanSdk .. "/Lib", {public = true})
|
||||
add_links("vulkan-1", {public = true})
|
||||
end
|
||||
elseif plat == "linux" then
|
||||
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
|
||||
add_syslinks("dl", "pthread")
|
||||
add_links("vulkan", {public = true})
|
||||
elseif plat == "macosx" then
|
||||
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
|
||||
add_frameworks("Cocoa", "IOKit", "CoreVideo")
|
||||
|
||||
-- Vulkan SDK (MoltenVK)
|
||||
local vulkanSdk = os.getenv("VULKAN_SDK")
|
||||
if vulkanSdk then
|
||||
add_includedirs(vulkanSdk .. "/include", {public = true})
|
||||
add_linkdirs(vulkanSdk .. "/lib", {public = true})
|
||||
add_links("vulkan", {public = true})
|
||||
end
|
||||
end
|
||||
|
||||
-- 编译器标志
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
-- 被主项目和示例共享使用
|
||||
--
|
||||
-- 窗口后端: GLFW
|
||||
-- 渲染后端: OpenGL
|
||||
-- 渲染后端: Vulkan
|
||||
-- ==============================================
|
||||
|
||||
-- 获取当前平台
|
||||
|
|
@ -41,14 +41,31 @@ function define_extra2d_engine()
|
|||
-- Windows (MinGW) 平台配置
|
||||
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", {public = true})
|
||||
add_syslinks("winmm", "imm32", "version", "setupapi", {public = true})
|
||||
|
||||
-- Vulkan SDK (使用系统安装的Vulkan SDK)
|
||||
local vulkanSdk = os.getenv("VULKAN_SDK")
|
||||
if vulkanSdk then
|
||||
add_includedirs(vulkanSdk .. "/Include", {public = true})
|
||||
add_linkdirs(vulkanSdk .. "/Lib", {public = true})
|
||||
add_links("vulkan-1", {public = true})
|
||||
end
|
||||
elseif plat == "linux" then
|
||||
-- Linux 平台配置
|
||||
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", {public = true})
|
||||
add_syslinks("dl", "pthread", {public = true})
|
||||
add_links("vulkan", {public = true})
|
||||
elseif plat == "macosx" then
|
||||
-- macOS 平台配置
|
||||
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", {public = true})
|
||||
add_frameworks("Cocoa", "IOKit", "CoreVideo", {public = true})
|
||||
|
||||
-- Vulkan SDK (MoltenVK)
|
||||
local vulkanSdk = os.getenv("VULKAN_SDK")
|
||||
if vulkanSdk then
|
||||
add_includedirs(vulkanSdk .. "/include", {public = true})
|
||||
add_linkdirs(vulkanSdk .. "/lib", {public = true})
|
||||
add_links("vulkan", {public = true})
|
||||
end
|
||||
end
|
||||
|
||||
-- 启用压缩库
|
||||
|
|
|
|||
Loading…
Reference in New Issue