diff --git a/CMakeLists.txt b/CMakeLists.txt index a53b860..c36571b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,8 +86,8 @@ set(EXECUTABLE_SOURCES ${EXECUTABLE_SOURCE_DIR}/render-passes.hpp ${EXECUTABLE_SOURCE_DIR}/settings.cpp ${EXECUTABLE_SOURCE_DIR}/settings.hpp - ${EXECUTABLE_SOURCE_DIR}/terrain.cpp - ${EXECUTABLE_SOURCE_DIR}/terrain.hpp + ${EXECUTABLE_SOURCE_DIR}/game/terrain.cpp + ${EXECUTABLE_SOURCE_DIR}/game/terrain.hpp ${EXECUTABLE_SOURCE_DIR}/controls.hpp ${EXECUTABLE_SOURCE_DIR}/input.cpp ${EXECUTABLE_SOURCE_DIR}/input.hpp @@ -109,10 +109,14 @@ set(EXECUTABLE_SOURCES ${EXECUTABLE_SOURCE_DIR}/ui/tween.hpp ${EXECUTABLE_SOURCE_DIR}/ui/tween.cpp ${EXECUTABLE_SOURCE_DIR}/render-passes.cpp - ${EXECUTABLE_SOURCE_DIR}/ant.hpp - ${EXECUTABLE_SOURCE_DIR}/ant.cpp - ${EXECUTABLE_SOURCE_DIR}/nest.hpp - ${EXECUTABLE_SOURCE_DIR}/nest.cpp + ${EXECUTABLE_SOURCE_DIR}/game/ant.hpp + ${EXECUTABLE_SOURCE_DIR}/game/ant.cpp + ${EXECUTABLE_SOURCE_DIR}/game/agent.hpp + ${EXECUTABLE_SOURCE_DIR}/game/agent.cpp + ${EXECUTABLE_SOURCE_DIR}/game/nest.hpp + ${EXECUTABLE_SOURCE_DIR}/game/nest.cpp + ${EXECUTABLE_SOURCE_DIR}/game/navmesh.hpp + ${EXECUTABLE_SOURCE_DIR}/game/navmesh.cpp ${EXECUTABLE_SOURCE_DIR}/debug.hpp ${EXECUTABLE_SOURCE_DIR}/debug.cpp ${EXECUTABLE_SOURCE_DIR}/camera-controller.hpp diff --git a/data b/data index 3f2b504..37456bb 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 3f2b504970e90fd476a682f4754d276bf162729c +Subproject commit 37456bb0933b6eed8444f2d6f1ce0819bac00b15 diff --git a/lib/emergent b/lib/emergent index 94f7940..341a6cd 160000 --- a/lib/emergent +++ b/lib/emergent @@ -1 +1 @@ -Subproject commit 94f7940aa2ab6375c7672354f3a731a86542f51c +Subproject commit 341a6cd0b704379cd38c9382f9ea0ef4b33a382a diff --git a/src/ant.cpp b/src/ant.cpp deleted file mode 100644 index 3929efd..0000000 --- a/src/ant.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 . - */ - -#include "ant.hpp" - diff --git a/src/ant.hpp b/src/ant.hpp deleted file mode 100644 index 11ca645..0000000 --- a/src/ant.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 . - */ - -#ifndef ANT_HPP -#define ANT_HPP - -#include - -#include -using namespace Emergent; - -class Gait; - -class Ant -{ -public: - /** - * Named constants corresponding to leg indices. - * - * \_/ - * L1 --| |-- R1 - * L2 --| |-- R2 - * L3 --|_|-- R3 - */ - enum class LegIndex - { - L1, - L2, - L3, - R1, - R2, - R3 - }; - -private: - Transform transform; - ModelInstance modelInstance; - Pose* skeletonPose; -}; - -class Colony -{ -public: - Ant* spawn(); - - const Model* getAntModel() const; - Model* getAntModel(); - - const Skeleton* getAntSkeleton() const; - Skeleton* getSkeleton(); - -private: - // Rendering - Model* antModel; - Skeleton* antSkeleton; - - // Locomotion - float walkSpeed; - float turnSpeed; - Gait* tripodGait; - Gait* rippleGait; - Gait* slowWaveGait; - - - std::vector ants; -}; - -#endif // ANT_HPP \ No newline at end of file diff --git a/src/application.cpp b/src/application.cpp index aaf4614..65e7cfd 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -340,6 +340,11 @@ Application::Application(int argc, char* argv[]): gameControlProfile->registerControl("camera-zoom-out", &cameraZoomOut); gameControlProfile->registerControl("camera-toggle-nest-view", &cameraToggleNestView); gameControlProfile->registerControl("camera-toggle-overhead-view", &cameraToggleOverheadView); + gameControlProfile->registerControl("walk-forward", &walkForward); + gameControlProfile->registerControl("walk-back", &walkBack); + gameControlProfile->registerControl("turn-left", &turnLeft); + gameControlProfile->registerControl("turn-right", &turnRight); + cameraMoveForward.bindKey(keyboard, SDL_SCANCODE_W); cameraMoveBack.bindKey(keyboard, SDL_SCANCODE_S); cameraMoveLeft.bindKey(keyboard, SDL_SCANCODE_A); @@ -350,6 +355,10 @@ Application::Application(int argc, char* argv[]): cameraZoomOut.bindKey(keyboard, SDL_SCANCODE_MINUS); cameraToggleOverheadView.bindKey(keyboard, SDL_SCANCODE_R); cameraToggleNestView.bindKey(keyboard, SDL_SCANCODE_F); + walkForward.bindKey(keyboard, SDL_SCANCODE_UP); + walkBack.bindKey(keyboard, SDL_SCANCODE_DOWN); + turnLeft.bindKey(keyboard, SDL_SCANCODE_LEFT); + turnRight.bindKey(keyboard, SDL_SCANCODE_RIGHT); cameraOverheadView = true; cameraNestView = false; @@ -364,6 +373,7 @@ Application::Application(int argc, char* argv[]): SDL_GL_SwapWindow(window); // Setup loaders + textureLoader = new TextureLoader(); materialLoader = new MaterialLoader(); modelLoader = new ModelLoader(); modelLoader->setMaterialLoader(materialLoader); diff --git a/src/application.hpp b/src/application.hpp index c55be67..c15580e 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -24,7 +24,7 @@ using namespace Emergent; #include "mesh.hpp" -#include "terrain.hpp" +#include "game/terrain.hpp" #include "input.hpp" #include "controls.hpp" #include "settings.hpp" @@ -102,7 +102,9 @@ public: Camera camera; Scene scene; BillboardBatch particleBatch; + Arcball arcball; + TextureLoader* textureLoader; MaterialLoader* materialLoader; ModelLoader* modelLoader; @@ -158,6 +160,11 @@ public: bool cameraOverheadView; bool cameraNestView; + Control walkForward; + Control walkBack; + Control turnLeft; + Control turnRight; + // Misc Timer frameTimer; @@ -167,8 +174,8 @@ public: float fontSizePT; float fontSizePX; Font* menuFont; - Texture splashTexture; - Texture titleTexture; + Texture* splashTexture; + Texture* titleTexture; Vector4 selectedColor; Vector4 deselectedColor; UIContainer* uiRootElement; diff --git a/src/game/agent.cpp b/src/game/agent.cpp new file mode 100644 index 0000000..4f7345f --- /dev/null +++ b/src/game/agent.cpp @@ -0,0 +1,157 @@ +/* + * 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 . + */ + +#include "agent.hpp" + +Agent::Agent(): + navmeshTriangle(nullptr), + barycentricPosition(0.0f), + position(0.0f), + forward(0, 0, -1), + up(0, 1, 0), + right(1, 0, 0), + rotation(1, 0, 0, 0), + wanderDirection(0, 0, -1) +{} + +void Agent::applyForce(const Vector3& force) +{ + acceleration += force; +} + +void Agent::updateVelocity() +{ + // Limit acceleration + acceleration = limit(acceleration / mass, maxAcceleration); + + // Add acceleration to velocity and limit + velocity = limit(velocity + acceleration, maxSpeed); + + // Reset acceleration to zero + acceleration = Vector3(0.0f); +} + +Vector3 Agent::wander(float dt) +{ + // Calculate center of wander circle + Vector3 wanderCircleCenter = forward * wanderCircleDistance; + + // Calculate wander force + Vector3 force = wanderCircleCenter + wanderDirection * wanderCircleRadius; + + // Rotate wander direction by a random displacement angle + float displacement = frand(-wanderRate * 0.5f, wanderRate * 0.5f); + wanderDirection = glm::normalize(glm::angleAxis(displacement, up) * wanderDirection); + + return force; +} + +Vector3 Agent::seek(const Vector3& target) const +{ + Vector3 desiredVelocity = glm::normalize(position - target) * maxSpeed; + return desiredVelocity - velocity; +} + +Vector3 Agent::flee(const Vector3& target) const +{ + Vector3 desiredVelocity = glm::normalize(target - position) * maxSpeed; + return desiredVelocity - velocity; +} + +Vector3 Agent::containment(const Vector3& probe) const +{ + std::vector traversal; + Navmesh::traverse(navmeshTriangle, barycentricPosition, probe, &traversal); + + if (traversal.empty()) + { + return Vector3(0.0f); + } + + const Navmesh::Step& step = traversal.back(); + + // If not on edge or on connected edge + if (step.edge == nullptr || step.edge->symmetric != nullptr) + { + return Vector3(0.0f); + } + + // Calculate edge normal + const Vector3& a = step.edge->vertex->position; + const Vector3& b = step.edge->next->vertex->position; + Vector3 ab = glm::normalize(b - a); + Vector3 edgeNormal = glm::cross(up, ab); + + // Calculate reflection vector of forward vector and edge normal + Vector3 force = glm::reflect(forward, edgeNormal); + + return force; +} + +void Agent::setPosition(Navmesh::Triangle* triangle, const Vector3& position) +{ + // Update navmesh triangle and position + navmeshTriangle = triangle; + barycentricPosition = position; + + // Convert navmesh-space barycentric position to world-space cartesian position + const Vector3& a = triangle->edge->vertex->position; + const Vector3& b = triangle->edge->next->vertex->position; + const Vector3& c = triangle->edge->previous->vertex->position; + this->position = cartesian(position, a, b, c); +} + +void Agent::setOrientation(const Vector3& newForward, const Vector3& newUp) +{ + // Calculate alignment quaternion + Quaternion alignment = glm::rotation(up, newUp); + + // Rebuild vector basis + forward = newForward; + right = glm::normalize(glm::cross(newUp, forward)); + up = glm::cross(forward, right); + + // Calculate rotation quaternion from vector basis + rotation = glm::normalize(glm::quat_cast(Matrix3(right, up, forward))); + + // Align wander direction + wanderDirection = glm::normalize(project_on_plane(alignment * wanderDirection, Vector3(0.0f), up)); +} + +void Agent::setWanderCircleDistance(float distance) +{ + wanderCircleDistance = distance; +} + +void Agent::setWanderCircleRadius(float radius) +{ + wanderCircleRadius = radius; +} + +void Agent::setWanderRate(float angle) +{ + wanderRate = angle; +} + +/** EXAMPLE USAGE +Vector3 wanderForce = wander(dt) * wanderWeight; +Vector3 fleeForce = flee(mouse) * fleeWeight; +Vector3 steerForce = wanderForce + fleeForce; +steer(steerForce); +**/ diff --git a/src/game/agent.hpp b/src/game/agent.hpp new file mode 100644 index 0000000..0e3bf6f --- /dev/null +++ b/src/game/agent.hpp @@ -0,0 +1,193 @@ +/* + * 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 . + */ + +#ifndef AGENT_HPP +#define AGENT_HPP + +#include "navmesh.hpp" + +#include + +#include +using namespace Emergent; + +class Obstacle; + + +/*************88 + +Ant is an agent. + +Ant combines steering behaviors with different weights. + +I.E. + +seek pheromones * 0.5 +separation * 0.1 +alignment * 0.1 +cohesion * 0.1 +followWall * 0.2 + +*/ + +/** + * An agent which navigates on a navmesh. + */ +class Agent +{ +public: + Agent(); + + /** + * Adds a force to the agent's acceleration vector. + * + * @param force Acceleration force + */ + void applyForce(const Vector3& force); + + /** + * Calculates velocity based on current acceleration vector, then resets acceleration to zero. + */ + void updateVelocity(); + + /** + * Calculates steering force for the wander behavior. + */ + Vector3 wander(float dt); + + /** + * Calculates steering force for the seek behavior. + */ + Vector3 seek(const Vector3& target) const; + + /** + * Calculates steering force for the flee behavior. + */ + Vector3 flee(const Vector3& target) const; + + Vector3 containment(const Vector3& probe) const; + + void setMaxSpeed(float speed); + void setMaxAcceleration(float acceleration); + void setMass(float mass); + void setWanderCircleDistance(float distance); + void setWanderCircleRadius(float radius); + void setWanderRate(float angle); + + /** + * Sets the position of the agent on a navmesh. + * + * @param triangle Navmesh triangle on which the agent resides + * @param position Barycentric position on the specified triangle + */ + void setPosition(Navmesh::Triangle* triangle, const Vector3& position); + + /** + * Sets the orientation of the agent. This effectively updates the agent's vector basis and rotation quaternion. + * + * @param forward Normalized forward vector + * @param up Normalized up vector + */ + void setOrientation(const Vector3& newForward, const Vector3& newUp); + + /* + Vector3 followWall(); + // or + Vector3 followEdge(); + Vector3 avoidObstacle(const Obstacle* obstacle); + Vector3 separation(const std::vector* neighbors); + Vector3 alignment(const std::vector* neighbors); + Vector3 cohesion(const std::vector* neighbors); + */ + + const Navmesh::Triangle* getNavmeshTriangle() const; + Navmesh::Triangle* getNavmeshTriangle(); + const Vector3& getBarycentricPosition() const; + const Vector3& getPosition() const; + const Vector3& getForward() const; + const Vector3& getUp() const; + const Vector3& getRight() const; + const Quaternion& getRotation() const; + +private: + Navmesh::Triangle* navmeshTriangle; + Vector3 barycentricPosition; + Vector3 position; + Vector3 forward; + Vector3 up; + Vector3 right; + Quaternion rotation; + + // Limits + float maxSpeed; + float maxAcceleration; + + // Steering forces + float mass; + Vector3 acceleration; + Vector3 velocity; + + // Wander variables + float wanderCircleDistance; + float wanderCircleRadius; + float wanderRate; + Vector3 wanderDirection; +}; + +inline const Navmesh::Triangle* Agent::getNavmeshTriangle() const +{ + return navmeshTriangle; +} + +inline Navmesh::Triangle* Agent::getNavmeshTriangle() +{ + return navmeshTriangle; +} + +inline const Vector3& Agent::getBarycentricPosition() const +{ + return barycentricPosition; +} + +inline const Vector3& Agent::getPosition() const +{ + return position; +} + +inline const Vector3& Agent::getForward() const +{ + return forward; +} + +inline const Vector3& Agent::getUp() const +{ + return up; +} + +inline const Vector3& Agent::getRight() const +{ + return right; +} + +inline const Quaternion& Agent::getRotation() const +{ + return rotation; +} + +#endif // AGENT_HPP \ No newline at end of file diff --git a/src/game/ant.cpp b/src/game/ant.cpp new file mode 100644 index 0000000..eda7f7b --- /dev/null +++ b/src/game/ant.cpp @@ -0,0 +1,138 @@ +/* + * 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 . + */ + +#include "ant.hpp" + +Ant::Ant(Colony* colony): + colony(colony), + state(Ant::State::IDLE), + transform(Transform::getIdentity()), + skeletonPose(nullptr) +{ + modelInstance.setModel(colony->getAntModel()); +} + +void Ant::move(const Vector3& velocity) +{ + std::vector 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) +{ + if (state == Ant::State::WANDER) + { + setWanderCircleDistance(3.0f); + setWanderCircleRadius(0.25f); + setWanderRate(glm::radians(90.0f)); + + // Calculate wander force + Vector3 wanderForce = wander(dt); + + // Setup containment probes + float probeLateralOffset = 0.35f; + Vector3 forwardProbe = getForward() * 0.5f; + Vector3 leftProbe = getForward() * 0.1f - getRight() * probeLateralOffset; + Vector3 rightProbe = getForward() * 0.1f + getRight() * probeLateralOffset; + + // Calculate containment force + Vector3 containmentForce = containment(forwardProbe) + + containment(leftProbe) + + containment(rightProbe); + + // Calculate velocity + Vector3 velocity(0.0f); + velocity += wanderForce; + velocity += containmentForce; + velocity = limit(velocity, 0.025f); + + setOrientation(glm::normalize(velocity), getUp()); + + // Move ant + move(velocity); + } + + // Update transform + transform.translation = getPosition(); + transform.rotation = getRotation(); + + // Update model instance + modelInstance.setTransform(transform); +} + +void Ant::setState(Ant::State state) +{ + this->state = state; +} + +Colony::Colony(): + antModel(nullptr) +{} + +Ant* Colony::spawn(Navmesh* navmesh, Navmesh::Triangle* triangle, const Vector3& position) +{ + // Allocate ant + Ant* ant = new Ant(this); + + // Position it on the navmesh + ant->setPosition(triangle, position); + + // Add ant to the colony + ants.push_back(ant); + + return ant; +} + +void Colony::update(float dt) +{ + for (Ant* ant: ants) + { + ant->update(dt); + } +} + +void Colony::setAntModel(Model* model) +{ + this->antModel = model; +} diff --git a/src/game/ant.hpp b/src/game/ant.hpp new file mode 100644 index 0000000..eef0c47 --- /dev/null +++ b/src/game/ant.hpp @@ -0,0 +1,165 @@ +/* + * 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 . + */ + +#ifndef ANT_HPP +#define ANT_HPP + +#include +#include "navmesh.hpp" +#include "agent.hpp" + +#include +using namespace Emergent; + +class Colony; +class Gait; + +/** + * An individual ant which belongs to a colony. + */ +class Ant: public Agent +{ +public: + /** + * Named constants corresponding to leg indices. + * + * \_/ + * L1 --| |-- R1 + * L2 --| |-- R2 + * L3 --|_|-- R3 + */ + enum class LegIndex + { + L1, + L2, + L3, + R1, + R2, + R3 + }; + + enum class State + { + IDLE, + WANDER + }; + + /** + * Creates an instance of Ant. + */ + Ant(Colony* colony); + + void move(const Vector3& velocity); + + void turn(float angle); + + void update(float dt); + + void setState(Ant::State state); + + const Colony* getColony() const; + Colony* getColony(); + const Transform& getTransform() const; + + const ModelInstance* getModelInstance() const; + ModelInstance* getModelInstance(); + +private: + /** + * Calculates the surface normal averaged between the surface normals at each of the ant's grounded feet. + */ + Vector3 calculateAverageSurfaceNormal() const; + + void updateTransform(); + + Colony* colony; + Ant::State state; + + Transform transform; + ModelInstance modelInstance; + Pose* skeletonPose; +}; + +inline const Colony* Ant::getColony() const +{ + return colony; +} + +inline Colony* Ant::getColony() +{ + return colony; +} + +inline const Transform& Ant::getTransform() const +{ + return transform; +} + +inline const ModelInstance* Ant::getModelInstance() const +{ + return &modelInstance; +} + +inline ModelInstance* Ant::getModelInstance() +{ + return &modelInstance; +} + +/** + * A colony of ants. + */ +class Colony +{ +public: + Colony(); + + Ant* spawn(Navmesh* navmesh, Navmesh::Triangle* triangle, const Vector3& position); + + void update(float dt); + + void setAntModel(Model* model); + const Model* getAntModel() const; + Model* getAntModel(); + +private: + // Rendering + Model* antModel; + + // Locomotion + float walkSpeed; + float turnSpeed; + Gait* tripodGait; + Gait* rippleGait; + Gait* slowWaveGait; + + + std::vector ants; +}; + +inline const Model* Colony::getAntModel() const +{ + return antModel; +} + +inline Model* Colony::getAntModel() +{ + return antModel; +} + +#endif // ANT_HPP \ No newline at end of file diff --git a/src/game/navmesh.cpp b/src/game/navmesh.cpp new file mode 100644 index 0000000..c319bb8 --- /dev/null +++ b/src/game/navmesh.cpp @@ -0,0 +1,546 @@ +/* + * 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 . + */ + +#include "navmesh.hpp" +#include +#include +#include +#include + +Navmesh::Navmesh() +{} + +Navmesh::~Navmesh() +{ + destroy(); +} + +bool Navmesh::create(const std::vector& vertices, const std::vector& indices) +{ + destroy(); + + if (indices.size() % 3 != 0) + { + std::cerr << "Navmesh::create(): index count is non multiple of 3\n"; + return false; + } + + // Copy vertices + this->vertices.resize(vertices.size()); + for (std::size_t i = 0; i < vertices.size(); ++i) + { + Navmesh::Vertex* vertex = new Navmesh::Vertex(); + vertex->edge = nullptr; + vertex->position = vertices[i]; + vertex->flags = 0; + vertex->index = i; + + this->vertices[i] = vertex; + } + + // Allocate triangles + triangles.resize(indices.size() / 3, nullptr); + std::size_t currentTriangle = 0; + + // Load triangles + std::map, Navmesh::Edge*> edgeMap; + for (std::size_t i = 0; i < indices.size(); i += 3) + { + std::size_t a = indices[i]; + std::size_t b = indices[i + 1]; + std::size_t c = indices[i + 2]; + + if (a >= vertices.size() || b >= vertices.size() || c >= vertices.size()) + { + std::cerr << "Navmesh::create(): Mesh contains invalid index.\n"; + destroy(); + return false; + } + + // Allocate three edges and a triangle + Navmesh::Edge* ab = new Navmesh::Edge(); + Navmesh::Edge* bc = new Navmesh::Edge(); + Navmesh::Edge* ca = new Navmesh::Edge(); + Navmesh::Triangle* triangle = new Navmesh::Triangle(); + + // Zero triangle flags + triangle->flags = 0; + + // Zero edge flags + ab->flags = 0; + bc->flags = 0; + ca->flags = 0; + + // Set triangle start edge + triangle->edge = ab; + + // For each edge in this triangle + std::size_t triangleIndices[] = {a, b, c}; + Navmesh::Edge* triangleEdges[] = {ab, bc, ca}; + for (std::size_t j = 0; j < 3; ++j) + { + // Set edge properties + Navmesh::Edge* edge = triangleEdges[j]; + edge->triangle = triangle; + edge->vertex = this->vertices[triangleIndices[j]]; + edge->previous = triangleEdges[(j + 2) % 3]; + edge->next = triangleEdges[(j + 1) % 3]; + edge->symmetric = nullptr; + + // Point vertex to this edge + edge->vertex->edge = edge; + + // Check for symmetry + std::pair symmetricPair(triangleIndices[(j + 1) % 3], triangleIndices[j]); + std::map, Navmesh::Edge*>::iterator it = edgeMap.find(symmetricPair); + if (it == edgeMap.end()) + { + // No symmetric edge found, insert this edge into the map + std::pair pair(triangleIndices[j], triangleIndices[(j + 1) % 3]); + edgeMap[pair] = edge; + } + else + { + // Symmetric edge found, connect + edge->symmetric = it->second; + it->second->symmetric = edge; + } + } + + // Set edge indices and add edges to the edge list + ab->index = edges.size(); + edges.push_back(ab); + bc->index = edges.size(); + edges.push_back(bc); + ca->index = edges.size(); + edges.push_back(ca); + + // Set triangle index and add triangle to the triangle list + triangle->index = currentTriangle; + triangles[currentTriangle++] = triangle; + } + + calculateNormals(); + + return true; +} + +void Navmesh::destroy() +{ + for (std::size_t i = 0; i < vertices.size(); ++i) + delete vertices[i]; + for (std::size_t i = 0; i < edges.size(); ++i) + delete edges[i]; + for (std::size_t i = 0; i < triangles.size(); ++i) + delete triangles[i]; + + vertices.clear(); + edges.clear(); + triangles.clear(); +} + +bool Navmesh::loadOBJ(const std::string& filename) +{ + // Open OBJ file + std::ifstream file(filename.c_str()); + if (!file.is_open()) + { + std::cerr << "Navmesh::loadOBJ(): Failed to open Wavefront OBJ file \"" << filename << "\"" << std::endl; + return false; + } + + // Read OBJ file from file stream + if (!readOBJ(&file, filename)) + { + std::cerr << "Navmesh::loadOBJ(): Failed to read Wavefront OBJ file \"" << filename << "\"" << std::endl; + file.close(); + return false; + } + + // Close OBJ file + file.close(); + + return true; +} + +void Navmesh::traverse(Navmesh::Triangle* startTriangle, const Vector3& startPosition, const Vector3& startVelocity, std::vector* traversal) +{ + // Form initial traversal step + Navmesh::Step step; + step.triangle = startTriangle; + step.start = normalize_barycentric(startPosition); + step.end = step.start; + step.edge = nullptr; + + // Determine the maximum distance of the traversal + float maxDistance = glm::length(startVelocity); + + // Set initial velocity + Vector3 velocity = startVelocity; + + // Traverse navmesh + float distance = 0.0f; + while (distance < maxDistance) + { + // Grab triangle coordinates + const Vector3& a = step.triangle->edge->vertex->position; + const Vector3& b = step.triangle->edge->next->vertex->position; + const Vector3& c = step.triangle->edge->previous->vertex->position; + + // Calculate target position + Vector3 cartesianStart = cartesian(step.start, a, b, c); + Vector3 target = cartesianStart + velocity; + + // Find closest point on triangle to target position + closestPointOnTriangle(target, step.triangle, &step.end, &step.edge); + step.end = normalize_barycentric(step.end); + + // Add step to the traversal + traversal->push_back(step); + + // Determine distance traveled by the step + Vector3 cartesianEnd = cartesian(step.end, a, b, c); + distance += glm::length(cartesianEnd - cartesianStart); + + // Check for no movement + if (cartesianEnd == cartesianStart) + { + /* + std::cout << "the same!\n"; + + if (step.edge == nullptr) + std::cout << "\tand no edge\n"; + else if (step.edge->symmetric == nullptr) + { + std::cout << "\tand disconnected\n"; + + //step.edge = step.edge->previous; + } + //break; + */ + + } + + // Check if traversal is complete or edge is disconnected + if (step.edge == nullptr || step.edge->symmetric == nullptr) + { + break; + } + + // Recalculate velocity + Quaternion rotation = glm::rotation(step.triangle->normal, step.edge->symmetric->triangle->normal); + velocity = glm::normalize(rotation * velocity) * (maxDistance - distance); + + // Move to the next triangle + step.triangle = step.edge->symmetric->triangle; + + // Calculate barycentric starting coordinates of the next step + step.start = normalize_barycentric(barycentric(cartesianEnd, + step.triangle->edge->vertex->position, + step.triangle->edge->next->vertex->position, + step.triangle->edge->previous->vertex->position)); + step.end = step.start; + step.edge = nullptr; + } + + /* + // Add triangle to visited list + visited->push_back(triangle); + + // Grab triangle coordinates + const glm::vec3& a = triangle->edge->vertex->position; + const glm::vec3& b = triangle->edge->next->vertex->position; + const glm::vec3& c = triangle->edge->previous->vertex->position; + + // Project target onto triangle + glm::vec3 closestPoint; + int edgeIndex = -1; + WingedEdge::Edge* closestEdge = nullptr; + project_on_triangle(target, a, b, c, &closestPoint, &edgeIndex); + *end = closestPoint; + + // Determine if projected target is on an edge + switch (edgeIndex) + { + case -1: + // Projected target inside triangle + return; + case 0: + closestEdge = triangle->edge; + break; + case 1: + closestEdge = triangle->edge->next; + break; + case 2: + closestEdge = triangle->edge->previous; + break; + } + + // If edge is not loose, repeat with connected triangle + if (closestEdge->symmetric != nullptr) + { + for (std::size_t i = 0; i < visited->size() - 1; ++i) + { + if ((*visited)[i] == closestEdge->symmetric->triangle) + return; + } + + move(mesh, closestEdge->symmetric->triangle, closestPoint, target, visited, end); + } + */ +} + +/* + if (steerCCW.isTriggered()) + { + glm::quat rotation = glm::angleAxis(0.1f, navi.triangle->normal); + navi_forward = glm::normalize(rotation * navi_forward); + } + if (steerCW.isTriggered()) + { + glm::quat rotation = glm::angleAxis(-0.1f, navi.triangle->normal); + navi_forward = glm::normalize(rotation * navi_forward); + } + + if (navigate.isTriggered()) + { + + Mesh::Triangle* triangle = navi.triangle; + glm::vec3 start = navi.position; + glm::vec3 target = start + navi_forward * 0.02f; + std::vector visited; + glm::vec3 end; + + move(&sceneManager.getTerrain()->mesh, triangle, start, target, &visited, &end); + + Mesh::Triangle* end_triangle = visited[visited.size() - 1]; + const glm::vec3& a = end_triangle->edge->vertex->position; + const glm::vec3& b = end_triangle->edge->next->vertex->position; + const glm::vec3& c = end_triangle->edge->previous->vertex->position; + glm::vec3 p = (a + b + c) / 3.0f; + const glm::vec3& n = end_triangle->normal; + glm::vec3 projected_start = project_on_plane(start, p, n); + + // Calculate difference between positions + glm::vec3 difference = end - projected_start; + if (glm::dot(difference, difference) != 0.0f) + { + if (end_triangle != triangle) + { + glm::quat alignment = glm::rotation(triangle->normal, end_triangle->normal); + navi_forward = glm::normalize(alignment * navi_forward); + } + } + + navi.position = end; + navi.triangle = visited[visited.size() - 1]; + } +*/ + +void Navmesh::calculateNormals() +{ + for (std::size_t i = 0; i < triangles.size(); ++i) + { + Navmesh::Triangle* triangle = triangles[i]; + + // Calculate surface normal + const Vector3& a = triangle->edge->vertex->position; + const Vector3& b = triangle->edge->next->vertex->position; + const Vector3& c = triangle->edge->previous->vertex->position; + Vector3 ba = b - a; + Vector3 ca = c - a; + triangle->normal = glm::normalize(glm::cross(ba, ca)); + } +} + +bool Navmesh::readOBJ(std::istream* stream, const std::string& filename) +{ + std::string line; + std::vector vertices; + std::vector indices; + + while (stream->good() && std::getline(*stream, line)) + { + // Tokenize line + std::vector tokens; + std::string token; + std::istringstream linestream(line); + while (linestream >> token) + tokens.push_back(token); + + // Skip empty lines and comments + if (tokens.empty() || tokens[0][0] == '#') + continue; + + if (tokens[0] == "v") + { + if (tokens.size() != 4) + { + std::cerr << "Navmesh::readOBJ(): Invalid line \"" << line << "\" in file \"" << filename << "\"" << std::endl; + return false; + } + + Vector3 vertex; + + std::stringstream(tokens[1]) >> vertex.x; + std::stringstream(tokens[2]) >> vertex.y; + std::stringstream(tokens[3]) >> vertex.z; + + vertices.push_back(vertex); + } + else if (tokens[0] == "f") + { + if (tokens.size() != 4) + { + std::cerr << "Navmesh::readOBJ(): Invalid line \"" << line << "\" in file \"" << filename << "\"" << std::endl; + return false; + } + + std::size_t a, b, c; + std::stringstream(tokens[1]) >> a; + std::stringstream(tokens[2]) >> b; + std::stringstream(tokens[3]) >> c; + + a -= 1; + b -= 1; + c -= 1; + + indices.push_back(a); + indices.push_back(b); + indices.push_back(c); + } + } + + return create(vertices, indices); +} + +Vector3 Navmesh::barycentric(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c) +{ + Vector3 v0 = b - a; + Vector3 v1 = c - a; + Vector3 v2 = p - a; + + float d00 = glm::dot(v0, v0); + float d01 = glm::dot(v0, v1); + float d11 = glm::dot(v1, v1); + float d20 = glm::dot(v2, v0); + float d21 = glm::dot(v2, v1); + float denom = d00 * d11 - d01 * d01; + + Vector3 result; + result.y = (d11 * d20 - d01 * d21) / denom; // v + result.z = (d00 * d21 - d01 * d20) / denom; // w + result.x = 1.0f - result.y - result.z; // u + + return result; +} + +Vector3 Navmesh::cartesian(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c) +{ + return a * p.x + b * p.y + c * p.z; +} + +// code taken from Detour's dtClosestPtPointTriangle +// @see https://github.com/recastnavigation/recastnavigation/blob/master/Detour/Source/DetourCommon.cpp +// (zlib license) +void Navmesh::closestPointOnTriangle(const Vector3& p, const Navmesh::Triangle* triangle, Vector3* closestPoint, Navmesh::Edge** closestEdge) +{ + // Grab triangle coordinates + const Vector3& a = triangle->edge->vertex->position; + const Vector3& b = triangle->edge->next->vertex->position; + const Vector3& c = triangle->edge->previous->vertex->position; + + // Check if P in vertex region outside A + Vector3 ab = b - a; + Vector3 ac = c - a; + Vector3 ap = p - a; + float d1 = glm::dot(ab, ap); + float d2 = glm::dot(ac, ap); + if (d1 <= 0.0f && d2 <= 0.0f) + { + // Barycentric coordinates (1, 0, 0) + *closestPoint = Vector3(1.0f, 0.0f, 0.0f); + *closestEdge = triangle->edge; + return; + } + + // Check if P in vertex region outside B + Vector3 bp = p - b; + float d3 = glm::dot(ab, bp); + float d4 = glm::dot(ac, bp); + if (d3 >= 0.0f && d4 <= d3) + { + // Barycentric coordinates (0, 1, 0) + *closestPoint = Vector3(0.0f, 1.0f, 0.0f); + *closestEdge = triangle->edge->next; + return; + } + + // Check if P in edge region of AB, if so return projection of P onto AB + float vc = d1 * d4 - d3 * d2; + if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) + { + // barycentric coordinates (1-v,v,0) + float v = d1 / (d1 - d3); + *closestPoint = Vector3(1.0f - v, v, 0.0f); + *closestEdge = triangle->edge; + return; + } + + // Check if P in vertex region outside C + Vector3 cp = p - c; + float d5 = glm::dot(ab, cp); + float d6 = glm::dot(ac, cp); + if (d6 >= 0.0f && d5 <= d6) + { + // Barycentric coordinates (0, 0, 1) + *closestPoint = Vector3(0.0f, 0.0f, 1.0f); + *closestEdge = triangle->edge->previous; + return; + } + + // Check if P in edge region of AC, if so return projection of P onto AC + float vb = d5 * d2 - d1 * d6; + if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) + { + // Barycentric coordinates (1 - w, 0, w) + float w = d2 / (d2 - d6); + *closestPoint = Vector3(1.0f - w, 0.0f, w); + *closestEdge = triangle->edge->previous; + return; + } + + // Check if P in edge region of BC, if so return projection of P onto BC + float va = d3 * d6 - d5 * d4; + if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) + { + // Barycentric coordinates (0, 1 - w, w) + float w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + *closestPoint = Vector3(0.0f, 1.0f - w, w); + *closestEdge = triangle->edge->next; + return; + } + + // P inside face region. Compute Q through its barycentric coordinates (u, v, w) + float denom = 1.0f / (va + vb + vc); + float v = vb * denom; + float w = vc * denom; + *closestPoint = Vector3(1.0f - v - w, v, w); + *closestEdge = nullptr; +} diff --git a/src/game/navmesh.hpp b/src/game/navmesh.hpp new file mode 100644 index 0000000..2679643 --- /dev/null +++ b/src/game/navmesh.hpp @@ -0,0 +1,264 @@ +/* + * 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 . + */ + +#ifndef NAVMESH_HPP +#define NAVMESH_HPP + +#include +#include +#include + +#include +using namespace Emergent; + +/** + * Navigation mesh represented by a half-edge structure. + * + * @ingroup geometry + */ +class Navmesh +{ +public: + struct Vertex; + struct Edge; + struct Triangle; + struct Step; + + /** + * Creates an instance of Navmesh. + */ + Navmesh(); + + /** + * Destroys an instance of Navmesh. + */ + ~Navmesh(); + + /** + * Forms a navmesh from a list of vertices and indices. + * + * @param vertices Specifies a list of vertices. + * @param indices Specifies a list of indices. + * @return `true` if the navmesh was successfully created, `false` otherwise. + */ + bool create(const std::vector& vertices, const std::vector& indices); + + /** + * Destroys the navmesh. + */ + void destroy(); + + /** + * Loads this navmesh from a triangulated Wavefront OBJ file. This method only supported **triangulated** Wavefront OBJ files. The supported commands are `v`, `f` and comment lines beginning with `#`. + * + * @param filename Path to the Wavefront OBJ file. + * @return `true` if the navmesh was successfully loaded from the OBJ file, `false` otherwise. + */ + bool loadOBJ(const std::string& filename); + + /** + * Traverses the navmesh. + * + * @param[in] startTriangle Initial triangle + * @param[in] startPosition Initial barycentric coordinates on the start triangle + * @param[in] startVelocity Initial cartesian velocity vector + * @param[out] traversal Traversal information + */ + static void traverse(Navmesh::Triangle* startTriangle, const Vector3& startPosition, const Vector3& startVelocity, std::vector* traversal); + + /// Returns a pointer to the navmesh vertices + const std::vector* getVertices() const; + + /// Returns a pointer to the navmesh edges + const std::vector* getEdges() const; + + /// Returns a pointer to the navmesh triangles + const std::vector* getTriangles() const; + + /// @copydoc Navmesh::getVertices() const + std::vector* getVertices(); + + /// @copydoc Navmesh::getEdges() const + std::vector* getEdges(); + + /// @copydoc Navmesh::getTriangles() const + std::vector* getTriangles(); + + /** + * Half-edge vertex which contains a pointer to its parent edge, a position vector, and an index. + */ + struct Vertex + { + /// Pointer to the edge to which this vertex belongs + Navmesh::Edge* edge; + + /// Vertex position vector + Vector3 position; + + /// Vertex flags + unsigned char flags; + + /// Index of this vertex + std::size_t index; + }; + + /** + * Half-edge edge which contains pointers to its starting vertex, parent triangle, and related edges. + */ + struct Edge + { + /// Pointer to the vertex at which the edge starts + Navmesh::Vertex* vertex; + + /// Pointer to the triangle to which this edge belongs + Navmesh::Triangle* triangle; + + /// Pointer to the previous edge in the parent triangle + Navmesh::Edge* previous; + + /// Pointer to the next edge in the parent triangle + Navmesh::Edge* next; + + /// Pointer to the symmetric edge + Navmesh::Edge* symmetric; + + /// Edge flags + unsigned char flags; + + /// Index of this edge + std::size_t index; + }; + + /** + * Half-edge triangle which contains a pointer to its first edge and its normal vector. + */ + struct Triangle + { + /// Pointer to the first edge in this triangle + Navmesh::Edge* edge; + + /// Faceted surface normal + Vector3 normal; + + /// Triangle flags + unsigned char flags; + + /// Index of this triangle + std::size_t index; + }; + + /** + * Contains informations about a single step in a navmesh traversal operation. + */ + struct Step + { + /// Pointer to the triangle on which the step occured + Triangle* triangle; + + /// Barycentric coordinates of the step's starting position + Vector3 start; + + /// Barycentric coordinates of the step's ending position + Vector3 end; + + /// Pointer to the edge on which the step exited the triangle, or `nullptr` if the step is within the triangle + Edge* edge; + }; + +private: + /** + * Calculates the faceted surface normals for each triangle. + */ + void calculateNormals(); + + /** + * Reads Wavefront OBJ data from an input stream + * + * @param stream Input stream containing OBJ data + * @param filename Path to the OBJ file + * @return `true` if OBJ data was successfully read from the file. + */ + bool readOBJ(std::istream* stream, const std::string& filename); + + /** + * Calculates barycentric coordinates from cartesian coordinates. + * + * @param p Cartesian point + * @param a First vertex in triangle + * @param b Second vertex in triangle + * @param c Third vertex in triangle + */ + static Vector3 barycentric(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c); + + /** + * Calculates cartesian coordinates from barycentric coordinates. + * + * @param p Barycentric point + * @param a First vertex in triangle + * @param b Second vertex in triangle + * @param c Third vertex in triangle + */ + static Vector3 cartesian(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c); + + /** + * Finds the closest point on a triangle. + * + * @param[in] p Point to project + * @param[in] triangle Triangle on which to find the closest point + * @param[out] closest Closest point on triangle + * @param[out] edge Edge on which the closest point is located, or `nullptr` if the closest point is not on an edge. + */ + static void closestPointOnTriangle(const Vector3& p, const Navmesh::Triangle* triangle, Vector3* closestPoint, Navmesh::Edge** closestEdge); + + std::vector vertices; + std::vector edges; + std::vector triangles; +}; + +inline const std::vector* Navmesh::getVertices() const +{ + return &vertices; +} + +inline const std::vector* Navmesh::getEdges() const +{ + return &edges; +} + +inline const std::vector* Navmesh::getTriangles() const +{ + return &triangles; +} + +inline std::vector* Navmesh::getVertices() +{ + return &vertices; +} + +inline std::vector* Navmesh::getEdges() +{ + return &edges; +} + +inline std::vector* Navmesh::getTriangles() +{ + return &triangles; +} + +#endif // NAVMESH_HPP diff --git a/src/nest.cpp b/src/game/nest.cpp similarity index 100% rename from src/nest.cpp rename to src/game/nest.cpp diff --git a/src/nest.hpp b/src/game/nest.hpp similarity index 100% rename from src/nest.hpp rename to src/game/nest.hpp diff --git a/src/terrain.cpp b/src/game/terrain.cpp similarity index 100% rename from src/terrain.cpp rename to src/game/terrain.cpp diff --git a/src/terrain.hpp b/src/game/terrain.hpp similarity index 100% rename from src/terrain.hpp rename to src/game/terrain.hpp diff --git a/src/material-loader.cpp b/src/material-loader.cpp index 31e72d0..35711bb 100644 --- a/src/material-loader.cpp +++ b/src/material-loader.cpp @@ -23,7 +23,12 @@ #include MaterialLoader::MaterialLoader() -{} +{ + textureLoader.setGamma(1.0f); + textureLoader.setCubemap(false); + textureLoader.setMipmapChain(false); + textureLoader.setMaxAnisotropy(16.0f); +} MaterialLoader::~MaterialLoader() { @@ -166,13 +171,13 @@ Texture* MaterialLoader::loadTexture(const std::string& filename) return it->second; } - // Load texture std::string fullFilename = std::string("data/textures/") + filename; - Texture* texture = new Texture(); - if (!texture->load(fullFilename)) + + // Load texture + Texture* texture = textureLoader.load(fullFilename); + if (!texture) { std::cerr << "MaterialLoader::loadTexture(): Failed to load texture file \"" << fullFilename << "\"" << std::endl; - delete texture; return nullptr; } diff --git a/src/material-loader.hpp b/src/material-loader.hpp index d7c65d9..9bdfe07 100644 --- a/src/material-loader.hpp +++ b/src/material-loader.hpp @@ -36,6 +36,7 @@ private: Texture* loadTexture(const std::string& filename); std::map textureCache; std::map materialCache; + TextureLoader textureLoader; }; #endif // MATERIAL_LOADER_HPP diff --git a/src/render-passes.cpp b/src/render-passes.cpp index 6c3ca9a..9a65be3 100644 --- a/src/render-passes.cpp +++ b/src/render-passes.cpp @@ -23,11 +23,13 @@ ShadowMapRenderPass::ShadowMapRenderPass(): depthShader(nullptr) -{} +{ + modelViewProjectionParam = parameterSet.addParameter("modelViewProjection", ShaderParameter::Type::MATRIX_4, 1); +} bool ShadowMapRenderPass::load(const RenderContext* renderContext) { - depthShader = shaderLoader.load("data/shaders/depth-pass.glsl"); + depthShader = shaderLoader.load("data/shaders/depth-pass.glsl", ¶meterSet); if (!depthShader) { return false; @@ -66,7 +68,6 @@ void ShadowMapRenderPass::render(const RenderContext* renderContext) // Bind shader depthShader->bind(); - ShaderParameterSet* parameters = depthShader->getParameters(); const Camera& camera = *(renderContext->camera); const std::list* operations = renderContext->queue->getOperations(); @@ -89,8 +90,8 @@ void ShadowMapRenderPass::render(const RenderContext* renderContext) } const Matrix4& modelMatrix = operation.transform; - Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); + Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; + depthShader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); glBindVertexArray(operation.vao); glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); @@ -101,14 +102,18 @@ void ShadowMapRenderPass::render(const RenderContext* renderContext) ClippingRenderPass::ClippingRenderPass(): shader(nullptr) -{} +{ + clippingPlanesParam = parameterSet.addParameter("clippingPlanes", ShaderParameter::Type::VECTOR_4, 1); + modelParam = parameterSet.addParameter("modelMatrix", ShaderParameter::Type::MATRIX_4, 1); + modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); +} bool ClippingRenderPass::load(const RenderContext* renderContext) { shaderLoader.undefine(); shaderLoader.define("CLIPPING_PLANE_COUNT", 1); - shader = shaderLoader.load("data/shaders/clip.glsl"); + shader = shaderLoader.load("data/shaders/clip.glsl", ¶meterSet); if (!shader) { return false; @@ -133,10 +138,9 @@ void ClippingRenderPass::render(const RenderContext* renderContext) // Bind shader shader->bind(); - ShaderParameterSet* parameters = shader->getParameters(); // Pass clipping planes to shader - parameters->setValue(ShaderParameter::CLIPPING_PLANES, clippingPlane); + shader->setParameter(clippingPlanesParam, clippingPlane); // Grab render context parameters const Camera& camera = *(renderContext->camera); @@ -163,8 +167,8 @@ void ClippingRenderPass::render(const RenderContext* renderContext) { const Matrix4& modelMatrix = operation.transform; Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; - parameters->setValue(ShaderParameter::MODEL_MATRIX, modelMatrix); - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); + shader->setParameter(modelParam, modelMatrix); + shader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); glBindVertexArray(operation.vao); glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); @@ -185,23 +189,28 @@ void ClippingRenderPass::setClippingPlane(const Plane& plane) CappingRenderPass::CappingRenderPass(): shader(nullptr) -{} +{ + horizonTexturesParam = parameterSet.addParameter("horizonTextures", ShaderParameter::Type::INT, 4); + modelParam = parameterSet.addParameter("modelMatrix", ShaderParameter::Type::MATRIX_4, 1); + modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); +} bool CappingRenderPass::load(const RenderContext* renderContext) { shaderLoader.undefine(); shaderLoader.define("TEXTURE_COUNT", 4); - shader = shaderLoader.load("data/shaders/soil-profile.glsl"); + shader = shaderLoader.load("data/shaders/soil-profile.glsl", ¶meterSet); if (!shader) { return false; } - horizonOTexture.load("data/textures/horizon-o.png"); - horizonATexture.load("data/textures/horizon-a.png"); - horizonBTexture.load("data/textures/horizon-b.png"); - horizonCTexture.load("data/textures/horizon-c.png"); + TextureLoader textureLoader; + horizonOTexture = textureLoader.load("data/textures/horizon-o.png"); + horizonATexture = textureLoader.load("data/textures/horizon-a.png"); + horizonBTexture = textureLoader.load("data/textures/horizon-b.png"); + horizonCTexture = textureLoader.load("data/textures/horizon-c.png"); return true; } @@ -211,10 +220,15 @@ void CappingRenderPass::unload() delete shader; shader = nullptr; - horizonOTexture.destroy(); - horizonATexture.destroy(); - horizonBTexture.destroy(); - horizonCTexture.destroy(); + delete horizonOTexture; + delete horizonATexture; + delete horizonBTexture; + delete horizonCTexture; + + horizonOTexture = nullptr; + horizonATexture = nullptr; + horizonBTexture = nullptr; + horizonCTexture = nullptr; } void CappingRenderPass::render(const RenderContext* renderContext) @@ -225,22 +239,21 @@ void CappingRenderPass::render(const RenderContext* renderContext) // Bind shader shader->bind(); - ShaderParameterSet* parameters = shader->getParameters(); // Bind texture glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, horizonOTexture.getTextureID()); + glBindTexture(GL_TEXTURE_2D, horizonOTexture->getTextureID()); glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, horizonATexture.getTextureID()); + glBindTexture(GL_TEXTURE_2D, horizonATexture->getTextureID()); glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, horizonBTexture.getTextureID()); + glBindTexture(GL_TEXTURE_2D, horizonBTexture->getTextureID()); glActiveTexture(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, horizonCTexture.getTextureID()); + glBindTexture(GL_TEXTURE_2D, horizonCTexture->getTextureID()); // Pass texture units to shader int textureUnits[] = {0, 1, 2, 3}; - parameters->setValue(ShaderParameter::MATERIAL_TEXTURE, 0, &textureUnits[0], 4); - + shader->setParameter(horizonTexturesParam, 0, &textureUnits[0], 4); + const Camera& camera = *(renderContext->camera); const std::list* operations = renderContext->queue->getOperations(); @@ -249,8 +262,8 @@ void CappingRenderPass::render(const RenderContext* renderContext) { const Matrix4& modelMatrix = operation.transform; Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; - parameters->setValue(ShaderParameter::MODEL_MATRIX, modelMatrix); - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); + shader->setParameter(modelParam, modelMatrix); + shader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); glBindVertexArray(operation.vao); glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); @@ -262,7 +275,10 @@ void CappingRenderPass::render(const RenderContext* renderContext) LightingRenderPass::LightingRenderPass(): shadowMap(0), - shadowCamera(nullptr) + shadowCamera(nullptr), + modelLoader(nullptr), + treeShadow(nullptr), + diffuseCubemap(nullptr) { // Initialize bias matrix for calculating the model-view-projection-bias matrix (used for shadow map texture coordinate calculation) biasMatrix = Matrix4( @@ -271,11 +287,24 @@ LightingRenderPass::LightingRenderPass(): 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f); - modelLoader.setMaterialLoader(&materialLoader); + modelParam = parameterSet.addParameter("modelMatrix", ShaderParameter::Type::MATRIX_4, 1); + modelViewParam = parameterSet.addParameter("modelViewMatrix", ShaderParameter::Type::MATRIX_4, 1); + modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); + normalModelViewParam = parameterSet.addParameter("normalModelViewMatrix", ShaderParameter::Type::MATRIX_3, 1); + normalModelParam = parameterSet.addParameter("normalModelMatrix", ShaderParameter::Type::MATRIX_3, 1); + cameraPositionParam = parameterSet.addParameter("cameraPosition", ShaderParameter::Type::VECTOR_3, 1); + directionalLightCountParam = parameterSet.addParameter("directionalLightCount", ShaderParameter::Type::INT, 1); + directionalLightColorsParam = parameterSet.addParameter("directionalLightColors", ShaderParameter::Type::VECTOR_3, 1); + directionalLightDirectionsParam = parameterSet.addParameter("directionalLightDirections", ShaderParameter::Type::VECTOR_3, 1); + albedoOpacityMapParam = parameterSet.addParameter("albedoOpacityMap", ShaderParameter::Type::INT, 1); + metalnessRoughnessMapParam = parameterSet.addParameter("metalnessRoughness", ShaderParameter::Type::INT, 1); + normalOcclusionMapParam = parameterSet.addParameter("normalOcclusionMap", ShaderParameter::Type::INT, 1); + diffuseCubemapParam = parameterSet.addParameter("diffuseCubemap", ShaderParameter::Type::INT, 1); + specularCubemapParam = parameterSet.addParameter("specularCubemap", ShaderParameter::Type::INT, 1); } bool LightingRenderPass::load(const RenderContext* renderContext) -{ +{ // For each render operation /* if (renderContext != nullptr) @@ -289,13 +318,32 @@ bool LightingRenderPass::load(const RenderContext* renderContext) */ // Load tree shadow - if (!treeShadow.load("data/textures/tree-shadow-0.png")) + TextureLoader textureLoader; + treeShadow = textureLoader.load("data/textures/tree-shadow-0.png"); + if (!treeShadow) { std::cerr << "Failed to load tree shadow" << std::endl; } + // Load cubemap + textureLoader.setCubemap(true); + textureLoader.setMipmapChain(false); + diffuseCubemap = textureLoader.load("data/textures/galileo-diffuse.png"); + if (!diffuseCubemap) + { + std::cerr << "Failed to load cubemap" << std::endl; + } + + textureLoader.setCubemap(true); + textureLoader.setMipmapChain(true); + specularCubemap = textureLoader.load("data/textures/galileo-specular_m%02d.png"); + if (!specularCubemap) + { + std::cerr << "Failed to load cubemap" << std::endl; + } + // Load unit plane - unitPlaneModel = modelLoader.load("data/models/unit-plane.mdl"); + unitPlaneModel = modelLoader->load("data/models/unit-plane.mdl"); if (!unitPlaneModel) { std::cout << "Failed to load unit plane" << std::endl; @@ -327,15 +375,16 @@ bool LightingRenderPass::load(const RenderContext* renderContext) shaderLoader.define("TEXTURE_COUNT", 0); shaderLoader.define("VERTEX_POSITION", EMERGENT_VERTEX_POSITION); shaderLoader.define("VERTEX_NORMAL", EMERGENT_VERTEX_NORMAL); + shaderLoader.define("VERTEX_TEXCOORD", EMERGENT_VERTEX_TEXCOORD); - lightingShader = shaderLoader.load("data/shaders/lit-object.glsl"); + lightingShader = shaderLoader.load("data/shaders/lit-object.glsl", ¶meterSet); if (!lightingShader) { return false; } time = 0.0f; - + return true; } @@ -352,6 +401,15 @@ void LightingRenderPass::unload() delete it->second; } shaderCache.clear(); + + delete treeShadow; + treeShadow = nullptr; + + delete diffuseCubemap; + diffuseCubemap = nullptr; + + delete specularCubemap; + specularCubemap = nullptr; } void LightingRenderPass::render(const RenderContext* renderContext) @@ -757,30 +815,96 @@ void LightingRenderPass::render(const RenderContext* renderContext) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Bind shader - lightingShader->bind(); - ShaderParameterSet* parameters = lightingShader->getParameters(); + Shader* shader = lightingShader; + shader->bind(); + + // Pass texture units to shader + shader->setParameter(albedoOpacityMapParam, 0); + shader->setParameter(normalOcclusionMapParam, 2); int directionalLightCount = 1; - Vector3 directionalLightColor[3]; - Vector3 directionalLightDirection[3]; - directionalLightColor[0] = Vector3(1); - directionalLightDirection[0] = glm::normalize(Vector3(camera.getView() * -Vector4(0, 0, -1, 0))); - parameters->setValue(ShaderParameter::DIRECTIONAL_LIGHT_COLOR, 0, &directionalLightColor[0], directionalLightCount); - parameters->setValue(ShaderParameter::DIRECTIONAL_LIGHT_DIRECTION, 0, &directionalLightDirection[0], directionalLightCount); + Vector3 directionalLightColors[3]; + Vector3 directionalLightDirections[3]; + directionalLightColors[0] = Vector3(1); + directionalLightDirections[0] = glm::normalize(Vector3(camera.getView() * -Vector4(0, 0, -1, 0))); - + shader->setParameter(directionalLightCountParam, directionalLightCount); + shader->setParameter(directionalLightColorsParam, 0, &directionalLightColors[0], directionalLightCount); + shader->setParameter(directionalLightDirectionsParam, 0, &directionalLightDirections[0], directionalLightCount); + + shader->setParameter(cameraPositionParam, camera.getTranslation()); + + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_CUBE_MAP, diffuseCubemap->getTextureID()); + shader->setParameter(diffuseCubemapParam, 3); + + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_CUBE_MAP, specularCubemap->getTextureID()); + shader->setParameter(specularCubemapParam, 4); + + Texture* albedoOpacityMap = nullptr; + Texture* metalnessRoughnessMap = nullptr; + Texture* normalOcclusionMap = nullptr; // Render operations for (const RenderOperation& operation: *operations) - { + { + // Skip render operations with unsupported materials + if (operation.material->getMaterialFormatID() != static_cast(MaterialFormat::PHYSICAL)) + { + continue; + } + const PhysicalMaterial* material = static_cast(operation.material); + + // Skip render operations with unsupported vertex formats + + // Bind albedo-opacity map + if (material->albedoOpacityMap != albedoOpacityMap) + { + albedoOpacityMap = material->albedoOpacityMap; + + if (albedoOpacityMap != nullptr) + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, albedoOpacityMap->getTextureID()); + } + } + + // Bind metalness-roughness map + if (material->metalnessRoughnessMap != metalnessRoughnessMap) + { + metalnessRoughnessMap = material->metalnessRoughnessMap; + + if (metalnessRoughnessMap != nullptr) + { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, metalnessRoughnessMap->getTextureID()); + } + } + + // Bind normal-occlusion map + if (material->normalOcclusionMap != normalOcclusionMap) + { + normalOcclusionMap = material->normalOcclusionMap; + + if (normalOcclusionMap != nullptr) + { + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, normalOcclusionMap->getTextureID()); + } + } + const Matrix4& modelMatrix = operation.transform; Matrix4 modelViewMatrix = camera.getView() * modelMatrix; Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; Matrix3 normalModelViewMatrix = glm::transpose(glm::inverse(Matrix3(modelViewMatrix))); + Matrix3 normalModelMatrix = glm::transpose(glm::inverse(Matrix3(modelMatrix))); - parameters->setValue(ShaderParameter::MODEL_VIEW_MATRIX, modelViewMatrix); - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); - parameters->setValue(ShaderParameter::NORMAL_MODEL_VIEW_MATRIX, normalModelViewMatrix); + shader->setParameter(modelParam, modelMatrix); + shader->setParameter(modelViewParam, modelViewMatrix); + shader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); + shader->setParameter(normalModelViewParam, normalModelViewMatrix); + shader->setParameter(normalModelParam, normalModelMatrix); glBindVertexArray(operation.vao); glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); @@ -853,9 +977,14 @@ bool LightingRenderPass::loadShader(const RenderOperation& operation) return false; } +DebugRenderPass::DebugRenderPass() +{ + modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); +} + bool DebugRenderPass::load(const RenderContext* renderContext) { - unlitSolidShader = shaderLoader.load("data/shaders/unlit-solid.glsl"); + unlitSolidShader = shaderLoader.load("data/shaders/unlit-solid.glsl", ¶meterSet); if (!unlitSolidShader) { return false; @@ -934,7 +1063,6 @@ void DebugRenderPass::render(const RenderContext* renderContext) // Bind unlit solid shader unlitSolidShader->bind(); - ShaderParameterSet* parameters = unlitSolidShader->getParameters(); // Bind AABB geometry glBindVertexArray(aabbVAO); @@ -955,12 +1083,20 @@ void DebugRenderPass::render(const RenderContext* renderContext) Matrix4 modelMatrix = glm::translate(translation) * glm::scale(scale); Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); + unlitSolidShader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); glDrawElements(GL_LINES, aabbIndexCount, GL_UNSIGNED_INT, (void*)0); } } +UIRenderPass::UIRenderPass() +{ + modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); + textureParam = parameterSet.addParameter("tex", ShaderParameter::Type::INT, 1); + texcoordOffsetParam = parameterSet.addParameter("texcoordOffset", ShaderParameter::Type::VECTOR_2, 1); + texcoordScaleParam = parameterSet.addParameter("texcoordScale", ShaderParameter::Type::VECTOR_2, 1); +} + bool UIRenderPass::load(const RenderContext* renderContext) { shaderLoader.define("VERTEX_POSITION", EMERGENT_VERTEX_POSITION); @@ -969,14 +1105,14 @@ bool UIRenderPass::load(const RenderContext* renderContext) shaderLoader.define("GAMMA_CORRECT"); shaderLoader.define("TEXTURE_COUNT", 1); - texturedUIShader = shaderLoader.load("data/shaders/ui.glsl"); + texturedUIShader = shaderLoader.load("data/shaders/ui.glsl", ¶meterSet); shaderLoader.undefine(); shaderLoader.define("VERTEX_POSITION", EMERGENT_VERTEX_POSITION); shaderLoader.define("VERTEX_COLOR", EMERGENT_VERTEX_COLOR); shaderLoader.define("GAMMA_CORRECT"); - untexturedUIShader = shaderLoader.load("data/shaders/ui.glsl"); + untexturedUIShader = shaderLoader.load("data/shaders/ui.glsl", ¶meterSet); if (!texturedUIShader || !untexturedUIShader) { @@ -1017,8 +1153,8 @@ void UIRenderPass::render(const RenderContext* renderContext) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glActiveTexture(GL_TEXTURE0); - - ShaderParameterSet* parameters; + + Shader* shader = nullptr; // Render operations const std::list* operations = renderContext->queue->getOperations(); @@ -1034,34 +1170,25 @@ void UIRenderPass::render(const RenderContext* renderContext) if (material->texture != nullptr) { - // Bind textured UI shader - texturedUIShader->bind(); - parameters = texturedUIShader->getParameters(); - parameters->setValue(ShaderParameter::MATERIAL_TEXTURE, 0); + shader = texturedUIShader; + shader->bind(); + shader->setParameter(textureParam, 0); + shader->setParameter(texcoordOffsetParam, Vector2(0.0f)); + shader->setParameter(texcoordScaleParam, Vector2(1.0f)); glBindTexture(GL_TEXTURE_2D, material->texture->getTextureID()); - - //parameters->setValue(ShaderParameter::TEXCOORD_OFFSET, Vector2(texture->getCoordinateOffset())); - //parameters->setValue(ShaderParameter::TEXCOORD_SCALE, Vector2(texture->getCoordinateScale())); - - parameters->setValue(ShaderParameter::TEXCOORD_OFFSET, Vector2(0.0f)); - parameters->setValue(ShaderParameter::TEXCOORD_SCALE, Vector2(1.0f)); } else { - // Bind untextured UI shader - untexturedUIShader->bind(); - parameters = untexturedUIShader->getParameters(); + shader = untexturedUIShader; + shader->bind(); } - - parameters->setValue(ShaderParameter::MATERIAL_DIFFUSE_COLOR, Vector3(1.0f)); - parameters->setValue(ShaderParameter::MATERIAL_OPACITY, 1.0f); - + const Matrix4& modelMatrix = operation.transform; Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; // Pass matrix parameters - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); + shader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); // Draw geometry glBindVertexArray(operation.vao); @@ -1071,7 +1198,10 @@ void UIRenderPass::render(const RenderContext* renderContext) VignetteRenderPass::VignetteRenderPass(): shader(nullptr) -{} +{ + bayerTextureParam = parameterSet.addParameter("bayerTexture", ShaderParameter::Type::INT, 1); + modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); +} bool VignetteRenderPass::load(const RenderContext* renderContext) { @@ -1080,7 +1210,7 @@ bool VignetteRenderPass::load(const RenderContext* renderContext) shaderLoader.define("VERTEX_COLOR", EMERGENT_VERTEX_COLOR); shaderLoader.define("TEXTURE_COUNT", 1); - shader = shaderLoader.load("data/shaders/vignette.glsl"); + shader = shaderLoader.load("data/shaders/vignette.glsl", ¶meterSet); if (!shader) { return false; @@ -1106,7 +1236,6 @@ bool VignetteRenderPass::load(const RenderContext* renderContext) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, pattern); - return true; } @@ -1126,14 +1255,13 @@ void VignetteRenderPass::render(const RenderContext* renderContext) // Bind shader shader->bind(); - ShaderParameterSet* parameters = shader->getParameters(); // Bind texture - glActiveTexture(0); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, bayerTextureID); // Pass texture unit to shader - parameters->setValue(ShaderParameter::MATERIAL_TEXTURE, 0); + shader->setParameter(bayerTextureParam, 0); const Camera& camera = *(renderContext->camera); const std::list* operations = renderContext->queue->getOperations(); @@ -1145,7 +1273,7 @@ void VignetteRenderPass::render(const RenderContext* renderContext) const Matrix4& modelMatrix = operation.transform; Matrix4 modelViewProjectionMatrix = camera.getViewProjection() * modelMatrix; - parameters->setValue(ShaderParameter::MODEL_VIEW_PROJECTION_MATRIX, modelViewProjectionMatrix); + shader->setParameter(modelViewProjectionParam, modelViewProjectionMatrix); glBindVertexArray(operation.vao); glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); diff --git a/src/render-passes.hpp b/src/render-passes.hpp index 7918551..0747b4f 100644 --- a/src/render-passes.hpp +++ b/src/render-passes.hpp @@ -39,6 +39,9 @@ public: virtual void render(const RenderContext* renderContext); private: + ShaderParameterSet parameterSet; + const ShaderParameter* modelViewProjectionParam; + ShaderLoader shaderLoader; Shader* depthShader; }; @@ -57,6 +60,11 @@ public: void setClippingPlane(const Plane& plane); private: + ShaderParameterSet parameterSet; + const ShaderParameter* modelParam; + const ShaderParameter* modelViewProjectionParam; + const ShaderParameter* clippingPlanesParam; + ShaderLoader shaderLoader; Shader* shader; Vector4 clippingPlane; @@ -74,13 +82,18 @@ public: virtual void render(const RenderContext* renderContext); private: + ShaderParameterSet parameterSet; + const ShaderParameter* modelParam; + const ShaderParameter* modelViewProjectionParam; + const ShaderParameter* horizonTexturesParam; + ShaderLoader shaderLoader; Shader* shader; - Texture horizonOTexture; - Texture horizonATexture; - Texture horizonBTexture; - Texture horizonCTexture; + Texture* horizonOTexture; + Texture* horizonATexture; + Texture* horizonBTexture; + Texture* horizonCTexture; }; @@ -98,6 +111,7 @@ public: inline void setShadowMap(GLuint shadowMap) { this->shadowMap = shadowMap; } inline void setShadowCamera(const Camera* camera) { this->shadowCamera = camera; } + inline void setModelLoader(ModelLoader* modelLoader) { this->modelLoader = modelLoader; } inline void setClippingPlanes(const Plane* planes) { @@ -109,13 +123,32 @@ public: private: bool loadShader(const RenderOperation& operation); + + ShaderParameterSet parameterSet; + const ShaderParameter* modelParam; + const ShaderParameter* modelViewParam; + const ShaderParameter* modelViewProjectionParam; + const ShaderParameter* normalModelViewParam; + const ShaderParameter* normalModelParam; + const ShaderParameter* cameraPositionParam; + const ShaderParameter* directionalLightCountParam; + const ShaderParameter* directionalLightColorsParam; + const ShaderParameter* directionalLightDirectionsParam; + const ShaderParameter* albedoOpacityMapParam; + const ShaderParameter* metalnessRoughnessMapParam; + const ShaderParameter* normalOcclusionMapParam; + const ShaderParameter* diffuseCubemapParam; + const ShaderParameter* specularCubemapParam; + ShaderLoader shaderLoader; std::map shaderCache; Shader* lightingShader; Matrix4 biasMatrix; GLuint shadowMap; - Texture treeShadow; + Texture* treeShadow; + Texture* diffuseCubemap; + Texture* specularCubemap; const Camera* shadowCamera; float time; @@ -128,8 +161,7 @@ private: ClippingRenderPass clippingRenderPass; CappingRenderPass cappingRenderPass; - MaterialLoader materialLoader; - ModelLoader modelLoader; + ModelLoader* modelLoader; }; /** @@ -138,11 +170,15 @@ private: class DebugRenderPass: public RenderPass { public: + DebugRenderPass(); virtual bool load(const RenderContext* renderContext); virtual void unload(); virtual void render(const RenderContext* renderContext); private: + ShaderParameterSet parameterSet; + const ShaderParameter* modelViewProjectionParam; + ShaderLoader shaderLoader; Shader* unlitSolidShader; @@ -159,11 +195,18 @@ private: class UIRenderPass: public RenderPass { public: + UIRenderPass(); virtual bool load(const RenderContext* renderContext); virtual void unload(); virtual void render(const RenderContext* renderContext); private: + ShaderParameterSet parameterSet; + const ShaderParameter* modelViewProjectionParam; + const ShaderParameter* textureParam; + const ShaderParameter* texcoordOffsetParam; + const ShaderParameter* texcoordScaleParam; + ShaderLoader shaderLoader; Shader* texturedUIShader; Shader* untexturedUIShader; @@ -181,6 +224,10 @@ public: virtual void render(const RenderContext* renderContext); private: + ShaderParameterSet parameterSet; + const ShaderParameter* modelViewProjectionParam; + const ShaderParameter* bayerTextureParam; + ShaderLoader shaderLoader; Shader* shader; GLuint bayerTextureID; diff --git a/src/states/experiment-state.hpp b/src/states/experiment-state.hpp index f2ba12c..cc42b4d 100644 --- a/src/states/experiment-state.hpp +++ b/src/states/experiment-state.hpp @@ -23,8 +23,8 @@ #include "../application-state.hpp" #include "../input.hpp" #include "../debug.hpp" -#include "../nest.hpp" -#include "../terrain.hpp" +#include "../game/nest.hpp" +#include "../game/terrain.hpp" #include using namespace Emergent; diff --git a/src/states/splash-state.cpp b/src/states/splash-state.cpp index 3de8b88..578c97b 100644 --- a/src/states/splash-state.cpp +++ b/src/states/splash-state.cpp @@ -63,11 +63,13 @@ void SplashState::enter() } delete fontLoader; - // Load splash texture - application->splashTexture.load("data/textures/splash.png"); - - // Load title texture - application->titleTexture.load("data/textures/title.png"); + // Load splash & title textures + application->textureLoader->setGamma(1.0f); + application->textureLoader->setCubemap(false); + application->textureLoader->setMipmapChain(false); + application->textureLoader->setMaxAnisotropy(1.0f); + application->splashTexture = application->textureLoader->load("data/textures/galileo_cross.hdr"); + application->titleTexture = application->textureLoader->load("data/textures/title.png"); // Get UI strings std::string pressAnyKeyString; @@ -121,16 +123,16 @@ void SplashState::enter() application->splashImage = new UIImage(); application->splashImage->setAnchor(Anchor::CENTER); - application->splashImage->setDimensions(Vector2(application->splashTexture.getWidth(), application->splashTexture.getHeight())); - application->splashImage->setTexture(&application->splashTexture); + application->splashImage->setDimensions(Vector2(application->splashTexture->getWidth(), application->splashTexture->getHeight())); + application->splashImage->setTexture(application->splashTexture); application->splashImage->setVisible(false); application->uiRootElement->addChild(application->splashImage); application->titleImage = new UIImage(); application->titleImage->setAnchor(Vector2(0.5f, 0.0f)); - application->titleImage->setDimensions(Vector2(application->titleTexture.getWidth(), application->titleTexture.getHeight())); - application->titleImage->setTranslation(Vector2(0.0f, (int)(application->height * (1.0f / 3.0f) - application->titleTexture.getHeight()))); - application->titleImage->setTexture(&application->titleTexture); + application->titleImage->setDimensions(Vector2(application->titleTexture->getWidth(), application->titleTexture->getHeight())); + application->titleImage->setTranslation(Vector2(0.0f, (int)(application->height * (1.0f / 3.0f) - application->titleTexture->getHeight()))); + application->titleImage->setTexture(application->titleTexture); application->titleImage->setVisible(false); application->uiRootElement->addChild(application->titleImage); @@ -490,8 +492,8 @@ void SplashState::enter() application->selectMenuItem(application->selectedMenuItemIndex); // Models - application->displayModel = application->modelLoader->load("data/models/monkey.mdl"); - application->antModel = application->modelLoader->load("data/models/worker-ant.mdl"); + application->displayModel = application->modelLoader->load("data/models/icosphere.mdl"); + application->antModel = application->modelLoader->load("data/models/agent.mdl"); // Model instances application->displayModelInstance = new ModelInstance(); diff --git a/src/states/title-state.cpp b/src/states/title-state.cpp index af134d8..06f1ce5 100644 --- a/src/states/title-state.cpp +++ b/src/states/title-state.cpp @@ -20,6 +20,7 @@ #include "title-state.hpp" #include "experiment-state.hpp" #include "../application.hpp" +#include "../camera-controller.hpp" #include #include @@ -45,7 +46,7 @@ void TitleState::enter() // Setup screen fade-in transition fadeIn = false; fadeOut = false; - + /* // Load tunnel texture GLuint tunnelTexture; @@ -81,6 +82,7 @@ void TitleState::enter() application->lightingPass.setRenderTarget(&application->defaultRenderTarget); application->lightingPass.setShadowMap(0); application->lightingPass.setShadowCamera(&application->camera); + application->lightingPass.setModelLoader(application->modelLoader); application->defaultCompositor.addPass(&application->lightingPass); application->camera.lookAt( @@ -111,7 +113,6 @@ void TitleState::enter() application->scene.getLayer(0)->addObject(lightC); application->scene.getLayer(0)->addObject(application->displayModelInstance); - application->scene.getLayer(0)->addObject(application->antModelInstance); application->scene.getLayer(0)->addObject(&application->camera); // Load compositor @@ -130,8 +131,43 @@ void TitleState::enter() application->fadeInTween->start(); application->inputManager->addWindowObserver(this); + application->mouse->addMouseButtonObserver(this); windowResized(application->width, application->height); + // Setup camera controller + application->surfaceCam->setCamera(&application->camera); + application->surfaceCam->setFocalPoint(Vector3(0.0f)); + application->surfaceCam->setFocalDistance(10.0f); + application->surfaceCam->setElevation(0.0f); + application->surfaceCam->setAzimuth(0.0f); + application->surfaceCam->setTargetFocalPoint(application->surfaceCam->getFocalPoint()); + application->surfaceCam->setTargetFocalDistance(application->surfaceCam->getFocalDistance()); + application->surfaceCam->setTargetElevation(application->surfaceCam->getElevation()); + application->surfaceCam->setTargetAzimuth(application->surfaceCam->getAzimuth()); + application->surfaceCam->update(0.0f); + + // Setup arcball + dragging = false; + wasDragging = dragging; + application->arcball.setCenter(Vector2(application->width * 0.5f, application->height * 0.5f)); + application->arcball.setRadius(application->height * 0.5f); + + // Load navmesh + + navmesh.loadOBJ("data/textures/icosphere.obj"); + + // Setup colony + + colony.setAntModel(application->antModel); + for (int i = 0; i < 20; ++i) + { + ant = colony.spawn(&navmesh, (*navmesh.getTriangles())[0], normalize_barycentric(Vector3(0.5f))); + application->scene.getLayer(0)->addObject(ant->getModelInstance()); + ant->setState(Ant::State::WANDER); + } + + ant->setState(Ant::State::IDLE); + // Start timer stateTime = 0.0f; application->frameTimer.reset(); @@ -148,6 +184,13 @@ void TitleState::execute() // Add dt to state time stateTime += dt; + // Update menu controls + application->menuControlProfile->update(); + application->gameControlProfile->update(); + + // Update input + application->inputManager->update(); + if (substate == 0 || substate == 1) { if (stateTime >= titleDelay && !application->titleImage->isVisible()) @@ -242,24 +285,27 @@ void TitleState::execute() fadeIn = true; } - // Update display model - Transform transform = application->displayModelInstance->getTransform(); - transform.translation = Vector3(0, 0.0f, 0); - transform.scale = Vector3(0.75f); - transform.rotation = glm::angleAxis(stateTime * glm::radians(360.0f) / 60.0f, glm::vec3(0, 1, 0)); - application->displayModelInstance->setTransform(transform); + glm::ivec2 mousePosition = application->mouse->getCurrentPosition(); + mousePosition.y = application->height - mousePosition.y; + if (dragging && !wasDragging) + { + dragStart = application->arcball.project(Vector2(mousePosition.x, mousePosition.y)); + dragStartRotation = application->displayModelInstance->getTransform().rotation; + } + else if (dragging && wasDragging) + { + Vector3 dragEnd = application->arcball.project(Vector2(mousePosition.x, mousePosition.y)); + Quaternion rotation = glm::normalize(glm::rotation(dragStart, dragEnd)); + + // Update display model + Transform transform = application->displayModelInstance->getTransform(); + transform.rotation = glm::normalize(rotation * dragStartRotation); + application->displayModelInstance->setTransform(transform); + } + wasDragging = dragging; - transform.scale = Vector3(0.25f); - transform.translation.y = 0.0f; - //application->antModelInstance->setTransform(transform); - // Update menu controls - application->menuControlProfile->update(); - - // Update input - application->inputManager->update(); - // Check if application was closed if (application->inputManager->wasClosed() || application->escape.isTriggered()) { @@ -272,6 +318,19 @@ void TitleState::execute() { application->changeFullscreen(); } + + float rotationSpeed = glm::radians(3.0f) * dt / (1.0f / 60.0f); + if (application->cameraRotateCW.isTriggered()) + application->surfaceCam->rotate(-rotationSpeed); + if (application->cameraRotateCCW.isTriggered()) + application->surfaceCam->rotate(rotationSpeed); + // Zoom camera + float zoomFactor = application->surfaceCam->getFocalDistance() / 20.0f * dt / (1.0f / 60.0f); + if (application->cameraZoomIn.isTriggered()) + application->surfaceCam->zoom(zoomFactor * application->cameraZoomIn.getCurrentValue()); + if (application->cameraZoomOut.isTriggered()) + application->surfaceCam->zoom(-zoomFactor * application->cameraZoomOut.getCurrentValue()); + application->surfaceCam->update(dt); // Navigate menu if (application->menuDown.isTriggered() && !application->menuDown.wasTriggered()) @@ -312,6 +371,30 @@ void TitleState::execute() Vector2(container->getPosition().x - application->menuSelectorLabel->getDimensions().x * 1.5f, container->getPosition().y + lineHeight * 0.5f - application->menuSelectorLabel->getDimensions().y * 0.5f + lineHeight * application->selectedMenuItemIndex)); + + float walkSpeed = 3.0f * dt; + float turnSpeed = 4.0f * dt; + Vector3 antVelocity = ant->getForward() * walkSpeed; + + if (application->walkForward.isTriggered()) + { + ant->move(antVelocity); + } + if (application->walkBack.isTriggered()) + { + ant->move(-antVelocity); + } + if (application->turnLeft.isTriggered()) + { + ant->turn(turnSpeed); + } + if (application->turnRight.isTriggered()) + { + ant->turn(-turnSpeed); + } + + colony.update(dt); + // Perform tweening application->tweener->update(dt); @@ -378,4 +461,14 @@ void TitleState::windowResized(int width, int height) (float)application->width / (float)application->height, 0.1f, 1000.0f); -} \ No newline at end of file +} + +void TitleState::mouseButtonPressed(int button, int x, int y) +{ + dragging = true; +} + +void TitleState::mouseButtonReleased(int button, int x, int y) +{ + dragging = false; +} diff --git a/src/states/title-state.hpp b/src/states/title-state.hpp index ac1f2d1..e9d1018 100644 --- a/src/states/title-state.hpp +++ b/src/states/title-state.hpp @@ -23,13 +23,15 @@ #include "../application-state.hpp" #include "../ui/ui.hpp" +#include "../game/ant.hpp" + #include using namespace Emergent; /** * Displays the title screen. */ -class TitleState: public ApplicationState, public WindowObserver +class TitleState: public ApplicationState, public WindowObserver, public MouseButtonObserver { public: TitleState(Application* application); @@ -39,14 +41,25 @@ public: virtual void execute(); virtual void exit(); - void windowClosed(); - void windowResized(int width, int height); + virtual void windowClosed(); + virtual void windowResized(int width, int height); + virtual void mouseButtonPressed(int button, int x, int y); + virtual void mouseButtonReleased(int button, int x, int y); + private: float stateTime; bool fadeIn; bool fadeOut; + bool dragging; + bool wasDragging; + Vector3 dragStart; + Quaternion dragStartRotation; int substate; + + Colony colony; + Ant* ant; + Navmesh navmesh; }; #endif // TITLE_STATE_HPP \ No newline at end of file