Line data Source code
1 : /*
2 : * Copyright (C) 2025 aeml
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "simlab/Scenario.hpp"
19 : #include "ecs/World.hpp"
20 : #include "physics/Components.hpp"
21 : #include "physics/Systems.hpp"
22 : #include "jobs/JobSystem.hpp"
23 : #include "ascii/TextRenderer.hpp"
24 : #include "core/Logger.hpp"
25 : #include <vector>
26 : #include <cmath>
27 : #include <random>
28 : #include <iostream>
29 :
30 : namespace simlab
31 : {
32 : namespace
33 : {
34 : // Custom system for N-body gravity
35 : class PlanetaryGravitySystem : public ecs::ISystem
36 : {
37 : public:
38 560 : void Update(ecs::World& world, float dt) override
39 : {
40 : // Simple approach: Apply force towards center (0,0) for all dynamic bodies
41 : // Or true N-body if we want to be fancy, but O(N^2) is slow for many bodies without optimization.
42 : // Let's do a massive central attractor at (0,0).
43 :
44 560 : const float G = 100.0f;
45 560 : const float centerMass = 1000.0f;
46 :
47 560 : world.ForEach<physics::TransformComponent>([&](ecs::EntityId id, physics::TransformComponent& t) {
48 56560 : auto* body = world.GetComponent<physics::RigidBodyComponent>(id);
49 56560 : if (body && body->invMass > 0.0f)
50 : {
51 56000 : float dx = 0.0f - t.x;
52 56000 : float dy = 0.0f - t.y;
53 56000 : float distSq = dx*dx + dy*dy;
54 56000 : float dist = std::sqrt(distSq);
55 :
56 56000 : if (dist > 0.1f)
57 : {
58 56000 : float force = (G * centerMass * body->mass) / distSq;
59 56000 : float fx = (dx / dist) * force;
60 56000 : float fy = (dy / dist) * force;
61 :
62 : // Apply force (modify velocity directly for this simple integrator)
63 56000 : body->vx += (fx * body->invMass) * dt;
64 56000 : body->vy += (fy * body->invMass) * dt;
65 : }
66 : }
67 56560 : });
68 560 : }
69 : };
70 : }
71 :
72 : class PlanetaryGravityScenario : public IScenario
73 : {
74 : public:
75 18 : void Setup(ecs::World& world) override
76 : {
77 18 : m_renderer = std::make_unique<ascii::TextRenderer>(80, 40);
78 :
79 : // Physics setup
80 18 : physics::EnvironmentForces env;
81 18 : env.gravityY = 0.0f; // No global gravity, only radial
82 18 : env.drag = 0.0f; // Space has no drag
83 :
84 18 : auto physicsSystem = std::make_unique<physics::PhysicsSystem>();
85 18 : physics::PhysicsSettings settings;
86 18 : settings.substeps = 8;
87 18 : physicsSystem->SetSettings(settings);
88 18 : physicsSystem->SetEnvironment(env);
89 18 : physicsSystem->SetJobSystem(&m_jobSystem);
90 18 : world.AddSystem(std::move(physicsSystem));
91 :
92 : // Add custom gravity system
93 18 : world.AddSystem(std::make_unique<PlanetaryGravitySystem>());
94 :
95 : // Central Star (Static)
96 18 : auto star = world.CreateEntity();
97 18 : world.AddComponent<physics::TransformComponent>(star, 0.0f, 0.0f, 0.0f);
98 18 : auto& starBody = world.AddComponent<physics::RigidBodyComponent>(star);
99 18 : starBody.mass = 0.0f; // Static
100 18 : starBody.invMass = 0.0f;
101 18 : world.AddComponent<physics::CircleColliderComponent>(star, 2.0f);
102 :
103 : // Planets
104 18 : std::mt19937 rng(42);
105 18 : std::uniform_real_distribution<float> distAngle(0.0f, 6.28318f);
106 18 : std::uniform_real_distribution<float> distRadius(5.0f, 35.0f);
107 18 : std::uniform_real_distribution<float> distMass(0.5f, 2.0f);
108 :
109 1818 : for (int i = 0; i < 100; ++i)
110 : {
111 1800 : float angle = distAngle(rng);
112 1800 : float r = distRadius(rng);
113 1800 : float mass = distMass(rng);
114 :
115 1800 : float x = std::cos(angle) * r;
116 1800 : float y = std::sin(angle) * r;
117 :
118 : // Orbital velocity: v = sqrt(GM/r)
119 : // Perpendicular to radius
120 1800 : float v = std::sqrt(100.0f * 1000.0f / r);
121 1800 : float vx = -std::sin(angle) * v;
122 1800 : float vy = std::cos(angle) * v;
123 :
124 1800 : auto p = world.CreateEntity();
125 1800 : world.AddComponent<physics::TransformComponent>(p, x, y, 0.0f);
126 1800 : auto& body = world.AddComponent<physics::RigidBodyComponent>(p);
127 1800 : body.mass = mass;
128 1800 : body.invMass = 1.0f / mass;
129 1800 : body.vx = vx;
130 1800 : body.vy = vy;
131 1800 : body.restitution = 0.8f;
132 :
133 1800 : world.AddComponent<physics::CircleColliderComponent>(p, 0.5f);
134 1800 : physics::ConfigureCircleInertia(body, 0.5f);
135 : }
136 18 : }
137 :
138 565 : void Update(ecs::World& world, float dt) override
139 : {
140 : (void)world;
141 : (void)dt;
142 565 : }
143 :
144 204 : void Render(ecs::World& world, std::ostream& out) override
145 : {
146 204 : m_renderer->Clear();
147 :
148 : // Draw Star
149 204 : m_renderer->DrawCircle(40, 20, 4, '@');
150 :
151 : // Draw Planets
152 204 : world.ForEach<physics::TransformComponent>([&](ecs::EntityId, physics::TransformComponent& t) {
153 : // Map -40..40 to 0..80
154 20604 : int sx = static_cast<int>(t.x + 40.0f);
155 20604 : int sy = static_cast<int>(20.0f - t.y * 0.5f); // Aspect ratio correction roughly
156 :
157 20604 : if (sx >= 0 && sx < 80 && sy >= 0 && sy < 40)
158 : {
159 20604 : m_renderer->Put(sx, sy, 'o');
160 : }
161 20604 : });
162 :
163 204 : m_renderer->SetHeadless(simlab::IsHeadlessRendering());
164 204 : m_renderer->PresentDiff(out);
165 204 : }
166 :
167 : private:
168 : std::unique_ptr<ascii::TextRenderer> m_renderer;
169 : jobs::JobSystem m_jobSystem;
170 : };
171 :
172 23 : std::unique_ptr<IScenario> CreatePlanetaryGravityScenario()
173 : {
174 23 : return std::make_unique<PlanetaryGravityScenario>();
175 : }
176 : }
|