diff --git a/assets/scenes/Game.scene b/assets/scenes/Game.scene
index 87c5bdf..a90b4d5 100644
--- a/assets/scenes/Game.scene
+++ b/assets/scenes/Game.scene
@@ -26,6 +26,9 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/GameState.h b/src/GameState.h
index 4450149..7f9f091 100644
--- a/src/GameState.h
+++ b/src/GameState.h
@@ -8,11 +8,13 @@
#ifndef MODEL_GAMESTATE_H_
#define MODEL_GAMESTATE_H_
+#include
+#include
#include
#include
#include
-#include
#include
+#include
#include
#include
@@ -42,6 +44,8 @@ namespace farmlands {
std::vector seedsPrefabs;
std::vector plantsPrefabs;
+ utils::QTree* colliderTree;
+
// Timing
float time;
uint32_t date;
diff --git a/src/components/basic/Collider.cpp b/src/components/basic/Collider.cpp
new file mode 100644
index 0000000..8dfea75
--- /dev/null
+++ b/src/components/basic/Collider.cpp
@@ -0,0 +1,187 @@
+/*
+ * Collider.cpp
+ *
+ * Created on: Dec 15, 2016
+ * Author: tibi
+ */
+
+#include
+#include
+#include
+#include
+
+using namespace farmlands::utils;
+
+namespace farmlands {
+namespace components {
+namespace basic {
+
+static const float CollisionCheckDistance = 4.0f;
+
+
+Collider::Collider()
+ : collisionRect(0, 0, 1, 1),
+ solid(false),
+ m_sprite(nullptr),
+ m_lastBounds(0, 0, 0, 0),
+ m_lastX(0), m_lastY(0)
+{
+}
+
+Collider::~Collider()
+{
+ auto qtree = GameState::current().colliderTree;
+ if (!qtree)
+ return;
+
+ // Remove self from the qtree
+ auto it0 = qtree->find(this, m_lastBounds.x, m_lastBounds.y);
+
+ // Object may have never been initialized, so it is possible that it is not in the qtree
+ if (it0 != qtree->end())
+ {
+ qtree->erase(it0);
+
+ if (m_sprite)
+ {
+ auto it1 = qtree->find(this, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y);
+ qtree->erase(it1);
+ auto it2 = qtree->find(this, m_lastBounds.x, m_lastBounds.y + m_lastBounds.h);
+ qtree->erase(it2);
+ auto it3 = qtree->find(this, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y + m_lastBounds.h);
+ qtree->erase(it3);
+ }
+ }
+}
+
+model::Component* Collider::clone()
+{
+ Collider* clone = new Collider();
+ clone->collisionRect = collisionRect;
+ clone->solid = solid;
+ return clone;
+}
+
+void Collider::dump(unsigned level)
+{
+ for (unsigned i = 0; i < level; i++)
+ std::cout<<" ";
+
+ std::cout << " .Component: Collider\n";
+}
+
+void Collider::onInitialize()
+{
+ auto qtree = GameState::current().colliderTree;
+ Assert(qtree != nullptr, "Collider tree not set!");
+
+ // Obtain sprite
+ m_sprite = gameObject->component();
+ recomputeBounds();
+
+ qtree->insert(this, m_lastBounds.x, m_lastBounds.y);
+ if (m_sprite)
+ {
+ qtree->insert(this, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y);
+ qtree->insert(this, m_lastBounds.x, m_lastBounds.y + m_lastBounds.h);
+ qtree->insert(this, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y + m_lastBounds.h);
+ }
+}
+
+void Collider::onUpdateLogic()
+{
+ auto qtree = GameState::current().colliderTree;
+ Assert(qtree != nullptr, "Collider tree not set!");
+
+ // Update position if object moved.
+ // As an optimization, we ignore sprite shape changes (i.e. scale) because
+ // we expect that to be used for animation.
+ bool moved = (m_lastX != gameObject->transform.x || m_lastY != gameObject->transform.y);
+
+ if (moved)
+ {
+ RectF oldBounds = m_lastBounds;
+ recomputeBounds();
+
+ auto it0 = qtree->find(this, oldBounds.x, oldBounds.y);
+ qtree->move(it0, m_lastBounds.x, m_lastBounds.y);
+
+ if (m_sprite)
+ {
+ auto it1 = qtree->find(this, oldBounds.x + oldBounds.w, oldBounds.y);
+ auto it2 = qtree->find(this, oldBounds.x, oldBounds.y + oldBounds.h);
+ auto it3 = qtree->find(this, oldBounds.x + oldBounds.w, oldBounds.y + oldBounds.h);
+
+ qtree->move(it1, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y);
+ qtree->move(it2, m_lastBounds.x, m_lastBounds.y + m_lastBounds.h);
+ qtree->move(it3, m_lastBounds.x + m_lastBounds.w, m_lastBounds.y + m_lastBounds.h);
+ }
+ }
+}
+
+Collider* Collider::checkCollision()
+{
+ auto qtree = GameState::current().colliderTree;
+ Assert(qtree != nullptr, "Collider tree not set!");
+
+ RectF searchArea(m_lastX - CollisionCheckDistance, m_lastY - CollisionCheckDistance, 2 * CollisionCheckDistance, 2 * CollisionCheckDistance);
+
+ for (auto it = qtree->lower_bound(searchArea); it != qtree->upper_bound(searchArea); it++)
+ {
+ if (it->data != this && m_lastBounds.intersects(it->data->m_lastBounds))
+ return it->data;
+ }
+
+ return nullptr;
+}
+
+bool Collider::canMove(float newX, float newY)
+{
+ auto qtree = GameState::current().colliderTree;
+ Assert(qtree != nullptr, "Collider tree not set!");
+
+ // Both objects must be solid to register a collision
+ if (!solid)
+ return true;
+
+ // Compute moved bounds
+ float offX = m_lastBounds.x - m_lastX;
+ float offY = m_lastBounds.y - m_lastY;
+ RectF movedBounds(newX + offX, newY + offY, m_lastBounds.w, m_lastBounds.h);
+
+ // Search for objects
+ RectF searchArea(newX - CollisionCheckDistance, newY - CollisionCheckDistance, 2 * CollisionCheckDistance, 2 * CollisionCheckDistance);
+ for (auto it = qtree->lower_bound(searchArea); it != qtree->upper_bound(searchArea); it++)
+ {
+ if (it->data != this && it->data->solid && movedBounds.intersects(it->data->m_lastBounds))
+ return false;
+ }
+
+ return true;
+}
+
+void Collider::recomputeBounds()
+{
+ m_lastX = gameObject->transform.x;
+ m_lastY = gameObject->transform.y;
+
+ if (m_sprite)
+ {
+ RectF spriteBounds = m_sprite->boundaries();
+ m_lastBounds.x = spriteBounds.x + spriteBounds.w * collisionRect.x;
+ m_lastBounds.y = spriteBounds.y + spriteBounds.h * collisionRect.y;
+ m_lastBounds.w = spriteBounds.w * collisionRect.w;
+ m_lastBounds.h = spriteBounds.h * collisionRect.h;
+ }
+ else
+ {
+ m_lastBounds.x = m_lastX;
+ m_lastBounds.y = m_lastY;
+ m_lastBounds.w = 0;
+ m_lastBounds.h = 0;
+ }
+}
+
+} /* namespace basic */
+} /* namespace components */
+} /* namespace farmlands */
diff --git a/src/components/basic/Collider.h b/src/components/basic/Collider.h
new file mode 100644
index 0000000..38151b4
--- /dev/null
+++ b/src/components/basic/Collider.h
@@ -0,0 +1,65 @@
+/*
+ * Collider.h
+ *
+ * Created on: Dec 15, 2016
+ * Author: tibi
+ */
+
+#ifndef COMPONENTS_BASIC_COLLIDER_H_
+#define COMPONENTS_BASIC_COLLIDER_H_
+
+#include
+#include
+#include
+
+namespace farmlands {
+namespace components {
+namespace basic {
+
+ class Collider: public model::Component
+ {
+ public:
+ Collider();
+ virtual ~Collider();
+
+ virtual model::Component* clone() override;
+ virtual void dump(unsigned level) override;
+ virtual void onInitialize() override;
+ virtual void onUpdateLogic() override;
+
+ /**
+ * Computes the collision bounds.
+ */
+ utils::RectF collisionBoundaries() const { return m_lastBounds; }
+
+ /**
+ * Checks if this object collides with another Collider object.
+ */
+ Collider* checkCollision();
+
+ /**
+ * Tests if this object can move to the given coordinates.
+ * Both objects must be solid to register a collision.
+ */
+ bool canMove(float newX, float newY);
+
+ // Collision rectangle, relative to sprite bounds (default is [0, 0, 1, 1])
+ // If object doesn't have a sprite, this property is not used
+ utils::RectF collisionRect;
+
+ // If an object is solid, it cannot be passed through.
+ bool solid;
+
+ private:
+ void recomputeBounds();
+
+ Sprite* m_sprite;
+ utils::RectF m_lastBounds;
+ float m_lastX, m_lastY;
+ };
+
+} /* namespace basic */
+} /* namespace components */
+} /* namespace farmlands */
+
+#endif /* COMPONENTS_BASIC_COLLIDER_H_ */
diff --git a/src/components/npc/MovingNpcController.cpp b/src/components/npc/MovingNpcController.cpp
new file mode 100644
index 0000000..b8ab098
--- /dev/null
+++ b/src/components/npc/MovingNpcController.cpp
@@ -0,0 +1,163 @@
+/*
+ * MovingNpcController.cpp
+ *
+ * Created on: Dec 17, 2016
+ * Author: tibi
+ */
+
+#include
+#include
+#include
+#include
+#include