Add trails and projections
parent
4911bfe8d5
commit
3ee599caeb
|
|
@ -1,27 +1,137 @@
|
|||
#include "components/PhysicsComponent.hpp"
|
||||
|
||||
#include "Entity.hpp"
|
||||
#include "components/GravityWellComponent.hpp"
|
||||
#include "components/NullZoneComponent.hpp"
|
||||
#include "components/TransformComponent.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void PhysicsComponent::Setup() {}
|
||||
|
||||
void PhysicsComponent::ClampVelocity(float &vxRef, float &vyRef) const {
|
||||
const float speed = std::sqrt(vxRef * vxRef + vyRef * vyRef);
|
||||
if (speed > speedCap && speed > 0.0f) {
|
||||
const float scale = speedCap / speed;
|
||||
vxRef *= scale;
|
||||
vyRef *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsComponent::ClampVelocity(double &vxRef, double &vyRef) const {
|
||||
const double speed = std::sqrt(vxRef * vxRef + vyRef * vyRef);
|
||||
if (speed > static_cast<double>(speedCap) && speed > 0.0) {
|
||||
const double scale = static_cast<double>(speedCap) / speed;
|
||||
vxRef *= scale;
|
||||
vyRef *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsComponent::IntegratePosition(float &xRef, float &yRef, float vxValue, float vyValue,
|
||||
float dt) {
|
||||
xRef += vxValue * dt;
|
||||
yRef += vyValue * dt;
|
||||
}
|
||||
|
||||
void PhysicsComponent::IntegratePosition(double &xRef, double &yRef, double vxValue, double vyValue,
|
||||
double dt) {
|
||||
xRef += vxValue * dt;
|
||||
yRef += vyValue * dt;
|
||||
}
|
||||
|
||||
bool PhysicsComponent::IsInsideNullZone(double xPos) const {
|
||||
if (!context || !context->entities) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &other : *context->entities) {
|
||||
if (!other || other.get() == entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto zone = other->GetComponent<NullZoneComponent>();
|
||||
auto zoneTransform = other->GetComponent<TransformComponent>();
|
||||
if (!zone || !zoneTransform) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const double left = static_cast<double>(zoneTransform->get().x);
|
||||
const double right = left + static_cast<double>(zone->get().width);
|
||||
if (xPos >= left && xPos <= right) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PhysicsComponent::ComputeWellDeltaVelocity(double px, double py, double dt, double &dvx,
|
||||
double &dvy, bool ignoreWellActive) const {
|
||||
dvx = 0.0;
|
||||
dvy = 0.0;
|
||||
|
||||
if (!context || !context->wellEntity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto wellTransform = context->wellEntity->GetComponent<TransformComponent>();
|
||||
auto wellGravity = context->wellEntity->GetComponent<GravityWellComponent>();
|
||||
if (!wellTransform || !wellGravity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ignoreWellActive && !wellGravity->get().active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsInsideNullZone(px)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double dx = static_cast<double>(wellTransform->get().x) - px;
|
||||
const double dy = static_cast<double>(wellTransform->get().y) - py;
|
||||
const double dist = std::sqrt(dx * dx + dy * dy);
|
||||
if (dist <= 0.0001) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double clampedDist = std::max(dist, static_cast<double>(wellGravity->get().minDist));
|
||||
const double force = static_cast<double>(wellGravity->get().mass) / (clampedDist * clampedDist);
|
||||
dvx = (dx / dist) * force * dt;
|
||||
dvy = (dy / dist) * force * dt;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PhysicsComponent::SimulateStep(double &xRef, double &yRef, double &vxRef, double &vyRef,
|
||||
double dt, bool ignoreWellActive) const {
|
||||
double dvx = 0.0;
|
||||
double dvy = 0.0;
|
||||
ComputeWellDeltaVelocity(xRef, yRef, dt, dvx, dvy, ignoreWellActive);
|
||||
vxRef += dvx;
|
||||
vyRef += dvy;
|
||||
|
||||
ClampVelocity(vxRef, vyRef);
|
||||
IntegratePosition(xRef, yRef, vxRef, vyRef, dt);
|
||||
}
|
||||
|
||||
void PhysicsComponent::Update(float dt) {
|
||||
auto transform = entity->GetComponent<TransformComponent>();
|
||||
if (!transform) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float speed = std::sqrt(vx * vx + vy * vy);
|
||||
if (speed > speedCap && speed > 0.0f) {
|
||||
const float scale = speedCap / speed;
|
||||
vx *= scale;
|
||||
vy *= scale;
|
||||
}
|
||||
double x = static_cast<double>(transform->get().x);
|
||||
double y = static_cast<double>(transform->get().y);
|
||||
double vxLocal = static_cast<double>(vx);
|
||||
double vyLocal = static_cast<double>(vy);
|
||||
|
||||
transform->get().x += vx * dt;
|
||||
transform->get().y += vy * dt;
|
||||
SimulateStep(x, y, vxLocal, vyLocal, static_cast<double>(dt), false);
|
||||
|
||||
transform->get().x = static_cast<float>(x);
|
||||
transform->get().y = static_cast<float>(y);
|
||||
vx = static_cast<float>(vxLocal);
|
||||
vy = static_cast<float>(vyLocal);
|
||||
}
|
||||
|
||||
void PhysicsComponent::Cleanup() {}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,38 @@ struct PhysicsComponent : public Component {
|
|||
float vy = 0.0f;
|
||||
float speedCap = 400.0f;
|
||||
|
||||
/**
|
||||
* Clamps the provided velocity vector to this component's speed cap.
|
||||
*/
|
||||
void ClampVelocity(float &vxRef, float &vyRef) const;
|
||||
void ClampVelocity(double &vxRef, double &vyRef) const;
|
||||
|
||||
/**
|
||||
* Integrates a position by velocity over dt.
|
||||
*/
|
||||
static void IntegratePosition(float &xRef, float &yRef, float vxValue, float vyValue, float dt);
|
||||
static void IntegratePosition(double &xRef, double &yRef, double vxValue, double vyValue,
|
||||
double dt);
|
||||
|
||||
/**
|
||||
* Returns true when the given X position is inside a null zone.
|
||||
*/
|
||||
bool IsInsideNullZone(double xPos) const;
|
||||
|
||||
/**
|
||||
* Computes velocity delta from well gravity for a position.
|
||||
* Returns false when gravity should not apply.
|
||||
*/
|
||||
bool ComputeWellDeltaVelocity(double px, double py, double dt, double &dvx, double &dvy,
|
||||
bool ignoreWellActive = false) const;
|
||||
|
||||
/**
|
||||
* Performs one projected simulation step using well/null-zone rules,
|
||||
* velocity clamp, and position integration.
|
||||
*/
|
||||
void SimulateStep(double &xRef, double &yRef, double &vxRef, double &vyRef, double dt,
|
||||
bool ignoreWellActive = false) const;
|
||||
|
||||
void Setup() override;
|
||||
void Update(float dt) override;
|
||||
void Cleanup() override;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ void ProbeStateComponent::Update(float) {
|
|||
|
||||
if (transform->get().y < -20.0f ||
|
||||
transform->get().y > static_cast<float>(GetScreenHeight() + 20) ||
|
||||
transform->get().x < -20.0f) {
|
||||
transform->get().x < -20.0f ||
|
||||
transform->get().x > static_cast<float>(GetScreenWidth() + 20)) {
|
||||
if (context->onPlayerDeath) {
|
||||
context->onPlayerDeath();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,74 @@
|
|||
#include "components/ProjectionComponent.hpp"
|
||||
|
||||
void ProjectionComponent::Setup() {}
|
||||
void ProjectionComponent::Update(float) {}
|
||||
void ProjectionComponent::Cleanup() {}
|
||||
#include "Entity.hpp"
|
||||
#include "components/GravityReceiverComponent.hpp"
|
||||
#include "components/GravityWellComponent.hpp"
|
||||
#include "components/PhysicsComponent.hpp"
|
||||
#include "components/TransformComponent.hpp"
|
||||
|
||||
void ProjectionComponent::Setup() { points.clear(); }
|
||||
|
||||
void ProjectionComponent::Update(float) {
|
||||
points.clear();
|
||||
highlightActive = false;
|
||||
|
||||
if (!context || !context->probeEntity || entity != context->probeEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto transform = entity->GetComponent<TransformComponent>();
|
||||
auto physics = entity->GetComponent<PhysicsComponent>();
|
||||
auto receiver = entity->GetComponent<GravityReceiverComponent>();
|
||||
if (!transform || !physics || !receiver) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entity *wellEntity = receiver->get().well ? receiver->get().well : context->wellEntity;
|
||||
if (!wellEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto wellTransform = wellEntity->GetComponent<TransformComponent>();
|
||||
auto wellGravity = wellEntity->GetComponent<GravityWellComponent>();
|
||||
if (!wellTransform || !wellGravity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only highlight if the well is active, inactive will be a lighter color to show the potential
|
||||
// projection if the player were to activate it
|
||||
highlightActive = wellGravity->get().active;
|
||||
|
||||
double px = static_cast<double>(transform->get().x);
|
||||
double py = static_cast<double>(transform->get().y);
|
||||
double vx = static_cast<double>(physics->get().vx);
|
||||
double vy = static_cast<double>(physics->get().vy);
|
||||
|
||||
points.reserve(static_cast<size_t>(steps));
|
||||
for (int i = 0; i < steps; ++i) {
|
||||
physics->get().SimulateStep(px, py, vx, vy, static_cast<double>(stepDt), true);
|
||||
points.push_back({static_cast<float>(px), static_cast<float>(py)});
|
||||
}
|
||||
|
||||
Draw();
|
||||
}
|
||||
|
||||
void ProjectionComponent::Cleanup() { points.clear(); }
|
||||
|
||||
void ProjectionComponent::Draw() const {
|
||||
if (points.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Color base = highlightActive ? activeColor : inactiveColor;
|
||||
for (size_t i = 0; i < points.size(); ++i) {
|
||||
// t goes from 0 to 1 across the points, used for fading effect
|
||||
const float t = (points.size() <= 1) ? 0.0f : (float)i / (points.size() - 1);
|
||||
Color c = base;
|
||||
float alphaScale = 1.0f - t;
|
||||
|
||||
// multiply alpha to make it fade towards the end
|
||||
c.a = (unsigned char)((float)(base.a) * alphaScale);
|
||||
|
||||
DrawCircleV(points[i], pointRadius, c);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,25 @@
|
|||
|
||||
#include "Component.hpp"
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Placeholder for future trajectory projection behavior.
|
||||
* Predicts and renders the probe's near-future trajectory.
|
||||
*/
|
||||
struct ProjectionComponent : public Component {
|
||||
std::vector<Vector2> points;
|
||||
int steps = 30;
|
||||
float stepDt = 1.0f / 60.0f;
|
||||
float pointRadius = 2.4f;
|
||||
bool highlightActive = false;
|
||||
Color inactiveColor = Color{125, 146, 178, 120};
|
||||
Color activeColor = Color{160, 206, 255, 220};
|
||||
|
||||
void Setup() override;
|
||||
void Update(float dt) override;
|
||||
void Cleanup() override;
|
||||
|
||||
void Draw() const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,94 @@
|
|||
#include "components/TrailComponent.hpp"
|
||||
|
||||
void TrailComponent::Setup() {}
|
||||
void TrailComponent::Update(float) {}
|
||||
void TrailComponent::Cleanup() {}
|
||||
#include "Entity.hpp"
|
||||
#include "components/TransformComponent.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void TrailComponent::Setup() {
|
||||
points.clear();
|
||||
elapsed = 0.0f;
|
||||
lastSampleAt = 0.0f;
|
||||
|
||||
auto transform = entity->GetComponent<TransformComponent>();
|
||||
if (!transform) {
|
||||
return;
|
||||
}
|
||||
|
||||
points.push_back({transform->get().x, transform->get().y, elapsed});
|
||||
}
|
||||
|
||||
void TrailComponent::Update(float dt) {
|
||||
elapsed += dt;
|
||||
|
||||
auto transform = entity->GetComponent<TransformComponent>();
|
||||
if (!transform) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!points.empty() && (elapsed - points.front().createdAt) > pointLifetime) {
|
||||
points.pop_front();
|
||||
}
|
||||
|
||||
const float x = transform->get().x;
|
||||
const float y = transform->get().y;
|
||||
if (points.empty()) {
|
||||
points.push_back({x, y, elapsed});
|
||||
lastSampleAt = elapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
const Point &last = points.back();
|
||||
const float dx = x - last.x;
|
||||
const float dy = y - last.y;
|
||||
|
||||
// check if we've moved far enough from the last point or if enough time has passed to warrant a
|
||||
// new sample, for smoother trails when the probe is moving slowly
|
||||
const bool movedEnough = (dx * dx + dy * dy) >= (sampleDistance * sampleDistance);
|
||||
const bool waitedEnough = (elapsed - lastSampleAt) >= sampleInterval;
|
||||
if (movedEnough || waitedEnough) {
|
||||
points.push_back({x, y, elapsed});
|
||||
lastSampleAt = elapsed;
|
||||
} else {
|
||||
points.back().x = x;
|
||||
points.back().y = y;
|
||||
points.back().createdAt = elapsed;
|
||||
}
|
||||
|
||||
if (points.size() > maxPoints) {
|
||||
while (points.size() > maxPoints) {
|
||||
points.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
Draw();
|
||||
}
|
||||
|
||||
void TrailComponent::Cleanup() {
|
||||
points.clear();
|
||||
elapsed = 0.0f;
|
||||
lastSampleAt = 0.0f;
|
||||
}
|
||||
|
||||
void TrailComponent::Draw() const {
|
||||
if (points.size() < 2 || pointLifetime <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < points.size(); ++i) {
|
||||
const Point &a = points[i - 1];
|
||||
const Point &b = points[i];
|
||||
|
||||
const float ageA = elapsed - a.createdAt;
|
||||
const float ageB = elapsed - b.createdAt;
|
||||
const float lifeNormA = std::clamp(1.0f - (ageA / pointLifetime), 0.0f, 1.0f);
|
||||
const float lifeNormB = std::clamp(1.0f - (ageB / pointLifetime), 0.0f, 1.0f);
|
||||
const float lifeNorm = 0.5f * (lifeNormA + lifeNormB);
|
||||
|
||||
Color c = color;
|
||||
c.a = (unsigned char)((float)(color.a) * lifeNorm);
|
||||
|
||||
DrawLineEx({a.x, a.y}, {b.x, b.y}, lineWidth, c);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,37 @@
|
|||
|
||||
#include "Component.hpp"
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
|
||||
/**
|
||||
* Placeholder for future probe trail visualization.
|
||||
* Stores and renders a fading positional trail for moving entities.
|
||||
*
|
||||
* Points are timestamped and retired from the front of a queue once they
|
||||
* exceed `pointLifetime`, so updates avoid touching every point each frame.
|
||||
*/
|
||||
struct TrailComponent : public Component {
|
||||
struct Point {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float createdAt = 0.0f;
|
||||
};
|
||||
|
||||
std::deque<Point> points;
|
||||
float elapsed = 0.0f;
|
||||
float lastSampleAt = 0.0f;
|
||||
float sampleDistance = 5.0f;
|
||||
float sampleInterval = 1.0f / 30.0f;
|
||||
float pointLifetime = 0.85f;
|
||||
float lineWidth = 2.0f;
|
||||
std::size_t maxPoints = 96;
|
||||
Color color = Color{120, 210, 255, 180};
|
||||
|
||||
void Setup() override;
|
||||
void Update(float dt) override;
|
||||
void Cleanup() override;
|
||||
|
||||
void Draw() const;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue