Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/lib/formats/map/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ struct MapObject {

bool isBridgePoint() const { return (flags & (FLAG_BRIDGE_POINT1 | FLAG_BRIDGE_POINT2)) != 0; }

bool shouldRender() const { return (flags & FLAG_DONT_RENDER) == 0; }
bool shouldRender() const {
return (flags & FLAG_DONT_RENDER) == 0 && !isRoadPoint() && !isBridgePoint();
}
};

struct PolygonTrigger {
Expand Down
171 changes: 171 additions & 0 deletions src/lib/scene/quadtree.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#include "quadtree.hpp"

#include <algorithm>

namespace w3d::scene {

Quadtree::Quadtree(float minX, float minZ, float maxX, float maxZ, int maxDepth, int maxPerNode)
: maxDepth_(maxDepth), maxPerNode_(maxPerNode) {
Node root;
root.bounds = {minX, minZ, maxX, maxZ};
nodes_.push_back(root);
}

Quadtree::Rect Quadtree::nodeWorldRect(const SceneNode *node) const {
gfx::BoundingBox wb = node->worldBounds();
if (wb.valid()) {
return {wb.min.x, wb.min.z, wb.max.x, wb.max.z};
}
const glm::vec3 &pos = node->position();
constexpr float kFallbackHalf = 1.0f;
return {pos.x - kFallbackHalf, pos.z - kFallbackHalf, pos.x + kFallbackHalf,
pos.z + kFallbackHalf};
}

void Quadtree::insert(SceneNode *node) {
Entry entry;
entry.node = node;
entry.bounds = nodeWorldRect(node);
insertInto(0, entry, 0);
}

void Quadtree::insertInto(int nodeIndex, const Entry &entry, int depth) {
Node &n = nodes_[nodeIndex];

if (n.isLeaf) {
n.entries.push_back(entry);
if (static_cast<int>(n.entries.size()) > maxPerNode_ && depth < maxDepth_) {
subdivide(nodeIndex, depth);
}
return;
}

float cx = (entry.bounds.minX + entry.bounds.maxX) * 0.5f;
float cz = (entry.bounds.minZ + entry.bounds.maxZ) * 0.5f;

for (int ci : n.children) {
if (ci < 0)
continue;
if (nodes_[ci].bounds.contains(cx, cz)) {
insertInto(ci, entry, depth + 1);
return;
}
}

for (int ci : n.children) {
if (ci < 0)
continue;
if (nodes_[ci].bounds.intersects(entry.bounds)) {
insertInto(ci, entry, depth + 1);
return;
}
}
}

void Quadtree::subdivide(int nodeIndex, int depth) {
float midX = (nodes_[nodeIndex].bounds.minX + nodes_[nodeIndex].bounds.maxX) * 0.5f;
float midZ = (nodes_[nodeIndex].bounds.minZ + nodes_[nodeIndex].bounds.maxZ) * 0.5f;

Rect quads[4] = {
{nodes_[nodeIndex].bounds.minX, nodes_[nodeIndex].bounds.minZ, midX, midZ },
{midX, nodes_[nodeIndex].bounds.minZ, nodes_[nodeIndex].bounds.maxX, midZ },
{nodes_[nodeIndex].bounds.minX, midZ, midX, nodes_[nodeIndex].bounds.maxZ},
{midX, midZ, nodes_[nodeIndex].bounds.maxX, nodes_[nodeIndex].bounds.maxZ},
};

int baseIndex = static_cast<int>(nodes_.size());
nodes_[nodeIndex].children[0] = baseIndex;
nodes_[nodeIndex].children[1] = baseIndex + 1;
nodes_[nodeIndex].children[2] = baseIndex + 2;
nodes_[nodeIndex].children[3] = baseIndex + 3;
nodes_[nodeIndex].isLeaf = false;

nodes_.reserve(nodes_.size() + 4);
for (int i = 0; i < 4; ++i) {
Node child;
child.bounds = quads[i];
nodes_.push_back(child);
}

std::vector<Entry> entries = std::move(nodes_[nodeIndex].entries);
nodes_[nodeIndex].entries.clear();

for (const auto &entry : entries) {
insertInto(nodeIndex, entry, depth);
}
}

void Quadtree::clear() {
float minX = nodes_[0].bounds.minX;
float minZ = nodes_[0].bounds.minZ;
float maxX = nodes_[0].bounds.maxX;
float maxZ = nodes_[0].bounds.maxZ;
nodes_.clear();
Node root;
root.bounds = {minX, minZ, maxX, maxZ};
nodes_.push_back(root);
}

void Quadtree::query(const Rect &rect, std::vector<SceneNode *> &result) const {
queryNode(0, rect, result);
}

void Quadtree::query(const gfx::Frustum &frustum, std::vector<SceneNode *> &result) const {
queryNodeFrustum(0, frustum, result);
}

void Quadtree::queryNode(int nodeIndex, const Rect &rect, std::vector<SceneNode *> &result) const {
const Node &n = nodes_[nodeIndex];

if (!n.bounds.intersects(rect))
return;

for (const auto &entry : n.entries) {
if (entry.bounds.intersects(rect)) {
result.push_back(entry.node);
}
}

if (!n.isLeaf) {
for (int ci : n.children) {
if (ci >= 0) {
queryNode(ci, rect, result);
}
}
}
}

bool Quadtree::rectIntersectsFrustum(const Rect &rect, const gfx::Frustum &frustum) {
gfx::BoundingBox box;
box.expand(glm::vec3(rect.minX, -1e6f, rect.minZ));
box.expand(glm::vec3(rect.maxX, 1e6f, rect.maxZ));
return frustum.isBoxVisible(box);
}

void Quadtree::queryNodeFrustum(int nodeIndex, const gfx::Frustum &frustum,
std::vector<SceneNode *> &result) const {
const Node &n = nodes_[nodeIndex];

if (!rectIntersectsFrustum(n.bounds, frustum))
return;

for (const auto &entry : n.entries) {
gfx::BoundingBox entryBox;
entryBox.expand(glm::vec3(entry.bounds.minX, -1e6f, entry.bounds.minZ));
entryBox.expand(glm::vec3(entry.bounds.maxX, 1e6f, entry.bounds.maxZ));
if (frustum.isBoxVisible(entry.node->worldBounds().valid() ? entry.node->worldBounds()
: entryBox)) {
result.push_back(entry.node);
}
}

if (!n.isLeaf) {
for (int ci : n.children) {
if (ci >= 0) {
queryNodeFrustum(ci, frustum, result);
}
}
}
}

} // namespace w3d::scene
62 changes: 62 additions & 0 deletions src/lib/scene/quadtree.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include <vector>

#include "lib/gfx/bounding_box.hpp"
#include "lib/gfx/frustum.hpp"
#include "lib/scene/scene_node.hpp"

namespace w3d::scene {

class Quadtree {
public:
struct Rect {
float minX = 0.0f;
float minZ = 0.0f;
float maxX = 0.0f;
float maxZ = 0.0f;

[[nodiscard]] bool intersects(const Rect &other) const {
return minX <= other.maxX && maxX >= other.minX && minZ <= other.maxZ && maxZ >= other.minZ;
}

[[nodiscard]] bool contains(float x, float z) const {
return x >= minX && x <= maxX && z >= minZ && z <= maxZ;
}
};

Quadtree(float minX, float minZ, float maxX, float maxZ, int maxDepth = 6, int maxPerNode = 8);

void insert(SceneNode *node);
void clear();

void query(const Rect &rect, std::vector<SceneNode *> &result) const;
void query(const gfx::Frustum &frustum, std::vector<SceneNode *> &result) const;

private:
struct Entry {
SceneNode *node = nullptr;
Rect bounds;
};

struct Node {
Rect bounds;
std::vector<Entry> entries;
int children[4] = {-1, -1, -1, -1};
bool isLeaf = true;
};

[[nodiscard]] Rect nodeWorldRect(const SceneNode *node) const;
void insertInto(int nodeIndex, const Entry &entry, int depth);
void subdivide(int nodeIndex, int depth);
void queryNode(int nodeIndex, const Rect &rect, std::vector<SceneNode *> &result) const;
void queryNodeFrustum(int nodeIndex, const gfx::Frustum &frustum,
std::vector<SceneNode *> &result) const;
[[nodiscard]] static bool rectIntersectsFrustum(const Rect &rect, const gfx::Frustum &frustum);

std::vector<Node> nodes_;
int maxDepth_;
int maxPerNode_;
};

} // namespace w3d::scene
38 changes: 38 additions & 0 deletions src/lib/scene/scene_graph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "scene_graph.hpp"

namespace w3d::scene {

SceneGraph::SceneGraph(float worldMinX, float worldMinZ, float worldMaxX, float worldMaxZ)
: quadtree_(worldMinX, worldMinZ, worldMaxX, worldMaxZ) {}

SceneNode *SceneGraph::addNode(std::unique_ptr<SceneNode> node) {
SceneNode *ptr = node.get();
quadtree_.insert(ptr);
nodes_.push_back(std::move(node));
return ptr;
}

void SceneGraph::clear() {
nodes_.clear();
quadtree_.clear();
}

void SceneGraph::queryVisible(const gfx::Frustum &frustum, std::vector<SceneNode *> &result) const {
std::vector<SceneNode *> candidates;
quadtree_.query(frustum, candidates);
for (SceneNode *n : candidates) {
if (n->isVisible()) {
result.push_back(n);
}
}
}

void SceneGraph::queryAll(std::vector<SceneNode *> &result) const {
for (const auto &node : nodes_) {
if (node->isVisible()) {
result.push_back(node.get());
}
}
}

} // namespace w3d::scene
35 changes: 35 additions & 0 deletions src/lib/scene/scene_graph.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <memory>
#include <vector>

#include "lib/gfx/frustum.hpp"
#include "lib/scene/quadtree.hpp"
#include "lib/scene/scene_node.hpp"

namespace w3d::scene {

class SceneGraph {
public:
SceneGraph(float worldMinX, float worldMinZ, float worldMaxX, float worldMaxZ);
~SceneGraph() = default;

SceneGraph(const SceneGraph &) = delete;
SceneGraph &operator=(const SceneGraph &) = delete;

[[nodiscard]] SceneNode *addNode(std::unique_ptr<SceneNode> node);

void clear();

[[nodiscard]] size_t nodeCount() const { return nodes_.size(); }

void queryVisible(const gfx::Frustum &frustum, std::vector<SceneNode *> &result) const;

void queryAll(std::vector<SceneNode *> &result) const;

private:
std::vector<std::unique_ptr<SceneNode>> nodes_;
Quadtree quadtree_;
};

} // namespace w3d::scene
59 changes: 59 additions & 0 deletions src/lib/scene/scene_node.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "scene_node.hpp"

#include <glm/gtc/matrix_transform.hpp>

#include <array>
#include <limits>

namespace w3d::scene {

void SceneNode::setPosition(const glm::vec3 &position) {
position_ = position;
}

void SceneNode::setRotationY(float radians) {
rotationY_ = radians;
}

void SceneNode::setScale(const glm::vec3 &scale) {
scale_ = scale;
}

glm::mat4 SceneNode::worldTransform() const {
glm::mat4 t = glm::translate(glm::mat4(1.0f), position_);
t = glm::rotate(t, rotationY_, glm::vec3(0.0f, 1.0f, 0.0f));
t = glm::scale(t, scale_);
return t;
}

void SceneNode::setLocalBounds(const gfx::BoundingBox &bounds) {
localBounds_ = bounds;
}

gfx::BoundingBox SceneNode::worldBounds() const {
if (!localBounds_.valid()) {
return {};
}

glm::mat4 transform = worldTransform();

std::array<glm::vec3, 8> corners = {
glm::vec3{localBounds_.min.x, localBounds_.min.y, localBounds_.min.z},
glm::vec3{localBounds_.max.x, localBounds_.min.y, localBounds_.min.z},
glm::vec3{localBounds_.min.x, localBounds_.max.y, localBounds_.min.z},
glm::vec3{localBounds_.max.x, localBounds_.max.y, localBounds_.min.z},
glm::vec3{localBounds_.min.x, localBounds_.min.y, localBounds_.max.z},
glm::vec3{localBounds_.max.x, localBounds_.min.y, localBounds_.max.z},
glm::vec3{localBounds_.min.x, localBounds_.max.y, localBounds_.max.z},
glm::vec3{localBounds_.max.x, localBounds_.max.y, localBounds_.max.z},
};

gfx::BoundingBox result;
for (const auto &corner : corners) {
glm::vec4 world = transform * glm::vec4(corner, 1.0f);
result.expand(glm::vec3(world));
}
return result;
}

} // namespace w3d::scene
Loading