#define BLANK_AI_AICONTROLLER_HPP_
#include "../app/IntervalTimer.hpp"
+#include "../model/geometry.hpp"
#include "../world/EntityController.hpp"
#include <glm/glm.hpp>
void SetHaltSpeed(float) noexcept;
glm::vec3 GetHaltForce(const Entity &, const EntityState &) const noexcept;
+ void StartAvoidingObstacles() noexcept;
+ void StopAvoidingObstacles() noexcept;
+ bool IsAvoidingObstacles() const noexcept;
+ glm::vec3 GetObstacleAvoidanceForce(const Entity &, const EntityState &) const noexcept;
+
void StartFleeing() noexcept;
void StopFleeing() noexcept;
bool IsFleeing() const noexcept;
bool halted;
float halt_speed;
+ bool avoid_obstacles;
+ AABB obstacle_box;
+ glm::mat4 obstacle_transform;
+
bool fleeing;
Entity *flee_target;
float flee_speed;
, decision_timer(1.0f)
, halted(false)
, halt_speed(1.0f)
+, avoid_obstacles(true)
+, obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) }
+, obstacle_transform(1.0f)
, fleeing(false)
, flee_target(nullptr)
, flee_speed(5.0f)
decision_timer.Update(dt);
state->Update(*this, e, dt);
+ if (avoid_obstacles && e.Moving()) {
+ obstacle_box = e.Bounds();
+ obstacle_box.min.z = -e.Speed();
+ obstacle_box.max.x = 0.0f;
+ // our box is oriented for -Z velocity
+ obstacle_transform = glm::mat4(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), e.Heading()));
+ // and positioned relative to the entity's chunk
+ obstacle_transform[3] = glm::vec4(e.GetState().block_pos, 1.0f);
+ }
+
if (wandering) {
glm::vec3 displacement(
random.SNorm() * wander_disp,
return GetHaltForce(entity, state);
}
glm::vec3 force(0.0f);
+ if (IsAvoidingObstacles() && entity.Moving()) {
+ if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) {
+ return force;
+ }
+ }
if (IsFleeing()) {
if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
return force;
return Halt(state, halt_speed);
}
+// obstacle avoidance
+
+void AIController::StartAvoidingObstacles() noexcept {
+ avoid_obstacles = true;
+}
+
+void AIController::StopAvoidingObstacles() noexcept {
+ avoid_obstacles = false;
+}
+
+bool AIController::IsAvoidingObstacles() const noexcept {
+ return avoid_obstacles;
+}
+
+namespace {
+
+std::vector<WorldCollision> col;
+
+}
+
+glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept {
+ if (!e.Moving()) {
+ return glm::vec3(0.0f);
+ }
+ col.clear();
+ if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) {
+ return glm::vec3(0.0f);
+ }
+ // find the nearest block
+ WorldCollision *nearest = nullptr;
+ glm::vec3 difference(0.0f);
+ float distance = std::numeric_limits<float>::infinity();
+ for (WorldCollision &c : col) {
+ // diff points from block to state
+ glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords();
+ float dist = length_squared(diff);
+ if (dist < distance) {
+ nearest = &c;
+ difference = diff;
+ distance = dist;
+ }
+ }
+ if (!nearest) {
+ // intersection test lied to us
+ return glm::vec3(0.0f);
+ }
+ // and steer away from it
+ // to_go is the distance between our position and the
+ // point on the "velocity ray" closest to obstacle
+ float to_go = dot(difference, e.Heading());
+ // point is our future position if we keep going our way
+ glm::vec3 point(e.GetState().block_pos + e.Heading() * to_go);
+ // now steer away in the direction of (point - block)
+ // with a magniture proportional to speed/distance
+ return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance));
+}
+
// flee
void AIController::StartFleeing() noexcept {
#include "geometry.hpp"
#include <limits>
+#include <glm/gtx/matrix_cross_product.hpp>
+#include <glm/gtx/optimum_pow.hpp>
+#include <glm/gtx/transform.hpp>
namespace blank {
+glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept {
+ glm::vec3 v(cross(a, b));
+ if (iszero(v)) {
+ // a and b are parallel
+ if (iszero(a - b)) {
+ // a and b are identical
+ return glm::mat3(1.0f);
+ } else {
+ // a and b are opposite
+ // create arbitrary unit vector perpendicular to a and
+ // rotate 180° around it
+ glm::vec3 arb(a);
+ if (std::abs(a.x - 1.0f) > std::numeric_limits<float>::epsilon()) {
+ arb.x += 1.0f;
+ } else {
+ arb.y += 1.0f;
+ }
+ glm::vec3 axis(normalize(cross(a, arb)));
+ return glm::mat3(glm::rotate(PI, axis));
+ }
+ }
+ float mv = length_squared(v);
+ float c = dot(a, b);
+ float f = (1 - c) / mv;
+ glm::mat3 vx(matrixCross3(v));
+ return glm::mat3(1.0f) + vx + (pow2(vx) * f);
+}
+
bool Intersection(
const Ray &ray,
const AABB &aabb,
}
+glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept;
+
+
struct AABB {
glm::vec3 min;
glm::vec3 max;
const glm::vec3 &Velocity() const noexcept { return state.velocity; }
- bool Moving() const noexcept {
- return dot(Velocity(), Velocity()) > std::numeric_limits<float>::epsilon();
- }
-
const glm::vec3 &Position() const noexcept { return state.block_pos; }
void Position(const glm::ivec3 &, const glm::vec3 &) noexcept;
void Position(const glm::vec3 &) noexcept;
/// get a ray in entity's face direction originating from center of vision
Ray Aim(const Chunk::Pos &chunk_offset) const noexcept;
+ /// true if this entity's position will change (significantly) the next update
+ bool Moving() const noexcept { return speed > 0.0f; }
+ /// magnitude of velocity
+ float Speed() const noexcept { return speed; }
+ /// normalized velocity or heading if standing still
const glm::vec3 &Heading() const noexcept { return heading; }
void SetState(const EntityState &s) noexcept { state = s; UpdateModel(); }
/// if this entity has no model, the eyes are assumed to
/// be at local origin and oriented towards -Z
glm::mat4 view_local;
- /// normalized velocity or heading if standing still
+ float speed;
glm::vec3 heading;
// TODO: I'd prefer a drag solution
EntityCollision &);
/// check if given entity intersects with the world
-// bool Intersection(const Entity &e, std::vector<WorldCollision> &col) {
-// return Intersection(e, e.GetState(), col);
-// }
bool Intersection(const Entity &e, const EntityState &, std::vector<WorldCollision> &);
+ /// check if given box (M * AABB) intersects with the world
+ /// M is assumed to be calculated in reference to given chunk coords
+ bool Intersection(
+ const AABB &box,
+ const glm::mat4 &M,
+ const glm::ivec3 &reference,
+ std::vector<WorldCollision> &);
+
const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
ChunkStore &Chunks() noexcept { return chunks; }
}
void Entity::UpdateHeading() noexcept {
- if (Moving()) {
- heading = normalize(Velocity());
+ speed = length(Velocity());
+ if (speed > std::numeric_limits<float>::epsilon()) {
+ heading = Velocity() / speed;
} else {
+ speed = 0.0f;
// use -Z (forward axis) of local view transform
heading = -glm::vec3(view_local[2]);
}
AABB box = e.Bounds();
Chunk::Pos reference = s.chunk_pos;
glm::mat4 M = s.Transform(reference);
+ return Intersection(box, M, reference, col);
+}
+
+bool World::Intersection(
+ const AABB &box,
+ const glm::mat4 &M,
+ const glm::ivec3 &reference,
+ std::vector<WorldCollision> &col
+) {
bool any = false;
for (Chunk &cur_chunk : chunks) {
if (manhattan_radius(cur_chunk.Position() - reference) > 1) {
return any;
}
-
void World::Update(int dt) {
float fdt(dt * 0.001f);
for (Entity &entity : entities) {