Implemented collisions.

This commit is contained in:
Tiberiu Chibici 2016-12-17 15:40:40 +02:00
parent 42f0d4125b
commit a7af100122
14 changed files with 651 additions and 11 deletions

View File

@ -26,6 +26,9 @@
<Transform x="120" y="100" />
<Sprite src="sprites/Player.sprite" />
<SpriteRenderer />
<Collider x="0.2" y="0.75"
w="0.6" h="0.25"
solid="true" />
<Inventory capacity="30" />
<Player hp="100" maxHp="100"
energy="100" maxEnergy="100"
@ -45,6 +48,24 @@
<Transform x="125" y="110" />
<Sprite src="sprites/Player.sprite" />
<SpriteRenderer />
<Collider x="0" y="0.5"
w="1" h="0.5"
solid="true" />
<MovingNpcController />
</GameObject>
<GameObject name="something">
<Transform x="130.5" y="100.3" />
<Sprite w="5" h="3"
anchorX="0" anchorY="0">
<State name="Normal">
<Frame tileSet="tilesets/Ground.png" cell="15" duration="1" />
</State>
</Sprite>
<SpriteRenderer />
<Collider solid="true"
x="0" y="0.5"
w="1" h="0.5" />
</GameObject>
</Scene>

View File

@ -8,11 +8,13 @@
#ifndef MODEL_GAMESTATE_H_
#define MODEL_GAMESTATE_H_
#include <components/basic/Collider.h>
#include <graphics/RenderContext.h>
#include <model/GameObject.h>
#include <model/Scene.h>
#include <model/Configuration.h>
#include <graphics/RenderContext.h>
#include <resources/ResourceManager.h>
#include <utils/QTree.h>
#include <utils/Random.h>
#include <memory>
@ -42,6 +44,8 @@ namespace farmlands {
std::vector<model::GameObject*> seedsPrefabs;
std::vector<model::GameObject*> plantsPrefabs;
utils::QTree<components::basic::Collider*>* colliderTree;
// Timing
float time;
uint32_t date;

View File

@ -0,0 +1,187 @@
/*
* Collider.cpp
*
* Created on: Dec 15, 2016
* Author: tibi
*/
#include <GameState.h>
#include <components/basic/Collider.h>
#include <model/GameObject.h>
#include <iostream>
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<Sprite>();
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 */

View File

@ -0,0 +1,65 @@
/*
* Collider.h
*
* Created on: Dec 15, 2016
* Author: tibi
*/
#ifndef COMPONENTS_BASIC_COLLIDER_H_
#define COMPONENTS_BASIC_COLLIDER_H_
#include <components/basic/Sprite.h>
#include <model/Component.h>
#include <utils/QTree.h>
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_ */

View File

@ -0,0 +1,163 @@
/*
* MovingNpcController.cpp
*
* Created on: Dec 17, 2016
* Author: tibi
*/
#include <GameState.h>
#include <components/items/Pickable.h>
#include <components/npc/MovingNpcController.h>
#include <input/Input.h>
#include <math/GameMath.h>
#include <iostream>
using namespace farmlands::components::basic;
using namespace farmlands::components::items;
using namespace farmlands::graphics;
using namespace farmlands::input;
using namespace farmlands::model;
namespace farmlands {
namespace components {
namespace npc {
static const float NpcWalkVelocity = 3.0f; // The default velocity of the player when walking (units/sec).
MovingNpcController::MovingNpcController()
: facingDirection(Direction::South),
walking(false),
m_collider(nullptr),
m_currentDestination(0),
m_waitTime(0)
{
}
MovingNpcController::~MovingNpcController()
{
}
Component* MovingNpcController::clone()
{
MovingNpcController* clone = new MovingNpcController();
// Movement
clone->facingDirection = facingDirection;
clone->walking = walking;
return clone;
}
void MovingNpcController::dump(unsigned level)
{
for (unsigned i = 0; i < level; i++)
std::cout<<" ";
std::cout << " .Component: MovingNpcController\n";
}
void MovingNpcController::onInitialize()
{
m_collider = gameObject->component<Collider>();
static const int offX[] = { 0, 1, 1, 0 };
static const int offY[] = { 0, 0, 1, 1 };
for (size_t i = 0; i < 4; i++)
{
m_destinationsX[i] = gameObject->transform.x + offX[i] * 10;
m_destinationsY[i] = gameObject->transform.y + offY[i] * 10;
}
}
void MovingNpcController::onUpdateLogic()
{
updateMovement();
}
void MovingNpcController::onPreRender()
{
preRenderMovement();
}
void MovingNpcController::preRenderMovement()
{
// Get sprite
Sprite* sprite = gameObject->component<Sprite>();
// Compute current state
std::string stateName = (walking) ? "Walking " : "Idle ";
switch (facingDirection)
{
case Direction::East:
stateName += "right";
break;
case Direction::West:
stateName += "left";
break;
case Direction::North:
stateName += "up";
break;
case Direction::South:
stateName += "down";
break;
}
sprite->setState(stateName);
}
void MovingNpcController::updateMovement()
{
walking = false;
if (m_waitTime > 0)
{
m_waitTime -= GameState::current().deltaTime;
return;
}
// Arrived at the destination?
bool arrivedX = std::abs(gameObject->transform.x - m_destinationsX[m_currentDestination]) < 0.5f;
bool arrivedY = std::abs(gameObject->transform.y - m_destinationsY[m_currentDestination]) < 0.5f;
if (arrivedX && arrivedY)
{
m_currentDestination = (m_currentDestination + 1) % 4;
m_waitTime += 2.0f;
return;
}
// Simply move
float speed = NpcWalkVelocity * GameState::current().deltaTime;
float newX = gameObject->transform.x;
float newY = gameObject->transform.y;
moveTowards(&newX, &newY, m_destinationsX[m_currentDestination], m_destinationsY[m_currentDestination], speed);
if (canMove(newX, newY))
{
facingDirection = getDirection(newX - gameObject->transform.x, newY - gameObject->transform.y);
gameObject->transform.x = newX;
gameObject->transform.y = newY;
walking = true;
}
}
Direction MovingNpcController::getDirection(float vx, float vy)
{
if (fabsf(vx) >= fabsf(vy))
return (vx > 0) ? Direction::East : Direction::West;
else
return (vy > 0) ? Direction::South : Direction::North;
}
bool MovingNpcController::canMove(float x, float y)
{
return m_collider->canMove(x, y);
}
} /* namespace npc */
} /* namespace components */
} /* namespace farmlands */

View File

@ -0,0 +1,55 @@
/*
* MovingNpcController.h
*
* Created on: Dec 17, 2016
* Author: tibi
*/
#ifndef COMPONENTS_NPC_MOVINGNPCCONTROLLER_H_
#define COMPONENTS_NPC_MOVINGNPCCONTROLLER_H_
#include <model/Component.h>
#include <model/Direction.h>
namespace farmlands {
namespace components {
namespace npc {
class MovingNpcController: public model::Component
{
public:
MovingNpcController();
virtual ~MovingNpcController();
virtual model::Component* clone() override;
virtual void dump(unsigned level) override;
virtual void onInitialize() override;
virtual void onUpdateLogic() override;
virtual void onPreRender() override;
// Movement
model::Direction facingDirection;
bool walking;
private:
// Movement
void preRenderMovement();
void updateMovement();
bool canMove(float x, float y);
static model::Direction getDirection(float vx, float vy);
float m_destinationsX[4];
float m_destinationsY[4];
size_t m_currentDestination;
float m_waitTime;
basic::Collider* m_collider;
};
} /* namespace npc */
} /* namespace components */
} /* namespace farmlands */
#endif /* COMPONENTS_NPC_MOVINGNPCCONTROLLER_H_ */

View File

@ -45,6 +45,7 @@ Player::Player()
hp(100), maxHp(100),
energy(100), maxEnergy(100),
money(0),
m_collider(nullptr),
m_inventory(nullptr),
m_grid(nullptr),
m_pickables(nullptr)
@ -92,6 +93,7 @@ void Player::dump(unsigned level)
void Player::onInitialize()
{
m_collider = gameObject->component<Collider>();
m_inventory = gameObject->component<Inventory>();
auto root = &GameState::current().scene->root;
@ -310,7 +312,7 @@ void Player::updateMovement()
Direction Player::getDirection(float vx, float vy)
{
if (vx != 0)
if (fabsf(vx) >= fabsf(vy))
return (vx > 0) ? Direction::East : Direction::West;
return (vy > 0) ? Direction::South : Direction::North;
}
@ -356,6 +358,11 @@ void Player::performAction(model::GameObject* obj)
}
}
bool Player::canMove(float x, float y)
{
return m_collider->canMove(x, y);
}
void Player::updatePickables()
{
// Don't do anything if inventory is full

View File

@ -8,6 +8,7 @@
#ifndef COMPONENTS_PLAYER_PLAYER_H_
#define COMPONENTS_PLAYER_PLAYER_H_
#include <components/basic/Collider.h>
#include <components/basic/Grid.h>
#include <components/basic/Inventory.h>
#include <components/items/Weapon.h>
@ -69,7 +70,7 @@ namespace player {
// Movement
void preRenderMovement();
void updateMovement();
bool canMove(float x, float y) { return true; }
bool canMove(float x, float y);
static model::Direction getDirection(float vx, float vy);
// Actions
@ -79,6 +80,8 @@ namespace player {
// Picking up
void updatePickables();
basic::Collider* m_collider;
basic::Inventory* m_inventory;
basic::Grid* m_grid;

View File

@ -72,7 +72,7 @@ void SpriteRenderer::onRender()
src.h = spriteH;
// Draw
SdlRenderer::instance().renderTexture(texture, &src, &dest);
SdlRenderer::instance().queueRenderTexture(texture, &src, &dest, 0);
}
}

View File

@ -26,9 +26,10 @@ void move(float *x, float *y, model::Direction direction, float distance)
void moveTowards(float *x, float *y, float towardsX, float towardsY, float speed)
{
float angle = atan2f(towardsX - *x, towardsY - *y);
// Y coordinate is opposite of trigonometric coordinates
float angle = atan2f(-(towardsY - *y), towardsX - *x);
*x += cosf(angle) * speed;
*y += sinf(angle) * speed;
*y -= sinf(angle) * speed;
}
float distanceSq(float x0, float y0, float x1, float y1)

View File

@ -114,6 +114,13 @@ void ResourceManager::loadGame()
model::GameObject* item = model::GameObject::instantiate(prefab, "inv seed", player);
inventory->add(item);
}
// Create collision qtree
auto mapObjIt = root->findByComponent<components::Map>();
Assert (mapObjIt != root->childrenEnd(), "Can't find map!");
components::Map* map = (*mapObjIt)->component<components::Map>();
utils::RectF colliderArea(0, 0, map->width, map->height);
GameState::current().colliderTree = new utils::QTree<components::basic::Collider*>(colliderArea);
}
std::string ResourceManager::getPath(ResourceId resourceId)

View File

@ -114,6 +114,25 @@ components::basic::Camera* parse<components::basic::Camera> (boost::property_tre
return camera;
}
template <>
components::basic::Collider* parse<components::basic::Collider> (boost::property_tree::ptree& root)
{
if (root.size() > 0 && root.front().first == "Collider")
root = root.front().second;
components::basic::Collider* collider = new components::basic::Collider();
collider->solid = root.get<bool>("<xmlattr>.solid", false);
utils::RectF collisionRect;
collisionRect.x = root.get<float>("<xmlattr>.x", 0.0f);
collisionRect.y = root.get<float>("<xmlattr>.y", 0.0f);
collisionRect.w = root.get<float>("<xmlattr>.w", 1.0f);
collisionRect.h = root.get<float>("<xmlattr>.h", 1.0f);
collider->collisionRect = collisionRect;
return collider;
}
template <>
components::basic::Frame* parse<components::basic::Frame> (boost::property_tree::ptree& root)
{
@ -461,6 +480,23 @@ model::Direction parseDirection(std::string directionStr)
return direction;
}
template <>
components::npc::MovingNpcController* parse<components::npc::MovingNpcController> (boost::property_tree::ptree& root)
{
// Ensure we are on the correct node (property tree seems to add root of its own)
if (root.size() > 0 && root.front().first == "MovingNpcController")
root = root.front().second;
components::npc::MovingNpcController* npc = new components::npc::MovingNpcController();
// Movement
std::string direction = root.get<std::string>("<xmlattr>.facingDirection", "South");
npc->facingDirection = parseDirection(direction);
npc->walking = root.get<bool>("<xmlattr>.walking", false);
return npc;
}
template <>
components::player::Player* parse<components::player::Player> (boost::property_tree::ptree& root)
{
@ -560,6 +596,9 @@ model::GameObject* parse<model::GameObject> (boost::property_tree::ptree& root)
else if (child.first == "Camera")
gameObj->addComponent(parse<components::basic::Camera>(child.second));
else if (child.first == "Collider")
gameObj->addComponent(parse<components::basic::Collider>(child.second));
else if (child.first == "Grid")
gameObj->addComponent(parse<components::basic::Grid>(child.second));
@ -607,6 +646,10 @@ model::GameObject* parse<model::GameObject> (boost::property_tree::ptree& root)
else if (child.first == "Seed")
gameObj->addComponent(parse<components::plants::Seed>(child.second));
// Components::npc
else if (child.first == "MovingNpcController")
gameObj->addComponent(parse<components::npc::MovingNpcController>(child.second));
// Components::player
else if (child.first == "Player")
gameObj->addComponent(parse<components::player::Player>(child.second));

View File

@ -9,6 +9,7 @@
#define STORAGE_PARSERS_PARSERS_H_
#include <components/basic/Camera.h>
#include <components/basic/Collider.h>
#include <components/basic/Grid.h>
#include <components/basic/Inventory.h>
#include <components/basic/InventoryItem.h>
@ -28,6 +29,7 @@
#include <components/Map.h>
#include <components/plants/Plant.h>
#include <components/plants/Seed.h>
#include <components/npc/MovingNpcController.h>
#include <components/player/Player.h>
#include <graphics/MapRenderer.h>
#include <graphics/SpriteRenderer.h>
@ -49,6 +51,9 @@ namespace storage {
template <>
components::basic::Camera* parse<components::basic::Camera> (boost::property_tree::ptree& root);
template <>
components::basic::Collider* parse<components::basic::Collider> (boost::property_tree::ptree& root);
template <>
components::basic::Frame* parse<components::basic::Frame> (boost::property_tree::ptree& root);
@ -109,6 +114,9 @@ namespace storage {
template <>
components::plants::Seed* parse<components::plants::Seed> (boost::property_tree::ptree& root);
template <>
components::npc::MovingNpcController* parse<components::npc::MovingNpcController> (boost::property_tree::ptree& root);
template <>
components::player::Player* parse<components::player::Player> (boost::property_tree::ptree& root);

View File

@ -9,6 +9,7 @@
#define UTILS_QTREE_H_
#include <utils/Assert.h>
#include <utils/Exceptions.h>
#include <utils/INonAssignable.h>
#include <utils/Rect.h>
@ -58,7 +59,7 @@ namespace utils {
/**
* Quad tree
*/
template <typename T, size_t Capacity>
template <typename T, size_t Capacity = 16>
class QTree : public INonAssignable
{
public:
@ -74,11 +75,13 @@ namespace utils {
bool empty() const;
size_t size() const;
void insert(T element, float x, float y);
void insert(const T& element, float x, float y);
void move(iterator it, float newX, float newY);
void erase(iterator it);
void clear();
iterator find(T element);
iterator find(const T& element);
iterator find(const T& element, float x, float y);
iterator lower_bound(const RectF& area);
iterator upper_bound(const RectF& area);
@ -312,7 +315,7 @@ namespace utils {
}
template<typename T, size_t Capacity>
void QTree<T, Capacity>::insert(T element, float x, float y)
void QTree<T, Capacity>::insert(const T& element, float x, float y)
{
Assert(m_bounds.contains(x, y), "Can't add element outside bounds.");
@ -344,6 +347,54 @@ namespace utils {
}
}
template<typename T, size_t Capacity>
void QTree<T, Capacity>::move(iterator it, float newX, float newY)
{
Assert(!it.m_tree->m_isSplit, "Container modified.");
Assert(it.m_item < it.m_tree->m_itemsCount, "Container modified.");
// Find destination tree
QTree<T, Capacity>* destTree = this;
bool found = false;
do
{
// Go to parent
if (!destTree->m_bounds.contains(newX, newY))
destTree = destTree->m_parent;
// Go to child
else if (destTree->m_isSplit)
{
for (size_t i = 0; i < 4; i++)
if (destTree->m_children[i]->m_bounds.contains(newX, newY))
{
destTree = destTree->m_children[i];
break;
}
}
else
{
found = true;
}
} while (!found && destTree != nullptr);
if (destTree == nullptr)
THROW(InvalidArgumentException, "Iterator doesn't belong to this tree.");
// No need to move
if (destTree == it.m_tree)
{
it->x = newX;
it->y = newY;
}
// Need to move. Preform an 'erase' and an 'insert'
T dataTemp = it->data;
it.m_tree->erase(it);
destTree->insert(dataTemp, newX, newY);
}
template<typename T, size_t Capacity>
void QTree<T, Capacity>::erase(iterator it)
{
@ -385,7 +436,7 @@ namespace utils {
}
template<typename T, size_t Capacity>
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::find(T element)
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::find(const T& element)
{
for (auto it = begin(); it != end(); it++)
if (it->data == element)
@ -394,6 +445,31 @@ namespace utils {
return end();
}
template<typename T, size_t Capacity>
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::find(const T& element, float x, float y)
{
if (!m_bounds.contains(x, y))
{
if (m_parent)
return m_parent->find(element, x, y);
return end();
}
if (m_isSplit)
{
for (size_t i = 0; i < 4; i++)
if (m_children[i]->m_bounds.contains(x, y))
return m_children[i]->find(element, x, y);
}
else
{
for (size_t i = 0; i < m_itemsCount; i++)
if (m_items[i].data == element && m_items[i].x == x && m_items[i].y == y)
return iterator(this, RectF(x, y, 0, 0), i);
}
return end();
}
template<typename T, size_t Capacity>
typename QTree<T, Capacity>::iterator QTree<T, Capacity>::lower_bound(const RectF& area)
{