Added raycast.

This commit is contained in:
Felix Ableitner 2013-04-04 22:44:15 +02:00
parent 7ebf912aad
commit a094c01d55
6 changed files with 171 additions and 78 deletions

View file

@ -12,6 +12,8 @@
#include <Thor/Vectors.hpp> #include <Thor/Vectors.hpp>
#include "util/Interval.h"
/** /**
* Insert a drawable into the group. Drawables should only be handled with shared_ptr. * Insert a drawable into the group. Drawables should only be handled with shared_ptr.
* An object can't be inserted more than once at the same level. * An object can't be inserted more than once at the same level.
@ -335,9 +337,10 @@ World::testCollision(std::shared_ptr<Sprite> spriteA,
float movementB = thor::dotProduct(axis, spriteB->getSpeed() * (elapsed / 1000.0f)); float movementB = thor::dotProduct(axis, spriteB->getSpeed() * (elapsed / 1000.0f));
// Allow movement if sprites are moving apart. // Allow movement if sprites are moving apart.
return Interval(centerA, radiusA).getOverlap(Interval(centerB, radiusB)).getLength() < return Interval::IntervalFromRadius(centerA, radiusA).getOverlap(
Interval(centerA + movementA, radiusA).getOverlap( Interval::IntervalFromRadius(centerB, radiusB)).getLength() <
Interval(centerB + movementB, radiusB)).getLength(); Interval::IntervalFromRadius(centerA + movementA, radiusA).getOverlap(
Interval::IntervalFromRadius(centerB + movementB, radiusB)).getLength();
} }
// circle-rect collision // circle-rect collision
if (((spriteA->mShape.type == Sprite::Shape::Type::CIRCLE) && if (((spriteA->mShape.type == Sprite::Shape::Type::CIRCLE) &&
@ -357,21 +360,21 @@ World::testCollision(std::shared_ptr<Sprite> spriteA,
sf::Vector2f circleMovement = circle->getSpeed() * (elapsed / 1000.0f); sf::Vector2f circleMovement = circle->getSpeed() * (elapsed / 1000.0f);
// We assume that rectangles are always axis aligned. // We assume that rectangles are always axis aligned.
float overlapNoMovementX = Interval(circlePos.x, radius) float overlapNoMovementX = Interval::IntervalFromRadius(circlePos.x, radius)
.getOverlap(Interval (rectPos.x, halfsize.x)).getLength(); .getOverlap(Interval::IntervalFromRadius(rectPos.x, halfsize.x)).getLength();
float overlapMovementX = Interval(circlePos.x + circleMovement.x, radius) float overlapMovementX = Interval::IntervalFromRadius(circlePos.x + circleMovement.x, radius)
.getOverlap(Interval (rectPos.x, halfsize.x)).getLength(); .getOverlap(Interval::IntervalFromRadius(rectPos.x, halfsize.x)).getLength();
float overlapNoMovementY = Interval(circlePos.y, radius) float overlapNoMovementY = Interval::IntervalFromRadius(circlePos.y, radius)
.getOverlap(Interval (rectPos.y, halfsize.y)).getLength(); .getOverlap(Interval::IntervalFromRadius(rectPos.y, halfsize.y)).getLength();
float overlapMovementY = Interval(circlePos.y + circleMovement.y, radius) float overlapMovementY = Interval::IntervalFromRadius(circlePos.y + circleMovement.y, radius)
.getOverlap(Interval (rectPos.y, halfsize.y)).getLength(); .getOverlap(Interval::IntervalFromRadius(rectPos.y, halfsize.y)).getLength();
bool xyCollisionResult = (((overlapNoMovementX < overlapMovementX) && bool xyCollisionResult = (((overlapNoMovementX < overlapMovementX) &&
(overlapNoMovementY > 0)) || (overlapNoMovementY > 0)) ||
((overlapNoMovementY < overlapMovementY) && (overlapNoMovementX > 0))); ((overlapNoMovementY < overlapMovementY) && (overlapNoMovementX > 0)));
// If circle center is overlapping rectangle on x or y axis, we can take xyCollisionResult. // If circle center is overlapping rectangle on x or y axis, we can take xyCollisionResult.
if (Interval(rectPos.x, halfsize.x).isInside(circlePos.x) || if (Interval::IntervalFromRadius(rectPos.x, halfsize.x).isInside(circlePos.x) ||
Interval(rectPos.y, halfsize.y).isInside(circlePos.y)) { Interval::IntervalFromRadius(rectPos.y, halfsize.y).isInside(circlePos.y)) {
return xyCollisionResult; return xyCollisionResult;
} }
// Test if the circle is colliding with a corner of the rectangle. // Test if the circle is colliding with a corner of the rectangle.
@ -397,11 +400,13 @@ World::testCollision(std::shared_ptr<Sprite> spriteA,
sf::Vector2f(halfsize.x, -halfsize.y)))); sf::Vector2f(halfsize.x, -halfsize.y))));
// Allow movement if sprites are moving apart. // Allow movement if sprites are moving apart.
return Interval(circlePosProjected, radius) return Interval::IntervalFromRadius(circlePosProjected, radius)
.getOverlap(Interval(rectPosProjected, rectHalfWidthProjected)) .getOverlap(Interval::IntervalFromRadius(rectPosProjected,
rectHalfWidthProjected))
.getLength() < .getLength() <
Interval(circlePosProjected + movementProjected, radius) Interval::IntervalFromRadius(circlePosProjected + movementProjected, radius)
.getOverlap(Interval(rectPosProjected, rectHalfWidthProjected)) .getOverlap(Interval::IntervalFromRadius(rectPosProjected,
rectHalfWidthProjected))
.getLength(); .getLength();
} }
// If there is no collision on x and y axis, there can't be one at all. // If there is no collision on x and y axis, there can't be one at all.
@ -439,52 +444,3 @@ World::draw(sf::RenderTarget& target, sf::RenderStates states) const {
} }
} }
} }
/**
* Creates an interval from a center point and a radius. The interval
* ranges from center - radius to center + radius.
*/
World::Interval::Interval(float center, float radius) :
start(center - radius),
end(center + radius) {
}
/**
* Returns the overlap between two intervals, e.g. the overlap between
* intervals (1,3) and (2,4) is (2,3).
*/
World::Interval
World::Interval::getOverlap(Interval other) const {
if ((start == other.start) && (end == other.end)) {
return *this;
}
Interval smaller = *this;
Interval bigger = other;
if (smaller.start > bigger.start) {
std::swap(smaller, bigger);
}
Interval iv(0, 0);
if (bigger.start < smaller.end) {
iv.start = bigger.start;
iv.end = smaller.end;
}
else {
iv.start = iv.end = 0.0f;
}
return iv;
}
/**
* Returns true if the point is inside the interval.
*/
bool
World::Interval::isInside(float point) const {
return start < point && point < end;
}
/**
* Returns the length of the interval (distance between start and end).
*/
float
World::Interval::getLength() {
return end - start;
}

View file

@ -35,18 +35,6 @@ public:
std::vector<std::shared_ptr<Character> > std::vector<std::shared_ptr<Character> >
getCharacters(const sf::Vector2f& position, float maxDistance) const; getCharacters(const sf::Vector2f& position, float maxDistance) const;
// Private types.
private:
class Interval {
public:
float start;
float end;
Interval(float center, float radius);
Interval getOverlap(Interval other) const;
bool isInside(float point) const;
float getLength();
};
// Private types. // Private types.
private: private:
struct Area; struct Area;

View file

@ -7,6 +7,9 @@
#include "TileManager.h" #include "TileManager.h"
#include <Thor/Vectors.hpp>
#include "../util/Interval.h"
#include "../util/Loader.h" #include "../util/Loader.h"
#include "../util/Yaml.h" #include "../util/Yaml.h"
#include "../World.h" #include "../World.h"
@ -104,7 +107,50 @@ TileManager::removeTile(const TilePosition& position) {
mTiles.erase(it); mTiles.erase(it);
} }
} }
}
/*
* Performs a raycast between two points to check if the path between them is
* clear of walls.
*
* @param lineStart First point of the line to test.
* @param lineEnd Second point of the line to test.
* @return True if the ray was not blocked.
*/
bool
TileManager::raycast(const sf::Vector2f& lineStart,
const sf::Vector2f& lineEnd) const {
assert(lineStart != lineEnd);
sf::Vector2f lineCenter = lineStart + 0.5f * (lineEnd - lineStart);
for (auto it : mTiles) {
if (it->getType() == Type::FLOOR) {
continue;
}
sf::Vector2f axis = it->getPosition() - lineCenter;
if (axis == sf::Vector2f()) {
return false;
}
axis = thor::unitVector(axis);
sf::Vector2f halfsize = it->getSize() / 2.0f;
float rectPosProjected = thor::dotProduct(axis, it->getPosition());
float lineStartProjected = thor::dotProduct(axis, lineStart);
float lineEndProjected = thor::dotProduct(axis, lineEnd);
// For corner projections, those on the same line with the rect
// center are equal by value, so we only need one on each axis
// and take the maximum.
float rectHalfWidthProjected = std::max(
abs(thor::dotProduct(axis, halfsize)),
abs(thor::dotProduct(axis,
sf::Vector2f(halfsize.x, -halfsize.y))));
Interval line = Interval::IntervalFromPoints(lineStartProjected, lineEndProjected);
Interval rect = Interval::IntervalFromRadius(rectPosProjected, rectHalfWidthProjected);
// Allow movement if sprites are moving apart.
if (line.getOverlap(rect).getLength() > 0.0f) {
return false;
}
}
return true;
} }
/** /**

View file

@ -36,6 +36,8 @@ public:
void insertTile(const TilePosition& position, Type type); void insertTile(const TilePosition& position, Type type);
void removeTile(const TilePosition& position); void removeTile(const TilePosition& position);
bool raycast(const sf::Vector2f& lineStart,
const sf::Vector2f& lineEnd) const;
// Private types. // Private types.
private: private:

74
source/util/Interval.cpp Normal file
View file

@ -0,0 +1,74 @@
/*
* Interval.cpp
*
* Created on: 03.04.2013
* Author: Felix
*/
#include "Interval.h"
#include <utility>
Interval::Interval(float start, float end) :
start(start),
end(end) {
if (this->start > this->end) {
std::swap(this->start, this->end);
}
};
/**
* Creates an interval from a center point and a radius. The interval
* ranges from center - radius to center + radius.
*/
Interval
Interval::IntervalFromRadius(float center, float radius) {
return Interval(center - radius, center + radius);
}
/**
* Creates an Interval from a start and end point.
*/
Interval
Interval::IntervalFromPoints(float start, float end) {
return Interval(start, end);
}
/**
* Returns the overlap between two intervals, e.g. the overlap between
* intervals (1,3) and (2,4) is (2,3).
*/
Interval
Interval::getOverlap(Interval other) const {
if ((start == other.start) && (end == other.end)) {
return *this;
}
Interval smaller = *this;
Interval bigger = other;
if (smaller.start > bigger.start) {
std::swap(smaller, bigger);
}
Interval iv(0, 0);
if (bigger.start < smaller.end) {
iv.start = bigger.start;
iv.end = smaller.end;
}
else {
iv.start = iv.end = 0.0f;
}
return iv;
}
/**
* Returns true if the point is inside the interval.
*/
bool
Interval::isInside(float point) const {
return start < point && point < end;
}
/**
* Returns the length of the interval (distance between start and end).
*/
float
Interval::getLength() {
return end - start;
}

27
source/util/Interval.h Normal file
View file

@ -0,0 +1,27 @@
/*
* Interval.h
*
* Created on: 03.04.2013
* Author: Felix
*/
#ifndef DG_INTERVAL_H_
#define DG_INTERVAL_H_
class Interval {
public:
static Interval IntervalFromRadius(float center, float radius);
static Interval IntervalFromPoints(float start, float end);
Interval getOverlap(Interval other) const;
bool isInside(float point) const;
float getLength();
private:
Interval(float start, float end);
private:
float start;
float end;
};
#endif /* DG_INTERVAL_H_ */