From af7e23ab119eba7c0579796abd288c027edabfa9 Mon Sep 17 00:00:00 2001 From: Paul Oliver Date: Thu, 29 Feb 2024 19:20:22 +0100 Subject: Initial commit --- src/Agent.cpp | 256 ++++++++++++++++++++++++++ src/App.cpp | 538 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/NR_Utils.cpp | 88 +++++++++ src/Stage.cpp | 192 ++++++++++++++++++++ 4 files changed, 1074 insertions(+) create mode 100644 src/Agent.cpp create mode 100644 src/App.cpp create mode 100644 src/NR_Utils.cpp create mode 100644 src/Stage.cpp (limited to 'src') diff --git a/src/Agent.cpp b/src/Agent.cpp new file mode 100644 index 0000000..c082b96 --- /dev/null +++ b/src/Agent.cpp @@ -0,0 +1,256 @@ +#include + +#include + +#include +#include + +using namespace hn; +using namespace sf; +using namespace std; + +size_t +Agent::_currentLifetime = 1500; + +void +Agent::create(Stage& stage) +{ + _eyeSight.resize(5); + + _currCheckPoint = &stage._checkPoints.back(); + _nextCheckPoint = &stage._checkPoints.front(); + + _shape.setRadius(AGENT_RAD); + _shape.setOrigin(AGENT_RAD, AGENT_RAD); + _shape.setFillColor(AGENT_COLOR); + + b2CircleShape agentShape; + agentShape.m_radius = AGENT_RAD; + + b2FixtureDef agentFixture; + agentFixture.shape = &agentShape; + agentFixture.density = 1.0f; + agentFixture.friction = 0.3f; + agentFixture.filter.groupIndex = -1; + agentFixture.userData = this; + + b2BodyDef agentDef; + agentDef.type = b2_dynamicBody; + + _body = stage._world.CreateBody(&agentDef); + _body->CreateFixture(&agentFixture); + + recreate(stage, false); +} + +void +Agent::recreate(const Stage& stage, bool createNNet) +{ + _currCheckPoint = &stage._checkPoints.back(); + _nextCheckPoint = &stage._checkPoints.front(); + + _completedCircuits = 0; + _lifetime = 0; + _distance = 0.0; + _isOnTrap = false; + _isOld = false; + + auto factoryPos = stage._factory.getPosition(); + double factoryRot = stage._factory.getRotation() / IN_RADIANS; + + _body->SetTransform({factoryPos.x, factoryPos.y}, factoryRot); + _body->SetLinearVelocity( { 0.0f, 0.0f }); + _body->SetAngularVelocity(0.0f); + + float pX = _body->GetPosition().x; + float pY = _body->GetPosition().y; + float angle = _body->GetAngle() / IN_DEGREES; + + Transform tr; + tr.translate(pX, pY); + tr.rotate(angle); + + float sight = AGENT_SIGHT; + float iSight = AGENT_SIGHT * 0.7071f; + + _eyeSight[0] = tr.transformPoint({ -sight, 0.0f}); + _eyeSight[1] = tr.transformPoint({-iSight, iSight}); + _eyeSight[2] = tr.transformPoint({ 0.0f, sight}); + _eyeSight[3] = tr.transformPoint({ iSight, iSight}); + _eyeSight[4] = tr.transformPoint({ sight, 0.0f}); + + if (createNNet) { + _organism->createNeuralNet(); + } +} + +void +Agent::setPoints(const Stage& stage, bool passToOrg) +{ + if (!_organism->isOld()) { + return; + } + + // Calculate fitness + double dist = distanceTravelled(stage); + + if (passToOrg) { + _organism->_fitness = (dist / static_cast(max(_lifetime, (size_t)1))); + } +} + +void +Agent::setBehavior(const Stage& stage) +{ + _organism->getBehavior().at(0) = _body->GetPosition().x; + _organism->getBehavior().at(1) = _body->GetPosition().y; + _organism->getBehavior().at(2) = (distanceTravelled(stage) / static_cast(max(_lifetime, (size_t)1))); +} + +void +Agent::update(const Stage& stage, bool withNS) +{ + if (withNS) { + if (_organism->getLifetime() >= (_currentLifetime - 1)) { + if (!_isOld) { + _isOld = true; + } + } else if (_organism->getLifetime() < (_currentLifetime - 1)) { + if (_isOld) { + _isOld = false; + } + } + } + + ++_lifetime; + + if (_isOnTrap) { + return; + } + + float pX = _body->GetPosition().x; + float pY = _body->GetPosition().y; + float angle = _body->GetAngle() / IN_DEGREES; + + Transform tr, rot; + tr.translate(pX, pY); + tr.rotate(angle); + rot.rotate(angle); + + float sight = AGENT_SIGHT; + float iSight = AGENT_SIGHT * 0.7071f; + + _eyeSight[0] = tr.transformPoint({ -sight, 0.0f}); + _eyeSight[1] = tr.transformPoint({-iSight, iSight}); + _eyeSight[2] = tr.transformPoint({ 0.0f, sight}); + _eyeSight[3] = tr.transformPoint({ iSight, iSight}); + _eyeSight[4] = tr.transformPoint({ sight, 0.0f}); + + for (size_t i = 0; i < 5; ++i) { + RayCastCallback callback; + b2Vec2 b2EyeSight = { _eyeSight[i].x, _eyeSight[i].y }; + stage._world.RayCast(&callback, _body->GetPosition(), b2EyeSight); + _organism->_neuralNet->inputAt(i) = callback._fraction; + } + + _organism->_neuralNet->cycle(); + + Vector2f velocity + = rot.transformPoint({ 0.0f, static_cast(_organism->_neuralNet->outputAt(1)) * AGENT_SPEED }); + float rotation + = (((_organism->_neuralNet->outputAt(0) + _organism->_neuralNet->outputAt(2)) * 2.0f - 1.0f) / IN_RADIANS) * + AGENT_SPEED; + + _body->SetLinearVelocity({velocity.x, velocity.y}); + _body->SetAngularVelocity(rotation); +} + +void +Agent::drawOn(sf::RenderWindow& surface) +{ + float pX = _body->GetPosition().x; + float pY = _body->GetPosition().y; + + _shape.setPosition(pX, pY); + + VertexArray eyeSightLines(Lines); + + for (auto & i : _eyeSight) { + eyeSightLines.append({{pX, pY}, SIGHT_COLOR}); + eyeSightLines.append({ i, SIGHT_COLOR}); + } + + surface.draw(_shape); + surface.draw(eyeSightLines); +} + +void +Agent::incrementCheckPoint(Stage& stage) +{ + _currCheckPoint = _nextCheckPoint; + + if (_currCheckPoint == &stage._checkPoints.back()) { + _nextCheckPoint = &stage._checkPoints.front(); + } else { + ++_nextCheckPoint; + } + + if (_currCheckPoint == &stage._checkPoints.back()) { + ++_completedCircuits; + } +} + +double +Agent::distanceTravelled(const Stage& stage) +{ + double pCurr = _currCheckPoint->_value; + double pLoop = stage._checkPoints.back()._value; + + if (_currCheckPoint == &stage._checkPoints.back()) { + pCurr -= pLoop; + } + + double dNext = distanceToCheckpoint(*_nextCheckPoint); + double sNext = _nextCheckPoint->_segment; + + if (dNext > sNext) { + _distance = pCurr + (pLoop * _completedCircuits); + } else { + double pNext = _nextCheckPoint->_value; + double f1 = dNext / sNext; + double f2 = 1.0 - f1; + _distance = (pCurr * f1) + (pNext * f2) + (pLoop * _completedCircuits); + } + + if (_distance < 1.0) { + _distance = 1.0; + } + + return _distance; +} + +float +Agent::distanceToCheckpoint(const Stage::CheckPoint& cp) const +{ + b2Vec2 p = _body->GetPosition(); + b2Vec2 v = {cp._line[0].position.x, cp._line[0].position.y}; + b2Vec2 w = {cp._line[1].position.x, cp._line[1].position.y}; + + float l2 = (w - v).LengthSquared(); + + if (l2 == 0.0f) { + return dist(p, v); + } + + float t = dotP(p - v, w - v) / l2; + + if (t < 0.0f) { + return dist(p, v); + } else if (t > 1.0f) { + return dist(p, w); + } + + b2Vec2 projection = v + t * (w - v); + + return dist(p, projection); +} diff --git a/src/App.cpp b/src/App.cpp new file mode 100644 index 0000000..c97061e --- /dev/null +++ b/src/App.cpp @@ -0,0 +1,538 @@ +#include +#include +#include +#include +#include +#include + +using namespace hn; +using namespace sf; +using namespace tbb; +using namespace std; + +int +App::startup() +{ + ifstream paramsFile("parameters"); + ifstream popFile("current.population"); + + if (!paramsFile) { + return EXIT_FAILURE; + } + + cout << "Type hours to run sim.: "; + cin >> _secondsToRun; + _secondsToRun *= (60 * 60); + + bool popLoaded = false; + + if (popFile) { + popLoaded = true; + + LoadFile lFile(popFile); + lFile.loadPopulation(_population); + + _popPrms = _population.getPopulationPrms(); + _doNoveltySearch = _population.isNoveltyMetricSet(); + } + + auto until = numeric_limits::max(); + + while (!paramsFile.eof()) { + paramsFile.ignore(until, '<'); + + string command; + getline(paramsFile, command, '>'); + + if (!popLoaded && command == "POPULATION_PARAMETERS") { + paramsFile.ignore(until, '<'); + + while (paramsFile.peek() != '/') { + string subCommand; + getline(paramsFile, subCommand, '>'); + + if (subCommand == "populationSize") { + paramsFile >> _popPrms._popSize; + } else if (subCommand == "seed") { + paramsFile >> _popPrms._seed; + } else if (subCommand == "weightRange") { + paramsFile >> _popPrms._weightRange; + } else if (subCommand == "c1Disjoint") { + paramsFile >> _popPrms._c1Disjoint; + } else if (subCommand == "c3WeightDifference") { + paramsFile >> _popPrms._c3WeightDifference; + } else if (subCommand == "populationSize") { + paramsFile >> _popPrms._popSize; + } else if (subCommand == "initialDistanceThreshold") { + paramsFile >> _popPrms._initialDistanceThreshold; + } else if (subCommand == "distanceThresholdShift") { + paramsFile >> _popPrms._distanceThresholdShift; + } else if (subCommand == "sexualReproductionRate") { + paramsFile >> _popPrms._sexualReproductionRate; + } else if (subCommand == "weightMutationRate") { + paramsFile >> _popPrms._weightMutationRate; + } else if (subCommand == "weightDeviation") { + paramsFile >> _popPrms._weightDeviation; + } else if (subCommand == "interspeciesMatingRate") { + paramsFile >> _popPrms._interspeciesMatingRate; + } else if (subCommand == "geneDisablingRatio") { + paramsFile >> _popPrms._geneDisablingRatio; + } else if (subCommand == "linkMutationRate") { + paramsFile >> _popPrms._linkMutationRate; + } else if (subCommand == "nodeMutationRate") { + paramsFile >> _popPrms._nodeMutationRate; + } else if (subCommand == "targetSpeciesCount") { + paramsFile >> _popPrms._targetSpeciesCount; + } else if (subCommand == "eligibilityRatio") { + paramsFile >> _popPrms._eligibilityRatio; + } else if (subCommand == "minimumLifetime") { + paramsFile >> _popPrms._minimumLifetime; + } else if (subCommand == "replBeforeReorganization") { + paramsFile >> _popPrms._replBeforeReorganization; + } + + paramsFile.ignore(until, '<'); + } + } else if (command == "NEURAL_NET_PARAMETERS") { + paramsFile.ignore(until, '<'); + + while (paramsFile.peek() != '/') { + string subCommand; + getline(paramsFile, subCommand, '>'); + + if (subCommand == "testGridLevel") { + paramsFile >> _nnPrms._testGridLevel; + } else if (subCommand == "maxQuadTreeLevel") { + paramsFile >> _nnPrms._maxQuadTreeLevel; + } else if (subCommand == "minQuadTreeLevel") { + paramsFile >> _nnPrms._minQuadTreeLevel; + } else if (subCommand == "bandPruningThreshold") { + paramsFile >> _nnPrms._bandPruningThreshold; + } else if (subCommand == "varianceThreshold") { + paramsFile >> _nnPrms._varianceThreshold; + } else if (subCommand == "divisionThreshold") { + paramsFile >> _nnPrms._divisionThreshold; + } else if (subCommand == "searchIterations") { + paramsFile >> _nnPrms._iterations; + } + + paramsFile.ignore(until, '<'); + } + } else if (command == "NOVELTY_SEARCH_PARAMETERS") { + paramsFile.ignore(until, '<'); + + while (paramsFile.peek() != '/') { + string subCommand; + getline(paramsFile, subCommand, '>'); + + if (subCommand == "doNoveltySearch") { + paramsFile >> _doNoveltySearch; + } else if (subCommand == "noveltyThreshold") { + paramsFile >> _noveltyPrms._noveltyThreshold; + } else if (subCommand == "referenceOrganisms") { + paramsFile >> _noveltyPrms._referenceOrganisms; + } + + _noveltyPrms._characterizationSize = 3; + + paramsFile.ignore(until, '<'); + } + } else if (command == "STAGE_PARAMETERS") { + paramsFile.ignore(until, '<'); + + while (paramsFile.peek() != '/') { + string subCommand; + getline(paramsFile, subCommand, '>'); + + if (subCommand == "file") { + string fileName; + paramsFile >> fileName; + + if (_stage.load(fileName) == EXIT_FAILURE) { + return EXIT_FAILURE; + } + } else if (subCommand == "draw") { + paramsFile >> _draw; + } + + paramsFile.ignore(until, '<'); + } + } + } + + if (!_draw) { + _isVSyncEnabled = false; + _isPaused = false; + } + + _nnPrms._inputMap = { + { -1.0, -0.5 }, + { -0.7, 0.2 }, + { 0.0, 0.5 }, + { 0.7, 0.2 }, + { 1.0, -0.5 } + }; + + _nnPrms._outputMap = { + { -0.5, -0.5 }, + { 0.0, 0.0 }, + { 0.5, -0.5 } + }; + + if (!popLoaded) { + _population.create(_popPrms, _nnPrms); + } + + if (_doNoveltySearch && !_population.isNoveltyMetricSet()) { + _population.setNoveltyMetric(_noveltyPrms); + } else if (!_doNoveltySearch && _population.isNoveltyMetricSet()) { + _population.clearNoveltyMetric(); + } + + _agents.resize(_popPrms._popSize, Agent()); + + for (size_t i = 0; i < _agents.size(); ++i) { + _agents[i]._organism = &_population.getOrganism(i); + _agents[i].create(_stage); + } + + // Agent::_currentLifetime = (_stage._checkPoints.back()._value / 1.75); + + _champ.setFillColor(Color::Transparent); + _champ.setOutlineColor(CHAMP_COLOR); + _champ.setOutlineThickness(2); + _champ.setRadius(AGENT_RAD + 2); + _champ.setOrigin(AGENT_RAD + 2, AGENT_RAD + 2); + + _selected = _champ; + _selected.setOutlineColor(SELECTED_COLOR); + + // Print NoveltyMarks + if (_population.isNoveltyMetricSet()) { + for (auto& i : _population.getNoveltyMetric().getArchive()) { + _noveltyMarks.emplace_back(); + _noveltyMarks.back().setFillColor({ 255, 255, 0, 10 }); + _noveltyMarks.back().setRadius(AGENT_RAD + 2); + _noveltyMarks.back().setOrigin(AGENT_RAD + 2, AGENT_RAD + 2); + _noveltyMarks.back().setPosition(i[0], i[1]); + } + } + + // Prepare text + if (!_font.loadFromFile("C:/Windows/Fonts/font_1.ttf")) { + return EXIT_FAILURE; + } + + _updates.setFont(_font); + _champFitness.setFont(_font); + _avergFitness.setFont(_font); + + _updates.setCharacterSize(11); + _champFitness.setCharacterSize(11); + _avergFitness.setCharacterSize(11); + + _updates.setPosition(5, 5); + _champFitness.setPosition(5, 18); + _avergFitness.setPosition(5, 31); + + _updates.setColor(TEXT_COLOR); + _champFitness.setColor(TEXT_COLOR); + _avergFitness.setColor(TEXT_COLOR); + + return EXIT_SUCCESS; +} + +int +App::execute() +{ + if (!_draw) { + hn::Thread runner([&]() { + while (_runningWOGfx) { + update(); + } + }); + + cin.get(); + cin.get(); + + _runningWOGfx = false; + runner.join(); + + return EXIT_SUCCESS; + } + + _window.create(VIDEO_MODE, WIN_TITLE, WIN_STYLE, WIN_SETTINGS); + + _clock.restart(); + _globalClock.restart(); + + while (_window.isOpen()) { + if (!_isPaused) { + update(); + } + + Event event; + + while (_window.pollEvent(event)) { + if (event.type == Event::Closed) { + _cppnEx.shutdown(); + _window.close(); + } else if (event.type == Event::KeyPressed) { + if (event.key.code == Keyboard::Space) { + _isPaused = !_isPaused; + } else if (event.key.code == Keyboard::V) { + _isVSyncEnabled = !_isVSyncEnabled; + + if (_isVSyncEnabled) { + _window.setVerticalSyncEnabled(true); + } else { + _window.setVerticalSyncEnabled(false); + } + } else if (event.key.code == Keyboard::L) { + if (_selectedIdx != -1) { + if (_population.isOrganismLocked(_selectedIdx)) { + _population.unlockOrganism(_selectedIdx); + } else { + _population.lockOrganism(_selectedIdx); + } + } + } else if (event.key.code == Keyboard::F) { + if (_selectedIdx != -1) { + if (_population.isOrganismFrozen(_selectedIdx)) { + _population.unfreezeOrganism(_selectedIdx); + } else { + _population.freezeOrganism(_selectedIdx); + } + } + } else if (event.key.code == Keyboard::C) { + if (_selectedIdx != -1) { + auto& genome = _population.getOrganism(_selectedIdx).getGenome(); + _cppnEx.run(genome, &_nnPrms); + } + } + } else if (event.type == Event::MouseButtonPressed && event.mouseButton.button == Mouse::Left) { + float cX = static_cast(Mouse::getPosition(_window).x); + float cY = static_cast(Mouse::getPosition(_window).y); + + b2AABB aabb; + aabb.lowerBound = { cX - 0.5f, cY - 0.5f }; + aabb.upperBound = { cX + 0.5f, cY + 0.5f }; + + QueryCallback qCallback; + + _stage._world.QueryAABB(&qCallback, aabb); + + if (qCallback._fixture) { + if (qCallback._fixture->GetFilterData().groupIndex == -1) { + Agent* agent = static_cast(qCallback._fixture->GetUserData()); + size_t agentIdx = 0; + + while (&_agents[agentIdx] != agent) { + ++agentIdx; + } + + _selectedIdx = agentIdx; + + size_t oldOrg = 0; + double averageDist = 0.0; + + for (auto& i : _agents) { + if (i._organism->isOld()) { + ++oldOrg; + averageDist += i._distance; + } + } + + averageDist /= static_cast(oldOrg); + + cout.setf(ios::boolalpha); + cout << endl; + cout << "completedCircuits : " << _agents[agentIdx]._completedCircuits << endl; + cout << "lifetime : " << _agents[agentIdx]._lifetime << endl; + cout << "distance : " << _agents[agentIdx]._distance << endl; + cout << "isOnTrap : " << _agents[agentIdx]._isOnTrap << endl; + cout << "isOld : " << _agents[agentIdx]._organism->isOld() << endl; + // cout << "noveltyScore : " << _agents[agentIdx]._organism->getBehavior().getNoveltyScore() << endl; + // cout << "criteriaReached : " << _agents[agentIdx]._organism->getBehavior()._criteriaReached << endl; + cout << "averageDist : " << averageDist << endl; + cout << endl; + } else { + _selectedIdx = -1; + } + } else { + _selectedIdx = -1; + } + } + } + + draw(); + } + + return EXIT_SUCCESS; +} + +void +App::update() +{ + parallel_for(size_t(0), _agents.size(), size_t(1), [&](size_t i) { + if (!_agents[i]._organism->isBeingGenerated()) { + _agents[i].update(_stage, _doNoveltySearch); + } + }); + + _population.update([&]() { + if (!_doNoveltySearch) { + for (auto& i : _agents) { + i.setPoints(_stage, true); + } + } else { + for (auto& i : _agents) { + i.setBehavior(_stage); + } + } + }, [&]() { + _agents[_population.getLastReplacement()->getIndex()].recreate(_stage, true); + }); + + if (_doNoveltySearch) { + size_t maxCircuits = 0; + + for (auto& i : _agents) { + maxCircuits = max(maxCircuits, i._completedCircuits); + } + + if (maxCircuits != _completedCircuits) { + _completedCircuits = maxCircuits; + size_t newLifetime = (_stage._checkPoints.back()._value / 1.75) * (_completedCircuits + 1); + + _population.setMinimumLifetime(newLifetime); + Agent::_currentLifetime = newLifetime; + } + } + + for (auto& i : _stage._traps) { + TrapCallback trapCallback; + _stage._world.QueryAABB(&trapCallback, i); + } + + if (_doNoveltySearch) { + auto& archive = _population.getNoveltyMetric().getArchive(); + + if (archive.size() > _noveltyMarks.size()) { + for (size_t i = _noveltyMarks.size(); i < archive.size(); ++i) { + _noveltyMarks.emplace_back(); + _noveltyMarks.back().setFillColor({ 255, 255, 0, 10 }); + _noveltyMarks.back().setRadius(AGENT_RAD + 2); + _noveltyMarks.back().setOrigin(AGENT_RAD + 2, AGENT_RAD + 2); + _noveltyMarks.back().setPosition(archive[i][0], archive[i][1]); + } + } + } + + _stage.update(); + + if (_population.getUpdates() % 10000 == 0) { + ofstream popFile("current.population"); + SaveFile sFile(popFile); + sFile.savePopulation(_population, true); + + using chrono::system_clock; + system_clock::time_point today = system_clock::now(); + + time_t tt = system_clock::to_time_t(today); + cout << "> Saved on : " << ctime(&tt); + cout << "> Champ Fitness : " << _population.getChampion()._fitness << endl; + cout << "> Comp. Circuits : " << _completedCircuits << endl; + + if (_population.isNoveltyMetricSet()) { + cout << "> Archive Size : " << _population.getNoveltyMetric().getArchive().size() << endl << endl; + } else { + cout << endl; + } + } + + if (_globalClock.getElapsedTime().asSeconds() > _secondsToRun) { + _window.close(); + _runningWOGfx = false; + } +} + +void +App::draw() +{ + if (!_isVSyncEnabled) { + if (_clock.getElapsedTime().asSeconds() < 1.0f / 60.0f) { + return; + } + + _clock.restart(); + } + + _window.clear(BG_COLOR); + + _stage.drawOn(_window); + + for (auto& i : _noveltyMarks) { + _window.draw(i); + } + + for (size_t i = 0, end = _agents.size(); i < end; ++i) { + if (!_population.isOrganismBeingGenerated(i)) { + _agents[i].drawOn(_window); + } + } + + // Highlight champ + double champFtn = _population.getChampion()._fitness; + ssize_t champIdx = _population.getChampion().getIndex(); + double averageFitness = _population.getAverageFitness(); + + if (champIdx != -1) { + auto cPos = _agents[champIdx]._body->GetPosition(); + _champ.setPosition(cPos.x, cPos.y); + _window.draw(_champ); + } + + // Highlight selected + if (_selectedIdx != -1) { + auto sPos = _agents[_selectedIdx]._body->GetPosition(); + _selected.setPosition(sPos.x, sPos.y); + _window.draw(_selected); + } + + // Print text + _updates.setString ("Simulation updates : " + toString(_population.getUpdates())); + _champFitness.setString("Champ's fitness : " + toString(champFtn)); + _avergFitness.setString("Average fitness : " + toString(averageFitness)); + + _window.draw(_updates); + _window.draw(_champFitness); + _window.draw(_avergFitness); + + _window.display(); +} + +void +App::shutdown() +{ + _population.shutdown(true); + + ofstream popFile("current.population"); + SaveFile sFile(popFile); + sFile.savePopulation(_population); + + cout << "SHUTTING DOWN!" << endl; +} + +int +main() +{ + App app; + + if (app.startup() == EXIT_FAILURE || app.execute() == EXIT_FAILURE) { + return EXIT_FAILURE; + } else { + app.shutdown(); + return EXIT_SUCCESS; + } +} diff --git a/src/NR_Utils.cpp b/src/NR_Utils.cpp new file mode 100644 index 0000000..2768dad --- /dev/null +++ b/src/NR_Utils.cpp @@ -0,0 +1,88 @@ +#include +#include +#include + +using namespace std; + +ssize_t +pMod(ssize_t n, ssize_t d) +{ + return (n % d + d) % d; +} + +float +dist(const b2Vec2& a, const b2Vec2 b) +{ + auto x = a.x - b.x; + auto y = a.y - b.y; + + return sqrt(x * x + y * y); +} + +float +dotP(const b2Vec2& a, const b2Vec2 b) +{ + return a.x * b.x + a.y * b.y; +} + +float32 +RayCastCallback::ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction) +{ + if (fixture->GetFilterData().groupIndex == -1 || fixture->IsSensor()) { + return -1.0f; + } + + _fixture = fixture; + _fraction = 1.0f - fraction; + + return fraction; +} + +bool +QueryCallback::ReportFixture(b2Fixture* fixture) +{ + _fixture = fixture; + + return false; +} + +bool +TrapCallback::ReportFixture(b2Fixture* fixture) +{ + if (fixture->GetBody()->GetType() != b2_dynamicBody) { + return true; + } + + Agent& agent = *static_cast(fixture->GetUserData()); + agent._isOnTrap = true; + agent._body->SetLinearVelocity({ 0.0f, 0.0f }); + agent._body->SetAngularVelocity(0.0f); + + return true; +} + +void +ContactListener::BeginContact(b2Contact* contact) +{ + b2Fixture* fixA = contact->GetFixtureA(); + b2Fixture* fixB = contact->GetFixtureB(); + + if (!fixA->IsSensor() && !fixB->IsSensor()) { + return; + } + + Agent* agent = nullptr; + b2Fixture* check = nullptr; + + if (fixA->IsSensor()) { + check = fixA; + agent = static_cast(fixB->GetUserData()); + } else { + check = fixB; + agent = static_cast(fixA->GetUserData()); + } + + if (check->GetUserData() == agent->_nextCheckPoint) { + agent->incrementCheckPoint(*_stage); + } +} diff --git a/src/Stage.cpp b/src/Stage.cpp new file mode 100644 index 0000000..b9fa67a --- /dev/null +++ b/src/Stage.cpp @@ -0,0 +1,192 @@ +#include + +using namespace sf; +using namespace std; + +int +Stage::load(const std::string& fileName) +{ + ifstream stageFile(fileName); + + if (!stageFile) { + return EXIT_FAILURE; + } + + auto until = numeric_limits::max(); + + while (!stageFile.eof()) { + stageFile.ignore(until, '<'); + + string command; + getline(stageFile, command, '>'); + + if (command == "WALL") { + _walls.emplace_back(LinesStrip); + hn::Vector vertices; + + stageFile.ignore(until, '<'); + + while (stageFile.peek() != '/') { + Vector2f vtxPos; + + stageFile >> vtxPos.x; + stageFile.ignore(until, ','); + stageFile >> vtxPos.y; + stageFile.ignore(until, '<'); + + _walls.back().append({ vtxPos, WALL_COLOR }); + vertices.emplace_back(vtxPos.x, vtxPos.y); + } + + b2ChainShape shape; + shape.CreateChain(vertices.data(), vertices.size()); + + b2BodyDef bodyDef; + _wallBodies.emplace_back(_world.CreateBody(&bodyDef)); + _wallBodies.back()->CreateFixture(&shape, 0.0f); + } else if (command == "FACTORY") { + Vector2f factPos; + double factRot = 0.0; + + stageFile.ignore(until, '<'); + stageFile >> factPos.x; + stageFile.ignore(until, ','); + stageFile >> factPos.y; + stageFile.ignore(until, ','); + stageFile >> factRot; + stageFile.ignore(until, '<'); + + _factory.setPosition(factPos); + _factory.setRotation(factRot); + _factory.setOrigin(AGENT_RAD + 2, AGENT_RAD + 2); + _factory.setFillColor(Color::Black); + _factory.setOutlineColor(FACTORY_COLOR); + _factory.setOutlineThickness(2.0f); + } else if (command == "CHECKPOINT") { + stageFile.ignore(until, '<'); + + while (stageFile.peek() != '/') { + _checkPoints.emplace_back(); + + Vector2f vtx1Pos; + Vector2f vtx2Pos; + b2Vec2 shVx[2]; + + stageFile >> vtx1Pos.x; + shVx[0].x = vtx1Pos.x; + stageFile.ignore(until, ','); + stageFile >> vtx1Pos.y; + shVx[0].y = vtx1Pos.y; + stageFile.ignore(until, ','); + stageFile >> vtx2Pos.x; + shVx[1].x = vtx2Pos.x; + stageFile.ignore(until, ','); + stageFile >> vtx2Pos.y; + shVx[1].y = vtx2Pos.y; + stageFile.ignore(until, ','); + stageFile >> _checkPoints.back()._value; + stageFile.ignore(until, '<'); + + _checkPoints.back()._line.append({ vtx1Pos, CHECKPNT_COLOR }); + _checkPoints.back()._line.append({ vtx2Pos, CHECKPNT_COLOR }); + + _checkPoints.front().calculateSegment(_checkPoints.back()); + + b2ChainShape cpShape; + cpShape.CreateChain(shVx, 2); + + b2FixtureDef cpFixture; + cpFixture.shape = &cpShape; + cpFixture.isSensor = true; + + b2BodyDef cpDef; + _checkPoints.back()._body = _world.CreateBody(&cpDef); + _checkPoints.back()._body->CreateFixture(&cpFixture); + + if (_checkPoints.size() > 1) { + auto& prev = _checkPoints[_checkPoints.size() - 2]; + + _checkPoints.back().calculateSegment(prev); + } + } + + for (auto & i : _checkPoints) { + i._body->GetFixtureList()->SetUserData(&i); + } + } else if (command == "TRAP") { + stageFile.ignore(until, '<'); + + while (stageFile.peek() != '/') { + sf::Vector2f v1; + sf::Vector2f v2; + + stageFile >> v1.x; + stageFile.ignore(until, ','); + stageFile >> v1.y; + stageFile.ignore(until, ','); + stageFile >> v2.x; + stageFile.ignore(until, ','); + stageFile >> v2.y; + stageFile.ignore(until, '<'); + + _trapRects.emplace_back(sf::Vector2f(v2.x - v1.x, v2.y - v1.y)); + _trapRects.back().setPosition(v1); + _trapRects.back().setFillColor({ 128, 32, 32, 128 }); + + _traps.emplace_back(); + _traps.back().lowerBound.Set(v1.x, v1.y); + _traps.back().upperBound.Set(v2.x, v2.y); + } + } + } + + _world.SetContactListener(&_listener); + _listener._stage = this; + + return EXIT_SUCCESS; +} + +void +Stage::update() +{ + _world.Step(_TIME_STEP, _VEL_ITERS, _POS_ITERS); +} + +void +Stage::drawOn(RenderWindow& surface) const +{ + for (auto& i : _trapRects) { + surface.draw(i); + } + + for (auto& i : _walls) { + surface.draw(i); + } + + surface.draw(_factory); + + for (auto& i : _checkPoints) { + surface.draw(i._line); + } +} + +void +Stage::CheckPoint::calculateSegment(const CheckPoint& prev) +{ + float32 a0X = prev._line[0].position.x; + float32 a0Y = prev._line[0].position.y; + float32 a1X = prev._line[1].position.x; + float32 a1Y = prev._line[1].position.y; + + float32 b0X = _line[0].position.x; + float32 b0Y = _line[0].position.y; + float32 b1X = _line[1].position.x; + float32 b1Y = _line[1].position.y; + + float32 dA0B0 = dist( { a0X, a0Y }, { b0X, b0Y }); + float32 dA0B1 = dist( { a0X, a0Y }, { b1X, b1Y }); + float32 dA1B0 = dist( { a1X, a1Y }, { b0X, b0Y }); + float32 dA1B1 = dist( { a1X, a1Y }, { b1X, b1Y }); + + _segment = min(min(dA0B0, dA0B1), min(dA1B0, dA1B1)); +} -- cgit v1.2.1