摄像机系统,和子对象绑定

This commit is contained in:
ArchZer0
2026-08-17 16:15:05 +08:00
parent 63d97f058a
commit 7b7756f0fe
20 changed files with 744 additions and 132 deletions
+2
View File
@@ -7,6 +7,8 @@ add_library(boundard STATIC
src/game.cpp src/game.cpp
src/scene.cpp src/scene.cpp
src/render.cpp src/render.cpp
src/render_loop.cpp
src/input.cpp
src/config.cpp src/config.cpp
src/engine.cpp src/engine.cpp
) )
+4 -4
View File
@@ -8,10 +8,10 @@
"input": [ "input": [
{ "action": "quit", "keys": ["Escape"] }, { "action": "quit", "keys": ["Escape"] },
{ "action": "switch_scene", "keys": ["Tab"] }, { "action": "switch_scene", "keys": ["Tab"] },
{ "action": "move_left", "keys": ["Left"] }, { "action": "move_left", "keys": ["a"] },
{ "action": "move_right", "keys": ["Right"] }, { "action": "move_right", "keys": ["d"] },
{ "action": "move_up", "keys": ["Up"] }, { "action": "move_up", "keys": ["w"] },
{ "action": "move_down", "keys": ["Down"] } { "action": "move_down", "keys": ["s"] }
], ],
"start_scene": "square", "start_scene": "square",
+3 -11
View File
@@ -19,10 +19,6 @@ void register_square_behaviors() {
"square_tick", "square_tick",
[](boundard::Actor& actor, float dt, const boundard::render::InputMap& input) { [](boundard::Actor& actor, float dt, const boundard::render::InputMap& input) {
const float t = actor.age(); const float t = actor.age();
actor.set_facing({
std::cos(t * 1.5F),
std::sin(t * 1.5F),
});
if (actor.mesh() != nullptr) { if (actor.mesh() != nullptr) {
actor.mesh()->set_color({ actor.mesh()->set_color({
@@ -34,7 +30,7 @@ void register_square_behaviors() {
} }
auto position = actor.position(); auto position = actor.position();
const float move_speed = 0.7F; constexpr float move_speed = 0.7F;
position[0] += input.get_axis("move_left", "move_right") * move_speed * dt; position[0] += input.get_axis("move_left", "move_right") * move_speed * dt;
position[1] += input.get_axis("move_up", "move_down") * move_speed * dt; position[1] += input.get_axis("move_up", "move_down") * move_speed * dt;
actor.set_position(position); actor.set_position(position);
@@ -54,12 +50,8 @@ void register_square_behaviors() {
behaviors.register_tick( behaviors.register_tick(
"square_alt_tick", "square_alt_tick",
[](boundard::Actor& actor, float dt, const boundard::render::InputMap& input) { [](boundard::Actor& actor, const float dt, const boundard::render::InputMap& input) {
const float t = actor.age(); const float t = actor.age();
actor.set_facing({
std::sin(t * 1.3F),
std::cos(t * 1.3F),
});
if (actor.mesh() != nullptr) { if (actor.mesh() != nullptr) {
actor.mesh()->set_color({ actor.mesh()->set_color({
@@ -71,7 +63,7 @@ void register_square_behaviors() {
} }
auto position = actor.position(); auto position = actor.position();
const float move_speed = 0.7F; constexpr float move_speed = 0.7F;
position[0] += input.get_axis("move_left", "move_right") * move_speed * dt; position[0] += input.get_axis("move_left", "move_right") * move_speed * dt;
position[1] += input.get_axis("move_up", "move_down") * move_speed * dt; position[1] += input.get_axis("move_up", "move_down") * move_speed * dt;
actor.set_position(position); actor.set_position(position);
+10
View File
@@ -1,4 +1,6 @@
{ {
"default_camera": "square_camera",
"grid_background": true,
"actors": [ "actors": [
{ {
"name": "square", "name": "square",
@@ -15,7 +17,15 @@
"mesh": { "mesh": {
"shape": "quad", "shape": "quad",
"texture": "Dank.png" "texture": "Dank.png"
},
"children": [
{
"name": "square_camera",
"type": "camera",
"position": { "x": 0, "y": 0 },
"zoom": 0.2
} }
]
} }
] ]
} }
+10
View File
@@ -1,4 +1,6 @@
{ {
"default_camera": "square_alt_camera",
"grid_background": true,
"actors": [ "actors": [
{ {
"name": "square_alt", "name": "square_alt",
@@ -9,7 +11,15 @@
"mesh": { "mesh": {
"shape": "quad", "shape": "quad",
"color": { "r": 0.9, "g": 0.3, "b": 0.2, "a": 1.0 } "color": { "r": 0.9, "g": 0.3, "b": 0.2, "a": 1.0 }
},
"children": [
{
"name": "square_alt_camera",
"type": "camera",
"position": { "x": 0, "y": 0 },
"zoom": 1.0
} }
]
} }
] ]
} }
+85 -9
View File
@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <vector>
#include <game.hpp> #include <game.hpp>
#include <mesh.hpp> #include <mesh.hpp>
@@ -15,14 +16,16 @@ class InputMap;
namespace boundard { namespace boundard {
class Actor { class Actor : public std::enable_shared_from_this<Actor> {
private: private:
size_t id; size_t id;
std::string nickName_; std::string nickName_;
mutable std::mutex transform_mutex_; mutable std::mutex transform_mutex_;
std::array<float, 2> position_{0.0F, 0.0F}; std::array<float, 2> position_{0.0F, 0.0F};
std::array<float, 2> facing_{1.0F, 0.0F}; std::array<float, 2> facing_{1.0F, 0.0F};
std::shared_ptr<Mesh> mesh_; mutable std::mutex tree_mutex_;
std::weak_ptr<Actor> parent_;
std::vector<std::shared_ptr<Actor>> children_;
float age_ = 0.0F; float age_ = 0.0F;
bool initialized_ = false; bool initialized_ = false;
@@ -43,11 +46,6 @@ class Actor {
nickName_ = std::move(nickName); nickName_ = std::move(nickName);
} }
Actor(std::string nickName, Game& game, std::shared_ptr<Mesh> mesh)
: Actor(std::move(nickName), game) {
mesh_ = std::move(mesh);
}
static Actor& getInstance() { static Actor& getInstance() {
static Actor instance; static Actor instance;
return instance; return instance;
@@ -75,6 +73,9 @@ class Actor {
if (init_handler_) { if (init_handler_) {
init_handler_(*this); init_handler_(*this);
} }
for (const std::shared_ptr<Actor>& child : children()) {
child->initialize();
}
} }
// 类似 Godot 的 _process(delta)。 // 类似 Godot 的 _process(delta)。
@@ -83,6 +84,9 @@ class Actor {
if (tick_handler_) { if (tick_handler_) {
tick_handler_(*this, dt, input); tick_handler_(*this, dt, input);
} }
for (const std::shared_ptr<Actor>& child : children()) {
child->process(dt, input);
}
} }
std::array<float, 2> position() const { std::array<float, 2> position() const {
@@ -95,6 +99,19 @@ class Actor {
position_ = position; position_ = position;
} }
std::array<float, 2> global_position() const {
const std::array<float, 2> local_position = position();
const std::shared_ptr<Actor> parent = this->parent();
if (parent == nullptr) return local_position;
const std::array<float, 2> parent_position = parent->global_position();
const std::array<float, 2> parent_facing = parent->global_facing();
return {parent_position[0] + parent_facing[0] * local_position[0] -
parent_facing[1] * local_position[1],
parent_position[1] + parent_facing[1] * local_position[0] +
parent_facing[0] * local_position[1]};
}
std::array<float, 2> facing() const { std::array<float, 2> facing() const {
std::lock_guard<std::mutex> lock(transform_mutex_); std::lock_guard<std::mutex> lock(transform_mutex_);
return facing_; return facing_;
@@ -105,8 +122,67 @@ class Actor {
facing_ = facing; facing_ = facing;
} }
const std::shared_ptr<Mesh>& mesh() const { return mesh_; } std::array<float, 2> global_facing() const {
void set_mesh(std::shared_ptr<Mesh> mesh) { mesh_ = std::move(mesh); } std::array<float, 2> local_facing = facing();
const std::shared_ptr<Actor> parent = this->parent();
if (parent == nullptr) return local_facing;
const std::array<float, 2> parent_facing = parent->global_facing();
return {parent_facing[0] * local_facing[0] - parent_facing[1] * local_facing[1],
parent_facing[1] * local_facing[0] + parent_facing[0] * local_facing[1]};
}
virtual const std::shared_ptr<Mesh>& mesh() const {
static const std::shared_ptr<Mesh> no_mesh;
return no_mesh;
}
virtual void set_mesh(std::shared_ptr<Mesh>) {}
std::shared_ptr<Actor> parent() const {
std::lock_guard<std::mutex> lock(tree_mutex_);
return parent_.lock();
}
std::vector<std::shared_ptr<Actor>> children() const {
std::lock_guard<std::mutex> lock(tree_mutex_);
return children_;
}
bool add_child(const std::shared_ptr<Actor>& child) {
if (child == nullptr) return false;
const std::shared_ptr<Actor> self = shared_from_this();
if (child == self) return false;
for (std::shared_ptr<Actor> ancestor = self; ancestor != nullptr;
ancestor = ancestor->parent()) {
if (ancestor == child) return false;
}
if (const std::shared_ptr<Actor> old_parent = child->parent()) {
old_parent->remove_child(child);
}
{
std::lock_guard<std::mutex> lock(tree_mutex_);
children_.push_back(child);
}
{
std::lock_guard<std::mutex> lock(child->tree_mutex_);
child->parent_ = self;
}
return true;
}
bool remove_child(const std::shared_ptr<Actor>& child) {
if (child == nullptr) return false;
std::lock_guard<std::mutex> lock(tree_mutex_);
for (auto it = children_.begin(); it != children_.end(); ++it) {
if (*it != child) continue;
children_.erase(it);
std::lock_guard<std::mutex> child_lock(child->tree_mutex_);
child->parent_.reset();
return true;
}
return false;
}
}; };
} // namespace boundard } // namespace boundard
+2
View File
@@ -3,6 +3,8 @@
#include <game.hpp> #include <game.hpp>
#include <mesh.hpp> #include <mesh.hpp>
#include <actor.hpp> #include <actor.hpp>
#include <sprite_actor.hpp>
#include <camera.hpp>
#include <behavior.hpp> #include <behavior.hpp>
#include <render.hpp> #include <render.hpp>
#include <config.hpp> #include <config.hpp>
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <actor.hpp>
namespace boundard {
class Camera : public Actor {
public:
Camera(std::string name, Game& game) : Actor(std::move(name), game) {}
float zoom() const { return zoom_; }
void set_zoom(float zoom) { zoom_ = zoom > 0.0F ? zoom : 0.0001F; }
private:
float zoom_ = 1.0F;
};
} // namespace boundard
+5
View File
@@ -21,11 +21,14 @@ struct MeshConfig {
struct ActorConfig { struct ActorConfig {
std::string name; std::string name;
std::string type = "actor";
std::string init_function; std::string init_function;
std::string tick_function; std::string tick_function;
std::array<float, 2> position{0.0F, 0.0F}; std::array<float, 2> position{0.0F, 0.0F};
std::array<float, 2> facing{1.0F, 0.0F}; std::array<float, 2> facing{1.0F, 0.0F};
float zoom = 1.0F;
MeshConfig mesh; MeshConfig mesh;
std::vector<ActorConfig> children;
}; };
struct InputBindingConfig { struct InputBindingConfig {
@@ -38,6 +41,8 @@ struct SceneConfig {
std::string name; std::string name;
std::string path; std::string path;
std::vector<ActorConfig> actors; std::vector<ActorConfig> actors;
std::string default_camera;
bool grid_background = false;
}; };
struct GameConfig { struct GameConfig {
+27
View File
@@ -44,6 +44,33 @@ class Mesh {
return mesh; return mesh;
} }
static Mesh grid(int half_cells = 12) {
Mesh mesh;
const float world_to_local = 1.0F / 0.35F;
const float half_extent = static_cast<float>(half_cells) * world_to_local;
const float line_half_width = 0.012F * world_to_local;
const std::array<float, 4> line_color{1.0F, 1.0F, 1.0F, 1.0F};
auto add_rect = [&](float left, float bottom, float right, float top) {
const uint32_t base = static_cast<uint32_t>(mesh.vertices.size());
mesh.vertices.insert(mesh.vertices.end(), {
{{left, bottom}, line_color, {0.0F, 0.0F}},
{{right, bottom}, line_color, {1.0F, 0.0F}},
{{right, top}, line_color, {1.0F, 1.0F}},
{{left, top}, line_color, {0.0F, 1.0F}},
});
mesh.indices.insert(mesh.indices.end(),
{base, base + 1, base + 2, base + 2, base + 3, base});
};
for (int cell = -half_cells; cell <= half_cells; ++cell) {
const float coordinate = static_cast<float>(cell) * world_to_local;
add_rect(coordinate - line_half_width, -half_extent, coordinate + line_half_width,
half_extent);
add_rect(-half_extent, coordinate - line_half_width, half_extent,
coordinate + line_half_width);
}
return mesh;
}
private: private:
std::unique_ptr<std::mutex> color_mutex_ = std::make_unique<std::mutex>(); std::unique_ptr<std::mutex> color_mutex_ = std::make_unique<std::mutex>();
std::array<float, 4> color_{1.0F, 1.0F, 1.0F, 1.0F}; std::array<float, 4> color_{1.0F, 1.0F, 1.0F, 1.0F};
+7
View File
@@ -13,6 +13,10 @@
#include <actor.hpp> #include <actor.hpp>
namespace boundard {
class Camera;
}
namespace boundard::render { namespace boundard::render {
struct InputEvent { struct InputEvent {
@@ -51,6 +55,7 @@ class Renderer {
void init(int width, int height, const char* title); void init(int width, int height, const char* title);
void draw(const Actor* const* actors, size_t actor_count); void draw(const Actor* const* actors, size_t actor_count);
void draw(const Actor* const* actors, size_t actor_count, const Camera* camera);
bool should_close() const; bool should_close() const;
void poll_events() const; void poll_events() const;
@@ -119,6 +124,7 @@ class RenderLoop {
void stop(); void stop();
void set_actors(std::vector<Actor*> actors); void set_actors(std::vector<Actor*> actors);
void set_camera(Camera* camera);
void poll_events() const; void poll_events() const;
bool should_close() const; bool should_close() const;
@@ -134,6 +140,7 @@ class RenderLoop {
std::atomic<bool> running_{false}; std::atomic<bool> running_{false};
mutable std::mutex actors_mutex_; mutable std::mutex actors_mutex_;
std::vector<Actor*> actors_; std::vector<Actor*> actors_;
Camera* camera_ = nullptr;
}; };
} // namespace boundard::render } // namespace boundard::render
+6 -1
View File
@@ -11,6 +11,7 @@ class InputMap;
namespace boundard { namespace boundard {
class Actor; class Actor;
class Camera;
// 运行时场景:持有场景名和其中的 Actor,并统一驱动 Actor 的 init/tick。 // 运行时场景:持有场景名和其中的 Actor,并统一驱动 Actor 的 init/tick。
class Scene { class Scene {
@@ -30,6 +31,9 @@ class Scene {
void set_actors(std::vector<std::shared_ptr<Actor>> actors); void set_actors(std::vector<std::shared_ptr<Actor>> actors);
void clear(); void clear();
void set_default_camera(std::string name);
Camera* primary_camera() const;
const std::vector<Actor*>& actors() const; const std::vector<Actor*>& actors() const;
std::vector<Actor*>& actors(); std::vector<Actor*>& actors();
@@ -39,7 +43,8 @@ class Scene {
private: private:
std::string name_; std::string name_;
std::vector<std::shared_ptr<Actor>> actor_storage_; std::vector<std::shared_ptr<Actor>> actor_storage_;
std::vector<Actor*> actors_; mutable std::vector<Actor*> actors_;
std::string default_camera_;
}; };
} // namespace boundard } // namespace boundard
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <actor.hpp>
namespace boundard {
class SpriteActor : public Actor {
public:
SpriteActor(std::string name, Game& game, std::shared_ptr<Mesh> mesh)
: Actor(std::move(name), game), mesh_(std::move(mesh)) {}
const std::shared_ptr<Mesh>& mesh() const override { return mesh_; }
void set_mesh(std::shared_ptr<Mesh> mesh) override { mesh_ = std::move(mesh); }
private:
std::shared_ptr<Mesh> mesh_;
};
} // namespace boundard
+11
View File
@@ -1,4 +1,6 @@
{ {
"default_camera": "main_camera",
"grid_background": true,
"actors": [ "actors": [
{ {
"name": "square", "name": "square",
@@ -13,7 +15,16 @@
"mesh": { "mesh": {
"shape": "quad", "shape": "quad",
"texture": "Dank.png" "texture": "Dank.png"
},
"children": [
{
"name": "main_camera",
"type": "camera",
"position": { "x": 0, "y": 0 },
"facing": { "x": 1, "y": 0 },
"zoom": 1.0
} }
]
} }
] ]
} }
+30 -1
View File
@@ -243,6 +243,9 @@ ActorConfig parse_actor(const Json::Value& value) {
if (value.isMember("name")) { if (value.isMember("name")) {
actor.name = json_string(value["name"], ""); actor.name = json_string(value["name"], "");
} }
if (value.isMember("type")) {
actor.type = json_string(value["type"], actor.type);
}
if (value.isMember("init")) { if (value.isMember("init")) {
actor.init_function = json_string(value["init"], ""); actor.init_function = json_string(value["init"], "");
} }
@@ -261,6 +264,9 @@ ActorConfig parse_actor(const Json::Value& value) {
actor.facing[0] = json_float(facing["x"], actor.facing[0]); actor.facing[0] = json_float(facing["x"], actor.facing[0]);
actor.facing[1] = json_float(facing["y"], actor.facing[1]); actor.facing[1] = json_float(facing["y"], actor.facing[1]);
} }
if (value.isMember("zoom")) {
actor.zoom = json_float(value["zoom"], actor.zoom);
}
if (value.isMember("mesh") && value["mesh"].isObject()) { if (value.isMember("mesh") && value["mesh"].isObject()) {
const Json::Value& mesh = value["mesh"]; const Json::Value& mesh = value["mesh"];
@@ -272,6 +278,11 @@ ActorConfig parse_actor(const Json::Value& value) {
} }
parse_color(mesh, actor.mesh); parse_color(mesh, actor.mesh);
} }
if (value.isMember("children") && value["children"].isArray()) {
for (const Json::Value& child : value["children"]) {
if (child.isObject()) actor.children.push_back(parse_actor(child));
}
}
return actor; return actor;
} }
@@ -312,12 +323,14 @@ void extract_actors(const Json::Value& root, std::vector<ActorConfig>& actors) {
void resolve_texture_paths(std::vector<ActorConfig>& actors, void resolve_texture_paths(std::vector<ActorConfig>& actors,
const std::filesystem::path& base_dir) { const std::filesystem::path& base_dir) {
for (ActorConfig& actor : actors) { for (ActorConfig& actor : actors) {
if (actor.mesh.texture.empty()) continue; if (!actor.mesh.texture.empty()) {
std::filesystem::path texture_path(actor.mesh.texture); std::filesystem::path texture_path(actor.mesh.texture);
if (texture_path.is_relative()) { if (texture_path.is_relative()) {
actor.mesh.texture = (base_dir / texture_path).lexically_normal().string(); actor.mesh.texture = (base_dir / texture_path).lexically_normal().string();
} }
} }
resolve_texture_paths(actor.children, base_dir);
}
} }
bool parse_scene_entry(const Json::Value& item, SceneConfig& scene, bool parse_scene_entry(const Json::Value& item, SceneConfig& scene,
@@ -330,6 +343,14 @@ bool parse_scene_entry(const Json::Value& item, SceneConfig& scene,
if (item.isMember("name")) { if (item.isMember("name")) {
scene.name = json_string(item["name"], ""); scene.name = json_string(item["name"], "");
} }
if (item.isMember("default_camera")) {
scene.default_camera = json_string(item["default_camera"], "");
} else if (item.isMember("camera")) {
scene.default_camera = json_string(item["camera"], "");
}
if (item.isMember("grid_background")) {
scene.grid_background = item["grid_background"].asBool();
}
if (scene.name.empty()) { if (scene.name.empty()) {
error = "场景缺少 name"; error = "场景缺少 name";
return false; return false;
@@ -353,6 +374,14 @@ bool parse_scene_entry(const Json::Value& item, SceneConfig& scene,
return false; return false;
} }
extract_actors(root, scene.actors); extract_actors(root, scene.actors);
if (root.isMember("default_camera")) {
scene.default_camera = json_string(root["default_camera"], scene.default_camera);
} else if (root.isMember("camera")) {
scene.default_camera = json_string(root["camera"], scene.default_camera);
}
if (root.isMember("grid_background")) {
scene.grid_background = root["grid_background"].asBool();
}
resolve_texture_paths(scene.actors, scene_path.parent_path()); resolve_texture_paths(scene.actors, scene_path.parent_path());
} else { } else {
// 允许场景直接内联在游戏配置中。 // 允许场景直接内联在游戏配置中。
+24 -1
View File
@@ -8,6 +8,8 @@
#include <vector> #include <vector>
#include <boundard.hpp> #include <boundard.hpp>
#include <camera.hpp>
#include <sprite_actor.hpp>
namespace boundard { namespace boundard {
namespace { namespace {
@@ -24,9 +26,21 @@ std::shared_ptr<Actor> create_actor(const ActorConfig& actor_config, Game& game,
mesh->set_color(actor_config.mesh.color); mesh->set_color(actor_config.mesh.color);
mesh->texture = actor_config.mesh.texture; mesh->texture = actor_config.mesh.texture;
auto actor = std::make_shared<Actor>(actor_config.name, game, mesh); std::shared_ptr<Actor> actor;
if (actor_config.type == "camera") {
actor = std::make_shared<Camera>(actor_config.name, game);
} else {
actor = std::make_shared<SpriteActor>(actor_config.name, game, mesh);
}
actor->set_position(actor_config.position); actor->set_position(actor_config.position);
actor->set_facing(actor_config.facing); actor->set_facing(actor_config.facing);
if (auto* camera = dynamic_cast<Camera*>(actor.get())) {
camera->set_zoom(actor_config.zoom);
}
for (const ActorConfig& child_config : actor_config.children) {
actor->add_child(create_actor(child_config, game, behaviors));
}
if (!actor_config.init_function.empty()) { if (!actor_config.init_function.empty()) {
auto handler = behaviors.init(actor_config.init_function); auto handler = behaviors.init(actor_config.init_function);
@@ -55,10 +69,18 @@ std::shared_ptr<Scene> build_scene(const SceneConfig& scene, Game& game,
BehaviorRegistry& behaviors) { BehaviorRegistry& behaviors) {
auto runtime_scene = std::make_shared<Scene>(scene.name); auto runtime_scene = std::make_shared<Scene>(scene.name);
if (scene.grid_background) {
auto grid = std::make_shared<SpriteActor>("__grid", game,
std::make_shared<Mesh>(Mesh::grid()));
grid->mesh()->set_color({0.20F, 0.28F, 0.36F, 0.75F});
runtime_scene->add_actor(std::move(grid));
}
for (const ActorConfig& actor_config : scene.actors) { for (const ActorConfig& actor_config : scene.actors) {
auto actor = create_actor(actor_config, game, behaviors); auto actor = create_actor(actor_config, game, behaviors);
runtime_scene->add_actor(std::move(actor)); runtime_scene->add_actor(std::move(actor));
} }
runtime_scene->set_default_camera(scene.default_camera);
return runtime_scene; return runtime_scene;
} }
@@ -143,6 +165,7 @@ int run_engine(int argc, char** argv, const char* default_config_path) {
scene->initialize(); scene->initialize();
game.set_current_scene(scene); game.set_current_scene(scene);
render_loop.set_actors(scene->actors()); render_loop.set_actors(scene->actors());
render_loop.set_camera(scene->primary_camera());
}; };
if (active_scene != nullptr) { if (active_scene != nullptr) {
+139
View File
@@ -0,0 +1,139 @@
#include <render.hpp>
namespace boundard::render {
void InputMap::add_action(const std::string& action) {
std::lock_guard<std::mutex> lock(mutex_);
actions_.try_emplace(action);
}
void InputMap::bind_key(const std::string& action, int key) {
std::lock_guard<std::mutex> lock(mutex_);
ActionState& state = actions_[action];
state.keys.insert(key);
state.held_keys.try_emplace(key, false);
}
void InputMap::unbind_key(const std::string& action, int key) {
std::lock_guard<std::mutex> lock(mutex_);
auto action_it = actions_.find(action);
if (action_it == actions_.end()) return;
ActionState& state = action_it->second;
state.keys.erase(key);
auto held_it = state.held_keys.find(key);
if (held_it != state.held_keys.end() && held_it->second) {
held_it->second = false;
if (state.held_key_count > 0) --state.held_key_count;
if (state.held_key_count + state.held_button_count == 0) {
state.pressed = false;
state.just_released = true;
}
}
state.held_keys.erase(key);
}
void InputMap::bind_mouse_button(const std::string& action, int button) {
std::lock_guard<std::mutex> lock(mutex_);
ActionState& state = actions_[action];
state.mouse_buttons.insert(button);
state.held_buttons.try_emplace(button, false);
}
void InputMap::unbind_mouse_button(const std::string& action, int button) {
std::lock_guard<std::mutex> lock(mutex_);
auto action_it = actions_.find(action);
if (action_it == actions_.end()) return;
ActionState& state = action_it->second;
state.mouse_buttons.erase(button);
auto held_it = state.held_buttons.find(button);
if (held_it != state.held_buttons.end() && held_it->second) {
held_it->second = false;
if (state.held_button_count > 0) --state.held_button_count;
if (state.held_key_count + state.held_button_count == 0) {
state.pressed = false;
state.just_released = true;
}
}
state.held_buttons.erase(button);
}
void InputMap::handle_event(const InputEvent& event) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [action, state] : actions_) {
std::unordered_set<int>* bindings = nullptr;
std::unordered_map<int, bool>* held_map = nullptr;
size_t* held_count = nullptr;
if (event.type == InputEvent::Type::Key) {
bindings = &state.keys;
held_map = &state.held_keys;
held_count = &state.held_key_count;
} else if (event.type == InputEvent::Type::MouseButton) {
bindings = &state.mouse_buttons;
held_map = &state.held_buttons;
held_count = &state.held_button_count;
} else {
continue;
}
const int input_id = event.type == InputEvent::Type::Key ? event.key : event.button;
if (bindings->find(input_id) == bindings->end()) continue;
if (event.action == InputEvent::Action::Press) {
bool& held = (*held_map)[input_id];
if (!held) {
held = true;
++(*held_count);
if (state.held_key_count + state.held_button_count == 1) {
state.pressed = true;
state.just_pressed = true;
}
}
} else if (event.action == InputEvent::Action::Release) {
auto held_it = held_map->find(input_id);
if (held_it == held_map->end() || !held_it->second) continue;
held_it->second = false;
if (*held_count > 0) --(*held_count);
if (state.held_key_count + state.held_button_count == 0) {
state.pressed = false;
state.just_released = true;
}
}
}
}
void InputMap::end_frame() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [action, state] : actions_) {
state.just_pressed = false;
state.just_released = false;
}
}
bool InputMap::is_action_pressed(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.pressed;
}
bool InputMap::is_action_just_pressed(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.just_pressed;
}
bool InputMap::is_action_just_released(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.just_released;
}
float InputMap::get_action_strength(const std::string& action) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = actions_.find(action);
return it != actions_.end() && it->second.pressed ? 1.0F : 0.0F;
}
float InputMap::get_axis(const std::string& negative_action,
const std::string& positive_action) const {
return get_action_strength(positive_action) - get_action_strength(negative_action);
}
} // namespace boundard::render
+194 -79
View File
@@ -1,4 +1,5 @@
#include <render.hpp> #include <render.hpp>
#include <camera.hpp>
#define GLFW_INCLUDE_VULKAN #define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
@@ -197,6 +198,7 @@ struct Renderer::Impl {
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
VkPipeline pipeline = VK_NULL_HANDLE; VkPipeline pipeline = VK_NULL_HANDLE;
std::atomic<bool> framebuffer_resized{false};
VkCommandPool command_pool = VK_NULL_HANDLE; VkCommandPool command_pool = VK_NULL_HANDLE;
std::vector<FrameData> frames; std::vector<FrameData> frames;
@@ -221,11 +223,12 @@ struct Renderer::Impl {
Renderer::InputCallback input_callback; Renderer::InputCallback input_callback;
void init(int width, int height, const char* title); void init(int width, int height, const char* title);
void draw(const Actor* const* actors, size_t actor_count); void draw(const Actor* const* actors, size_t actor_count, const Camera* camera);
void upload_mesh(const Mesh& mesh); void upload_mesh(const Mesh& mesh);
void create_texture(MeshResource& resource, const std::string& path); void create_texture(MeshResource& resource, const std::string& path);
VkCommandBuffer begin_transfer(); VkCommandBuffer begin_transfer();
void end_transfer(VkCommandBuffer command_buffer); void end_transfer(VkCommandBuffer command_buffer);
void recreate_swapchain();
void destroy(); void destroy();
}; };
@@ -240,7 +243,11 @@ void Renderer::init(int width, int height, const char* title) {
} }
void Renderer::draw(const Actor* const* actors, size_t actor_count) { void Renderer::draw(const Actor* const* actors, size_t actor_count) {
impl_->draw(actors, actor_count); impl_->draw(actors, actor_count, nullptr);
}
void Renderer::draw(const Actor* const* actors, size_t actor_count, const Camera* camera) {
impl_->draw(actors, actor_count, camera);
} }
bool Renderer::should_close() const { bool Renderer::should_close() const {
@@ -277,6 +284,7 @@ void Renderer::destroy() {
impl_->destroy(); impl_->destroy();
} }
#if 0 // InputMap implementation moved to input.cpp.
void InputMap::add_action(const std::string& action) { void InputMap::add_action(const std::string& action) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
actions_.try_emplace(action); actions_.try_emplace(action);
@@ -428,72 +436,7 @@ float InputMap::get_axis(const std::string& negative_action,
const std::string& positive_action) const { const std::string& positive_action) const {
return get_action_strength(positive_action) - get_action_strength(negative_action); return get_action_strength(positive_action) - get_action_strength(negative_action);
} }
#endif
RenderLoop::~RenderLoop() {
stop();
}
void RenderLoop::start(int width, int height, const char* title) {
if (running_) {
return;
}
renderer_.init(width, height, title);
renderer_.set_input_callback([this](const InputEvent& event) {
input_map_.handle_event(event);
});
running_ = true;
thread_ = std::thread(&RenderLoop::run, this);
}
void RenderLoop::stop() {
if (!running_) {
return;
}
running_ = false;
if (thread_.joinable()) {
thread_.join();
}
renderer_.destroy();
}
void RenderLoop::set_actors(std::vector<Actor*> actors) {
std::lock_guard<std::mutex> lock(actors_mutex_);
actors_ = std::move(actors);
}
void RenderLoop::poll_events() const {
renderer_.poll_events();
}
bool RenderLoop::should_close() const {
return renderer_.should_close();
}
InputMap& RenderLoop::input() {
return input_map_;
}
const InputMap& RenderLoop::input() const {
return input_map_;
}
void RenderLoop::run() {
while (running_) {
std::vector<Actor*> actors;
{
std::lock_guard<std::mutex> lock(actors_mutex_);
actors = actors_;
}
if (!actors.empty()) {
renderer_.draw(actors.data(), actors.size());
} else {
std::this_thread::yield();
}
}
}
void Renderer::Impl::init(int width, int height, const char* title) { void Renderer::Impl::init(int width, int height, const char* title) {
if (initialized) { if (initialized) {
@@ -509,8 +452,11 @@ void Renderer::Impl::init(int width, int height, const char* title) {
if (window == nullptr) { if (window == nullptr) {
throw std::runtime_error("failed to create GLFW window"); throw std::runtime_error("failed to create GLFW window");
} }
glfwSetWindowUserPointer(window, this); glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* target, int, int) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
if (impl != nullptr) impl->framebuffer_resized = true;
});
glfwSetKeyCallback(window, [](GLFWwindow* target, int key, int scancode, int action, glfwSetKeyCallback(window, [](GLFWwindow* target, int key, int scancode, int action,
int mods) { int mods) {
auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target)); auto* impl = static_cast<Renderer::Impl*>(glfwGetWindowUserPointer(target));
@@ -1051,7 +997,15 @@ void Renderer::Impl::init(int width, int height, const char* title) {
pipeline_info.pMultisampleState = &multisampling; pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pDepthStencilState = nullptr; pipeline_info.pDepthStencilState = nullptr;
pipeline_info.pColorBlendState = &color_blending; pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = nullptr; const std::array<VkDynamicState, 2> dynamic_states = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
VkPipelineDynamicStateCreateInfo dynamic_state{};
dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic_state.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic_state.pDynamicStates = dynamic_states.data();
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = pipeline_layout; pipeline_info.layout = pipeline_layout;
pipeline_info.renderPass = render_pass; pipeline_info.renderPass = render_pass;
pipeline_info.subpass = 0; pipeline_info.subpass = 0;
@@ -1232,7 +1186,139 @@ void Renderer::Impl::upload_mesh(const Mesh& mesh) {
create_texture(resource, mesh.texture); create_texture(resource, mesh.texture);
} }
void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) { void Renderer::Impl::recreate_swapchain() {
int width = 0;
int height = 0;
glfwGetFramebufferSize(window, &width, &height);
if (width == 0 || height == 0) return;
check(vkDeviceWaitIdle(device), "vkDeviceWaitIdle");
VkSurfaceCapabilitiesKHR capabilities{};
check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities),
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
uint32_t format_count = 0;
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, nullptr),
"vkGetPhysicalDeviceSurfaceFormatsKHR");
std::vector<VkSurfaceFormatKHR> formats(format_count);
check(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats.data()),
"vkGetPhysicalDeviceSurfaceFormatsKHR");
VkSurfaceFormatKHR surface_format = formats.front();
for (const VkSurfaceFormatKHR& format : formats) {
if (format.format == VK_FORMAT_B8G8R8A8_SRGB &&
format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
surface_format = format;
break;
}
}
if (surface_format.format != swapchain_format) {
throw std::runtime_error("swapchain format changed; renderer recreation is required");
}
uint32_t present_mode_count = 0;
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count,
nullptr),
"vkGetPhysicalDeviceSurfacePresentModesKHR");
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
check(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count,
present_modes.data()),
"vkGetPhysicalDeviceSurfacePresentModesKHR");
VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
for (VkPresentModeKHR mode : present_modes) {
if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
present_mode = mode;
break;
}
}
VkExtent2D extent{};
if (capabilities.currentExtent.width != UINT32_MAX) {
extent = capabilities.currentExtent;
} else {
extent.width = std::clamp(static_cast<uint32_t>(width), capabilities.minImageExtent.width,
capabilities.maxImageExtent.width);
extent.height = std::clamp(static_cast<uint32_t>(height), capabilities.minImageExtent.height,
capabilities.maxImageExtent.height);
}
uint32_t image_count = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0 && image_count > capabilities.maxImageCount) {
image_count = capabilities.maxImageCount;
}
for (VkFramebuffer framebuffer : framebuffers) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
}
framebuffers.clear();
for (VkImageView view : swapchain_image_views) {
vkDestroyImageView(device, view, nullptr);
}
swapchain_image_views.clear();
const VkSwapchainKHR old_swapchain = swapchain;
if (old_swapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device, old_swapchain, nullptr);
swapchain = VK_NULL_HANDLE;
}
VkSwapchainCreateInfoKHR create_info{};
create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
create_info.surface = surface;
create_info.minImageCount = image_count;
create_info.imageFormat = surface_format.format;
create_info.imageColorSpace = surface_format.colorSpace;
create_info.imageExtent = extent;
create_info.imageArrayLayers = 1;
create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
std::array<uint32_t, 2> families = {graphics_family, present_family};
create_info.imageSharingMode = graphics_family == present_family
? VK_SHARING_MODE_EXCLUSIVE
: VK_SHARING_MODE_CONCURRENT;
create_info.queueFamilyIndexCount = graphics_family == present_family ? 0 : 2;
create_info.pQueueFamilyIndices = families.data();
create_info.preTransform = capabilities.currentTransform;
create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
create_info.presentMode = present_mode;
create_info.clipped = VK_TRUE;
create_info.oldSwapchain = VK_NULL_HANDLE;
check(vkCreateSwapchainKHR(device, &create_info, nullptr, &swapchain), "vkCreateSwapchainKHR");
swapchain_extent = extent;
uint32_t actual_image_count = 0;
check(vkGetSwapchainImagesKHR(device, swapchain, &actual_image_count, nullptr),
"vkGetSwapchainImagesKHR");
if (actual_image_count > frames.size()) {
throw std::runtime_error("resized swapchain needs more frame resources");
}
swapchain_images.resize(actual_image_count);
check(vkGetSwapchainImagesKHR(device, swapchain, &actual_image_count, swapchain_images.data()),
"vkGetSwapchainImagesKHR");
swapchain_image_views.resize(actual_image_count);
for (uint32_t i = 0; i < actual_image_count; ++i) {
VkImageViewCreateInfo view_info{};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = swapchain_images[i];
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = swapchain_format;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.layerCount = 1;
check(vkCreateImageView(device, &view_info, nullptr, &swapchain_image_views[i]),
"vkCreateImageView");
}
framebuffers.resize(actual_image_count);
for (uint32_t i = 0; i < actual_image_count; ++i) {
VkFramebufferCreateInfo framebuffer_info{};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.renderPass = render_pass;
framebuffer_info.attachmentCount = 1;
framebuffer_info.pAttachments = &swapchain_image_views[i];
framebuffer_info.width = swapchain_extent.width;
framebuffer_info.height = swapchain_extent.height;
framebuffer_info.layers = 1;
check(vkCreateFramebuffer(device, &framebuffer_info, nullptr, &framebuffers[i]),
"vkCreateFramebuffer");
}
images_in_flight.assign(actual_image_count, VK_NULL_HANDLE);
framebuffer_resized = false;
}
void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count, const Camera* camera) {
if (!initialized || actor_count == 0) { if (!initialized || actor_count == 0) {
return; return;
} }
@@ -1245,6 +1331,7 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
frame.image_available, frame.image_available,
VK_NULL_HANDLE, &image_index); VK_NULL_HANDLE, &image_index);
if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) { if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) {
recreate_swapchain();
return; return;
} }
if (acquire_result != VK_SUCCESS && acquire_result != VK_SUBOPTIMAL_KHR) { if (acquire_result != VK_SUCCESS && acquire_result != VK_SUBOPTIMAL_KHR) {
@@ -1286,6 +1373,15 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
vkCmdBeginRenderPass(frame.command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(frame.command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindPipeline(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkViewport viewport{};
viewport.width = static_cast<float>(swapchain_extent.width);
viewport.height = static_cast<float>(swapchain_extent.height);
viewport.minDepth = 0.0F;
viewport.maxDepth = 1.0F;
VkRect2D scissor{};
scissor.extent = swapchain_extent;
vkCmdSetViewport(frame.command_buffer, 0, 1, &viewport);
vkCmdSetScissor(frame.command_buffer, 0, 1, &scissor);
for (size_t i = 0; i < actor_count; ++i) { for (size_t i = 0; i < actor_count; ++i) {
const Actor& actor = *actors[i]; const Actor& actor = *actors[i];
if (actor.mesh() == nullptr) continue; if (actor.mesh() == nullptr) continue;
@@ -1301,9 +1397,26 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
0, 1, &resource.descriptor_set, 0, nullptr); 0, 1, &resource.descriptor_set, 0, nullptr);
const auto position = actor.position(); auto position = actor.global_position();
const auto facing = actor.facing(); auto facing = actor.global_facing();
const float scale = 0.35F; float camera_zoom = 1.0F;
if (camera != nullptr) {
const auto camera_position = camera->global_position();
const auto camera_facing = camera->global_facing();
const float camera_length = std::sqrt(camera_facing[0] * camera_facing[0] +
camera_facing[1] * camera_facing[1]);
const float camera_x = camera_length > 0.0001F ? camera_facing[0] / camera_length : 1.0F;
const float camera_y = camera_length > 0.0001F ? camera_facing[1] / camera_length : 0.0F;
const float dx = position[0] - camera_position[0];
const float dy = position[1] - camera_position[1];
position = {camera_x * dx + camera_y * dy, -camera_y * dx + camera_x * dy};
facing = {camera_x * facing[0] + camera_y * facing[1],
-camera_y * facing[0] + camera_x * facing[1]};
camera_zoom = camera->zoom();
}
const float scale = 0.35F * camera_zoom;
const float horizontal_projection =
static_cast<float>(swapchain_extent.height) / static_cast<float>(swapchain_extent.width);
float facing_x = facing[0]; float facing_x = facing[0];
float facing_y = facing[1]; float facing_y = facing[1];
const float facing_length = std::sqrt(facing_x * facing_x + facing_y * facing_y); const float facing_length = std::sqrt(facing_x * facing_x + facing_y * facing_y);
@@ -1316,10 +1429,10 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
} }
std::array<float, 20> constants = {{ std::array<float, 20> constants = {{
scale * facing_x, scale * facing_y, 0.0F, 0.0F, scale * facing_x * horizontal_projection, scale * facing_y, 0.0F, 0.0F,
-scale * facing_y, scale * facing_x, 0.0F, 0.0F, -scale * facing_y * horizontal_projection, scale * facing_x, 0.0F, 0.0F,
0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F,
position[0], position[1], 0.0F, 1.0F, position[0] * horizontal_projection, position[1], 0.0F, 1.0F,
0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F,
}}; }};
const std::array<float, 4> color = actor.mesh()->color(); const std::array<float, 4> color = actor.mesh()->color();
@@ -1354,8 +1467,10 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
present_info.pSwapchains = &swapchain; present_info.pSwapchains = &swapchain;
present_info.pImageIndices = &image_index; present_info.pImageIndices = &image_index;
VkResult present_result = vkQueuePresentKHR(present_queue, &present_info); VkResult present_result = vkQueuePresentKHR(present_queue, &present_info);
if (present_result != VK_SUCCESS && present_result != VK_SUBOPTIMAL_KHR && if (present_result == VK_ERROR_OUT_OF_DATE_KHR || present_result == VK_SUBOPTIMAL_KHR ||
present_result != VK_ERROR_OUT_OF_DATE_KHR) { framebuffer_resized) {
recreate_swapchain();
} else if (present_result != VK_SUCCESS) {
check(present_result, "vkQueuePresentKHR"); check(present_result, "vkQueuePresentKHR");
} }
(void)present_result; (void)present_result;
+70
View File
@@ -0,0 +1,70 @@
#include <render.hpp>
namespace boundard::render {
RenderLoop::~RenderLoop() {
stop();
}
void RenderLoop::start(int width, int height, const char* title) {
if (running_) return;
renderer_.init(width, height, title);
renderer_.set_input_callback([this](const InputEvent& event) {
input_map_.handle_event(event);
});
running_ = true;
thread_ = std::thread(&RenderLoop::run, this);
}
void RenderLoop::stop() {
if (!running_) return;
running_ = false;
if (thread_.joinable()) thread_.join();
renderer_.destroy();
}
void RenderLoop::set_actors(std::vector<Actor*> actors) {
std::lock_guard<std::mutex> lock(actors_mutex_);
actors_ = std::move(actors);
}
void RenderLoop::set_camera(Camera* camera) {
std::lock_guard<std::mutex> lock(actors_mutex_);
camera_ = camera;
}
void RenderLoop::poll_events() const {
renderer_.poll_events();
}
bool RenderLoop::should_close() const {
return renderer_.should_close();
}
InputMap& RenderLoop::input() {
return input_map_;
}
const InputMap& RenderLoop::input() const {
return input_map_;
}
void RenderLoop::run() {
while (running_) {
std::vector<Actor*> actors;
Camera* camera = nullptr;
{
std::lock_guard<std::mutex> lock(actors_mutex_);
actors = actors_;
camera = camera_;
}
if (actors.empty()) {
std::this_thread::yield();
continue;
}
renderer_.draw(actors.data(), actors.size(), camera);
}
}
} // namespace boundard::render
+50 -2
View File
@@ -1,10 +1,38 @@
#include <scene.hpp> #include <scene.hpp>
#include <actor.hpp> #include <actor.hpp>
#include <camera.hpp>
#include <utility> #include <utility>
namespace boundard { namespace boundard {
namespace {
void append_actor_tree(Actor* actor, std::vector<Actor*>& output) {
if (actor == nullptr) return;
output.push_back(actor);
for (const std::shared_ptr<Actor>& child : actor->children()) {
append_actor_tree(child.get(), output);
}
}
void find_cameras(Actor* actor, const std::string& name, Camera*& first_camera,
Camera*& selected_camera) {
if (actor == nullptr || selected_camera != nullptr) return;
Camera* camera = dynamic_cast<Camera*>(actor);
if (camera != nullptr) {
if (first_camera == nullptr) first_camera = camera;
if (!name.empty() && camera->name() == name) {
selected_camera = camera;
return;
}
}
for (const std::shared_ptr<Actor>& child : actor->children()) {
find_cameras(child.get(), name, first_camera, selected_camera);
}
}
} // namespace
Scene::Scene(std::string name) : name_(std::move(name)) {} Scene::Scene(std::string name) : name_(std::move(name)) {}
@@ -36,24 +64,44 @@ void Scene::set_actors(std::vector<std::shared_ptr<Actor>> actors) {
void Scene::clear() { void Scene::clear() {
actors_.clear(); actors_.clear();
actor_storage_.clear(); actor_storage_.clear();
default_camera_.clear();
}
void Scene::set_default_camera(std::string name) {
default_camera_ = std::move(name);
}
Camera* Scene::primary_camera() const {
Camera* first_camera = nullptr;
Camera* selected_camera = nullptr;
for (const std::shared_ptr<Actor>& actor : actor_storage_) {
find_cameras(actor.get(), default_camera_, first_camera, selected_camera);
if (selected_camera != nullptr) return selected_camera;
}
return first_camera;
} }
const std::vector<Actor*>& Scene::actors() const { const std::vector<Actor*>& Scene::actors() const {
actors_.clear();
for (const std::shared_ptr<Actor>& actor : actor_storage_) {
append_actor_tree(actor.get(), actors_);
}
return actors_; return actors_;
} }
std::vector<Actor*>& Scene::actors() { std::vector<Actor*>& Scene::actors() {
static_cast<const Scene&>(*this).actors();
return actors_; return actors_;
} }
void Scene::initialize() { void Scene::initialize() {
for (Actor* actor : actors_) { for (const std::shared_ptr<Actor>& actor : actor_storage_) {
actor->initialize(); actor->initialize();
} }
} }
void Scene::process(float dt, const render::InputMap& input) { void Scene::process(float dt, const render::InputMap& input) {
for (Actor* actor : actors_) { for (const std::shared_ptr<Actor>& actor : actor_storage_) {
actor->process(dt, input); actor->process(dt, input);
} }
} }