💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

293 lines
8.0 KiB

/*
* Copyright (C) 2017 Christopher J. Howard
*
* This file is part of Antkeeper Source Code.
*
* Antkeeper Source Code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper Source Code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper Source Code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ant.hpp"
#include "colony.hpp"
#include "pheromone-matrix.hpp"
#include <cmath>
float FRAMES_PER_SECOND = 60;
float TIMESTEP = 1.0f / FRAMES_PER_SECOND;
float ANT_LENGTH = 0.5f; // 0.5 cm, head to abdomen (not including legs / antennae)
float ANT_COLLISION_RADIUS = ANT_LENGTH * 1.25f;
float RECEPTOR_RADIUS = 0.4f;
float RECEPTOR_SEPARATION = 0.882f;
float RECEPTOR_DISTANCE = 0.588f;
float MOUTH_DISTANCE = 0.2646f;
float BITE_RADIUS = 0.0294f;
float FOOD_PARTICLE_RADIUS = 0.1176f;
float MAX_RECEPTOR_NOISE = 0.05f; // essentially an epsilon
float MAX_EXCITEMENT = 1.0f;
float MAX_PHEROMONE_TURNING_ANGLE = glm::radians(8.5f);
float MIN_WALK_TIME = 0.5f; // seconds
float MAX_WALK_TIME = 8.0f; // seconds
float MIN_REST_TIME = 0.15f;
float MAX_REST_TIME = 0.7f;
float MIN_CHEW_TIME = 0.25f;
float MAX_CHEW_TIME = 0.5f;
float DEEXCITEMENT_FACTOR = 0.999f; // This should probably always be less than the evaporation factor
float CALM_FACTOR = 0.995f;
float MAX_WALK_FORCE = 1.5;
float MAX_PANIC_FORCE = 0.1029f;
float MAX_WALK_SPEED = 3.0f; // cm/s
float MAX_PANIC_SPEED = 8.82f; // cm/s
float PANIC_RADIUS = 7.35f;
float WANDER_CIRCLE_DISTANCE = 0.441f;
float WANDER_CIRCLE_RADIUS = 0.0294f;
float MAX_WANDER_ANGLE = 0.15f;
inline float fwrap(float angle, float limit)
{
return angle - std::floor(angle / limit) * limit;
}
Ant::Ant(Colony* colony):
colony(colony),
state(Ant::State::IDLE),
transform(Transform::getIdentity()),
pose(nullptr)
{
pose = new Pose(colony->getAntModel()->getSkeleton());
pose->reset();
pose->concatenate();
modelInstance.setModel(colony->getAntModel());
modelInstance.setPose(pose);
animationTime = frand(0.0f, 60.0f);
velocity = Vector3(0);
acceleration = Vector3(0);
wanderDirection = getForward();
excitement = MAX_EXCITEMENT;
}
Ant::~Ant()
{
delete pose;
}
void Ant::animate()
{
colony->getTripodGaitAnimation()->animate(pose, animationTime);
pose->concatenate();
animationTime = fwrap(animationTime + 4.0f, colony->getTripodGaitAnimation()->getEndTime());
}
void Ant::suspend(const Vector3& suspensionPoint, const Quaternion& suspensionRotation)
{
transform.translation = suspensionPoint;
transform.rotation = suspensionRotation;
modelInstance.setTransform(transform);
}
void Ant::move(const Vector3& velocity)
{
std::vector<Navmesh::Step> traversal;
Navmesh::traverse(getNavmeshTriangle(), getBarycentricPosition(), velocity, &traversal);
if (!traversal.empty())
{
const Navmesh::Step& step = traversal.back();
if (step.start != step.end)
{
if (step.triangle != getNavmeshTriangle())
{
Quaternion alignment = glm::rotation(getNavmeshTriangle()->normal, step.triangle->normal);
Vector3 newForward = glm::normalize(project_on_plane(alignment * getForward(), Vector3(0.0f), step.triangle->normal));
setOrientation(newForward, step.triangle->normal);
}
}
setPosition(step.triangle, step.end);
}
}
void Ant::turn(float angle)
{
// Rotate forward vector
Vector3 newForward = glm::normalize(glm::angleAxis(angle, getUp()) * getForward());
setOrientation(newForward, getUp());
}
void Ant::update(float dt)
{
float probeLateralOffset = 0.1f;
float probeForwardOffset = 0.3f;
animate();
// Calculate positions of receptors
receptorL = getPosition() + getForward() * RECEPTOR_DISTANCE;
receptorR = receptorL;
receptorL -= getRight() * RECEPTOR_SEPARATION * 0.5f;
receptorR += getRight() * RECEPTOR_SEPARATION * 0.5f;
// Steering
if (state == Ant::State::WANDER)
{
//setWanderCircleDistance(4.0f);
//setWanderCircleRadius(0.3f);
//setWanderRate(glm::radians(90.0f));
//setSeparationRadius(0.5f);
//setMaxSpeed(0.025f);
// Calculate wander force
Vector3 wanderForce = wander() * 1.5f;
Vector3 followForce = follow() * 3.0f;
// Setup containment probes
//Vector3 leftProbe = getForward() * probeForwardOffset - getRight() * probeLateralOffset;
//Vector3 rightProbe = getForward() * probeForwardOffset + getRight() * probeLateralOffset;
// Calculate containment force
//Vector3 containmentForce = containment(leftProbe) + containment(rightProbe);
// Determine neighbors
//float neighborhoodSize = 2.0f;
//AABB neighborhoodAABB(getPosition() - Vector3(neighborhoodSize * 0.5f), getPosition() + Vector3(neighborhoodSize * 0.5f));
//std::list<Agent*> neighbors;
//colony->queryAnts(neighborhoodAABB, &neighbors);
// Calculate separation force
//Vector3 separationForce = separation(neighbors);
applyForce(wanderForce);
applyForce(followForce);
float maxSpeed = MAX_WALK_SPEED * TIMESTEP;
// Limit acceleration
float accelerationMagnitudeSquared = glm::dot(acceleration, acceleration);
if (accelerationMagnitudeSquared > MAX_WALK_FORCE * MAX_WALK_FORCE)
{
acceleration = glm::normalize(acceleration) * MAX_WALK_FORCE;
}
// Accelerate
velocity += acceleration;
// Limit speed
float speedSquared = glm::dot(velocity, velocity);
if (speedSquared > maxSpeed * maxSpeed)
{
velocity = glm::normalize(velocity) * maxSpeed;
}
Vector3 direction = glm::normalize(velocity);
setOrientation(direction, getUp());
// Deposit pheromones
Vector2 position2D = Vector2(getPosition().x, getPosition().z);
colony->getHomingMatrix()->deposit(position2D, excitement);
excitement *= DEEXCITEMENT_FACTOR;
// Move ant
move(velocity);
}
else if (state == Ant::State::IDLE)
{
velocity = Vector3(0.0f);
// Move ant
move(velocity);
}
// Update transform
if (state == Ant::State::WANDER || state == Ant::State::IDLE)
{
transform.translation = getPosition();
transform.rotation = getRotation();
// Update model instance
modelInstance.setTransform(transform);
}
}
void Ant::setState(Ant::State state)
{
this->state = state;
}
Vector3 Ant::seek(const Vector3& target)
{
Vector3 steer(0.0f);
Vector3 difference = target - getPosition();
float distanceSquared = glm::dot(difference, difference);
if (distanceSquared > 0.0f)
{
float maxForce = MAX_WALK_FORCE;
steer = glm::normalize(difference) * maxForce - velocity;
}
return steer;
}
Vector3 Ant::flee(const Vector3& target)
{
return -seek(target);
}
Vector3 Ant::wander()
{
// Determine center of wander circle
Vector3 center = getPosition() + getForward() * WANDER_CIRCLE_DISTANCE;
// Calculate wander target
Vector3 target = center + wanderDirection * WANDER_CIRCLE_RADIUS;
// Rotate wander direction by a random displacement angle
float displacement = frand(-MAX_WANDER_ANGLE, MAX_WANDER_ANGLE);
wanderDirection = glm::normalize(glm::angleAxis(displacement, getUp()) * wanderDirection);
return seek(target);
}
Vector3 Ant::follow()
{
const PheromoneMatrix* pheromoneMatrix = colony->getRecruitmentMatrix();
Vector2 receptorL2D = Vector2(receptorL.x, receptorL.z);
Vector2 receptorR2D = Vector2(receptorR.x, receptorR.z);
float signalL = pheromoneMatrix->query(receptorL2D, RECEPTOR_RADIUS);
signalL += frand(0.0f, MAX_RECEPTOR_NOISE);
float signalR = pheromoneMatrix->query(receptorR2D, RECEPTOR_RADIUS);
signalR += frand(0.0f, MAX_RECEPTOR_NOISE);
if (signalL + signalR > 0.0f)
{
float angle = -MAX_PHEROMONE_TURNING_ANGLE * ((signalL - signalR) / (signalL + signalR));
Vector3 steer = glm::normalize(glm::angleAxis(angle, getUp()) * getForward());
return steer;
}
return Vector3(0.0f);
}
void Ant::applyForce(const Vector3& force)
{
acceleration += force;
}