diff --git a/as6/Entities.cpp b/as6/Entities.cpp index 6ea1bc5..07fc992 100644 --- a/as6/Entities.cpp +++ b/as6/Entities.cpp @@ -70,6 +70,43 @@ std::shared_ptr CreateGravityWell() { return e; } +std::shared_ptr CreateBlackHole(float x, float y) { + auto e = std::make_shared(); + auto &transform = e->AddComponent(); + transform.x = x; + transform.y = y; + + auto &scrollable = e->AddComponent(); + scrollable.worldX = x; + + auto &well = e->AddComponent(); + well.mass = static_cast(1 << 21); + well.minDist = 24.0f; + well.controlledByMouse = false; + well.alwaysActive = true; + + auto &collider = e->AddComponent(); + collider.radius = 18.0f; + + e->AddComponent(); + + auto &render = e->AddComponent(); + render.draw = [e]() { + auto transform = e->GetComponent(); + if (!transform) { + return; + } + + const int cx = static_cast(transform->get().x); + const int cy = static_cast(transform->get().y); + DrawCircle(cx, cy, 18.0f, Color{8, 10, 20, 255}); + DrawCircleLines(cx, cy, 18.0f, Color{70, 95, 170, 240}); + DrawCircleLines(cx, cy, 24.0f, Color{95, 120, 200, 120}); + }; + + return e; +} + std::shared_ptr CreateStar(float x, float y) { auto e = std::make_shared(); auto &t = e->AddComponent(); diff --git a/as6/Entities.hpp b/as6/Entities.hpp index 22d411f..dfa6a6b 100644 --- a/as6/Entities.hpp +++ b/as6/Entities.hpp @@ -6,6 +6,7 @@ std::shared_ptr CreateProbe(); std::shared_ptr CreateGravityWell(); +std::shared_ptr CreateBlackHole(float x, float y); std::shared_ptr CreateStar(float x, float y); std::shared_ptr CreateAsteroid(float x, float y); std::shared_ptr CreateDebris(float x, float y, float vx, float vy, float ttl = 1.25f); diff --git a/as6/components/GravityWellComponent.cpp b/as6/components/GravityWellComponent.cpp index f7b2691..ff77da1 100644 --- a/as6/components/GravityWellComponent.cpp +++ b/as6/components/GravityWellComponent.cpp @@ -15,6 +15,11 @@ void GravityWellComponent::Update(float dt) { return; } + if (!controlledByMouse) { + active = alwaysActive; + return; + } + active = IsMouseButtonDown(MOUSE_BUTTON_LEFT); const Vector2 mouse = GetMousePosition(); diff --git a/as6/components/GravityWellComponent.hpp b/as6/components/GravityWellComponent.hpp index c1746b1..8199496 100644 --- a/as6/components/GravityWellComponent.hpp +++ b/as6/components/GravityWellComponent.hpp @@ -23,6 +23,16 @@ struct GravityWellComponent : public Component { */ bool active = false; + /** + * If true, active state is driven by left mouse button and this well follows the cursor. + */ + bool controlledByMouse = true; + + /** + * If true and not mouse-controlled, this well applies gravity every frame. + */ + bool alwaysActive = false; + /** * Lerp factor for how quickly the gravity well follows the mouse position. */ diff --git a/as6/components/PhysicsComponent.cpp b/as6/components/PhysicsComponent.cpp index 290b25c..b6eeaa6 100644 --- a/as6/components/PhysicsComponent.cpp +++ b/as6/components/PhysicsComponent.cpp @@ -67,30 +67,20 @@ bool PhysicsComponent::IsInsideNullZone(double xPos) const { return false; } -bool PhysicsComponent::ComputeWellDeltaVelocity(double px, double py, double dt, double &dvx, - double &dvy, bool ignoreWellActive) const { +bool PhysicsComponent::ComputeGravityDeltaVelocity(double px, double py, double dt, double &dvx, + double &dvy, bool ignoreWellActive) const { dvx = 0.0; dvy = 0.0; - if (!context || !context->wellEntity) { + if (!context || !context->entities) { return false; } - auto wellTransform = context->wellEntity->GetComponent(); - auto wellGravity = context->wellEntity->GetComponent(); - if (!wellTransform || !wellGravity) { - return false; - } - - if (!ignoreWellActive && !wellGravity->get().active) { - return false; - } - - // Probe cannot receive well acceleration when meter is depleted. + bool probeMeterDepleted = false; if (context && entity == context->probeEntity && context->statsEntity) { auto stats = context->statsEntity->GetComponent(); if (stats && stats->get().value <= 0.0f) { - return false; + probeMeterDepleted = true; } } @@ -98,25 +88,49 @@ bool PhysicsComponent::ComputeWellDeltaVelocity(double px, double py, double dt, return false; } - const double dx = static_cast(wellTransform->get().x) - px; - const double dy = static_cast(wellTransform->get().y) - py; - const double dist = std::sqrt(dx * dx + dy * dy); - if (dist <= 0.0001) { - return false; + bool applied = false; + for (auto &other : *context->entities) { + if (!other || other.get() == entity) { + continue; + } + + auto wellTransform = other->GetComponent(); + auto wellGravity = other->GetComponent(); + if (!wellTransform || !wellGravity) { + continue; + } + + if (probeMeterDepleted && wellGravity->get().controlledByMouse) { + continue; + } + + if (!ignoreWellActive && !wellGravity->get().active) { + continue; + } + + const double dx = static_cast(wellTransform->get().x) - px; + const double dy = static_cast(wellTransform->get().y) - py; + const double dist = std::sqrt(dx * dx + dy * dy); + if (dist <= 0.0001) { + continue; + } + + const double clampedDist = std::max(dist, static_cast(wellGravity->get().minDist)); + const double force = + static_cast(wellGravity->get().mass) / (clampedDist * clampedDist); + dvx += (dx / dist) * force * dt; + dvy += (dy / dist) * force * dt; + applied = true; } - const double clampedDist = std::max(dist, static_cast(wellGravity->get().minDist)); - const double force = static_cast(wellGravity->get().mass) / (clampedDist * clampedDist); - dvx = (dx / dist) * force * dt; - dvy = (dy / dist) * force * dt; - return true; + return applied; } 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); + ComputeGravityDeltaVelocity(xRef, yRef, dt, dvx, dvy, ignoreWellActive); vxRef += dvx; vyRef += dvy; diff --git a/as6/components/PhysicsComponent.hpp b/as6/components/PhysicsComponent.hpp index b8bd2cf..a792792 100644 --- a/as6/components/PhysicsComponent.hpp +++ b/as6/components/PhysicsComponent.hpp @@ -29,11 +29,11 @@ struct PhysicsComponent : public Component { bool IsInsideNullZone(double xPos) const; /** - * Computes velocity delta from well gravity for a position. - * Returns false when gravity should not apply. + * Computes summed velocity delta from all gravity sources for a position. + * Returns false when no gravity should apply. */ - bool ComputeWellDeltaVelocity(double px, double py, double dt, double &dvx, double &dvy, - bool ignoreWellActive = false) const; + bool ComputeGravityDeltaVelocity(double px, double py, double dt, double &dvx, double &dvy, + bool ignoreWellActive = false) const; /** * Performs one projected simulation step using well/null-zone rules, diff --git a/as6/components/SpawnComponent.cpp b/as6/components/SpawnComponent.cpp index d820348..12e425f 100644 --- a/as6/components/SpawnComponent.cpp +++ b/as6/components/SpawnComponent.cpp @@ -33,21 +33,26 @@ void SpawnComponent::Update(float) { while (cursorWX < spawnLimit) { const float r = static_cast(GetRandomValue(0, 99)); - if (r < 55.0f) { + if (r < 50.0f) { const float y = static_cast(GetRandomValue(48, GetScreenHeight() - 48)); auto star = CreateStar(cursorWX, y); star->SetContext(context); context->entities->push_back(star); - } else if (r < 88.0f) { + } else if (r < 78.0f) { const float y = static_cast(GetRandomValue(42, GetScreenHeight() - 42)); auto asteroid = CreateAsteroid(cursorWX, y); asteroid->SetContext(context); context->entities->push_back(asteroid); - } else { + } else if (r < 95.0f) { const float width = static_cast(GetRandomValue(70, 140)); auto zone = CreateNullZone(cursorWX, width); zone->SetContext(context); context->entities->push_back(zone); + } else { + const float y = static_cast(GetRandomValue(42, GetScreenHeight() - 42)); + auto blackHole = CreateBlackHole(cursorWX, y); + blackHole->SetContext(context); + context->entities->push_back(blackHole); } const float gap =