ecsgame/ECS/Systems/PathMoveSystem.h
karl d6effd66f6 Finish basic spline movement
The speed isn't chosen correctly yet, but the basic movement along the spline is functional!

There are opportunities for a performance increase: Some calculations which we currently do every frame, are actually only necessary once we choose a new point for moving towards.
2020-09-28 18:14:20 +02:00

153 lines
6.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef __PATHMOVESYSTEM_H__
#define __PATHMOVESYSTEM_H__
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "../ECS.h"
#include "../Components/Transform.h"
#include "../Events/InputEvent.h"
#include "../Components/PathMove.h"
using namespace ECS;
// Spline helper functions
// Calculate the t value for a CatmullRom spline
float get_t(float alpha, float t, glm::vec3 p0, glm::vec3 p1) {
float a = pow((p1.x - p0.x), 2.0f) + pow((p1.y - p0.y), 2.0f) + pow((p1.z - p0.z), 2.0f);
float b = pow(a, alpha * 0.5f);
return (b + t);
}
// Given four points, calculate an interpolated point between p1 and p2 using a Catmul-Rom spline.
// t specifies the position along the path, with t=0 being p1 and t=2 being p2.
glm::vec3 catmul(float alpha, glm::vec3 p0, glm::vec3 p1, glm::vec3 p2, glm::vec3 p3, float t) {
float t0 = 0.0f;
float t1 = get_t(alpha, t0, p0, p1);
float t2 = get_t(alpha, t1, p1, p2);
float t3 = get_t(alpha, t2, p2, p3);
// Lerp t to be between t1 and t2
t = t1 + t * (t2 - t1);
glm::vec3 A1 = (t1-t)/(t1-t0)*p0 + (t-t0)/(t1-t0)*p1;
glm::vec3 A2 = (t2-t)/(t2-t1)*p1 + (t-t1)/(t2-t1)*p2;
glm::vec3 A3 = (t3-t)/(t3-t2)*p2 + (t-t2)/(t3-t2)*p3;
glm::vec3 B1 = (t2-t)/(t2-t0)*A1 + (t-t0)/(t2-t0)*A2;
glm::vec3 B2 = (t3-t)/(t3-t1)*A2 + (t-t1)/(t3-t1)*A3;
glm::vec3 C = (t2-t)/(t2-t1)*B1 + (t-t1)/(t2-t1)*B2;
return C;
}
class PathMoveSystem : public EntitySystem, public EventSubscriber<InputEvent> {
void configure(World *pWorld) override {
myWorld = pWorld;
// TODO: Changing velocity
myWorld->subscribe<InputEvent>(this);
}
void receive(World *pWorld, const InputEvent &event) override {
if (event.key == GLFW_KEY_W) {
myWorld->each<PathMove>([&](Entity *ent, ComponentHandle<PathMove> pathmove) {
if (event.action == GLFW_PRESS) {
// TODO: Velocity adder
} else if (event.action == GLFW_RELEASE) {
// TODO
}
});
} else if (event.key == GLFW_KEY_S) {
myWorld->each<PathMove>([&](Entity *ent, ComponentHandle<PathMove> pathmove) {
if (event.action == GLFW_PRESS) {
// TODO
} else if (event.action == GLFW_RELEASE) {
// TODO
}
});
}
}
void tick(World *pWorld, float deltaTime) override {
pWorld->each<Transform, PathMove>(
[&](Entity *ent, ComponentHandle<Transform> transform, ComponentHandle<PathMove> pathmove) {
// Shorthand for the path (we'll use this a lot)
PathMove::Path path = pathmove->path;
// Add the passed time
pathmove->time_passed += deltaTime;
// Shorthand for number of points in the path
int num_points = path.points.size();
if (pathmove->time_passed >= 1.0) {
// If we passed the last target, set the current_point_index to that target
pathmove->time_passed -= 1.0;
pathmove->current_point_index += 1;
// If the point index is greater than the second to last one, reset
// (The point index specifies the point we're coming from, not the one we're moving towards)
if (pathmove->current_point_index >= num_points - 1) {
pathmove->current_point_index = 0;
}
}
// The four points which are needed for the spline
// p1 and p2 are always the same (the current origin and the current target), but the rest depends on edge cases
glm::vec3 p0;
glm::vec3 p1 = path.points[pathmove->current_point_index];
glm::vec3 p2 = path.points[pathmove->current_point_index + 1];
glm::vec3 p3;
if (pathmove->current_point_index == num_points - 2) {
// We're moving towards the last point, so the point after that needs to be interpolated.
// We interpolate linearly along the line from this point to the target point.
glm::vec3 interp_direction = p2 - p1;
p3 = p2 + interp_direction * 2.0f;
} else {
// We're fine - use the point after the target for p3
p2 = path.points[pathmove->current_point_index + 1];
p3 = path.points[pathmove->current_point_index + 2];
}
if (pathmove->current_point_index == 0) {
// We're at the first point, so the point before this needs to be interpolated.
// We interpolate linearly along the line from this to the next point (backwards).
glm::vec3 interp_direction = path.points[pathmove->current_point_index] - path.points[pathmove->current_point_index + 1];
p0 = path.points[pathmove->current_point_index] + interp_direction;
} else {
// We're fine - use the point before the current point
p0 = path.points[pathmove->current_point_index - 1];
}
// Calculate the point on the spline
glm::vec3 point = catmul(0.5f,
p0,
p1,
p2,
p3,
pathmove->time_passed);
// Apply
transform->set_position(point);
// FIXME: Debugging output
std::cout << point.x << ", " << point.y << ", " << point.z << std::endl;
});
}
void unconfigure(World *pWorld) override {
pWorld->unsubscribeAll(this);
}
private:
World *myWorld;
};
#endif // __PATHMOVESYSTEM_H__