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 <vector>
25 : #include <cmath>
26 : #include <iostream>
27 :
28 : namespace simlab
29 : {
30 : class WreckingBallScenario : public IScenario
31 : {
32 : public:
33 6 : void Setup(ecs::World& world) override
34 : {
35 6 : m_renderer = std::make_unique<ascii::TextRenderer>(80, 30);
36 :
37 6 : physics::EnvironmentForces env;
38 6 : env.gravityY = -15.0f;
39 6 : env.drag = 0.01f;
40 :
41 6 : auto physicsSystem = std::make_unique<physics::PhysicsSystem>();
42 6 : physics::PhysicsSettings settings;
43 6 : settings.substeps = 16;
44 6 : settings.constraintIterations = 16; // High for stable chains
45 6 : physicsSystem->SetSettings(settings);
46 6 : physicsSystem->SetEnvironment(env);
47 6 : physicsSystem->SetJobSystem(&m_jobSystem);
48 6 : world.AddSystem(std::move(physicsSystem));
49 :
50 : // Floor
51 6 : auto floor = world.CreateEntity();
52 6 : world.AddComponent<physics::TransformComponent>(floor, 0.0f, -10.0f, 0.0f);
53 6 : auto& fb = world.AddComponent<physics::RigidBodyComponent>(floor);
54 6 : fb.mass = 0.0f; fb.invMass = 0.0f;
55 6 : world.AddComponent<physics::AABBComponent>(floor, -40.0f, -11.0f, 40.0f, -9.0f);
56 :
57 : // Wall of boxes
58 6 : float boxSize = 1.5f;
59 54 : for (int y = 0; y < 8; ++y)
60 : {
61 336 : for (int x = 0; x < 6; ++x)
62 : {
63 288 : auto box = world.CreateEntity();
64 288 : const float xPos = 5.0f + x * boxSize;
65 288 : const float yPos = -9.0f + y * boxSize + boxSize / 2;
66 288 : world.AddComponent<physics::TransformComponent>(box, xPos, yPos, 0.0f);
67 288 : auto& b = world.AddComponent<physics::RigidBodyComponent>(box);
68 288 : b.mass = 1.0f; b.invMass = 1.0f; b.friction = 0.6f;
69 288 : world.AddComponent<physics::AABBComponent>(
70 : box,
71 0 : xPos - boxSize / 2,
72 0 : yPos - boxSize / 2,
73 0 : xPos + boxSize / 2,
74 288 : yPos + boxSize / 2);
75 288 : physics::ConfigureBoxInertia(b, boxSize, boxSize);
76 : }
77 : }
78 :
79 : // Wrecking Ball Chain
80 6 : ecs::EntityId prevLink = world.CreateEntity(); // Anchor
81 6 : world.AddComponent<physics::TransformComponent>(prevLink, -10.0f, 10.0f, 0.0f);
82 6 : auto& ab = world.AddComponent<physics::RigidBodyComponent>(prevLink);
83 6 : ab.mass = 0.0f; ab.invMass = 0.0f;
84 :
85 6 : int chainLen = 10;
86 6 : float linkLen = 1.5f;
87 66 : for (int i = 0; i < chainLen; ++i)
88 : {
89 60 : auto link = world.CreateEntity();
90 60 : world.AddComponent<physics::TransformComponent>(link, -10.0f + (i+1)*1.0f, 10.0f - (i+1)*1.0f, 0.0f); // Initial swing pos
91 60 : auto& lb = world.AddComponent<physics::RigidBodyComponent>(link);
92 :
93 60 : if (i == chainLen - 1) {
94 : // Heavy Ball
95 6 : lb.mass = 50.0f; lb.invMass = 1.0f/50.0f;
96 6 : world.AddComponent<physics::CircleColliderComponent>(link, 2.0f);
97 6 : physics::ConfigureCircleInertia(lb, 2.0f);
98 : } else {
99 : // Chain link
100 54 : lb.mass = 0.5f; lb.invMass = 2.0f;
101 54 : world.AddComponent<physics::CircleColliderComponent>(link, 0.2f);
102 : }
103 :
104 60 : auto& joint = world.AddComponent<physics::DistanceJointComponent>(link);
105 60 : joint.entityA = prevLink;
106 60 : joint.entityB = link;
107 60 : joint.targetDistance = linkLen;
108 60 : joint.compliance = 0.0f; // Rigid chain
109 :
110 60 : prevLink = link;
111 : }
112 6 : }
113 :
114 544 : void Update(ecs::World& world, float dt) override
115 : {
116 : (void)world;
117 : (void)dt;
118 544 : }
119 :
120 183 : void Render(ecs::World& world, std::ostream& out) override
121 : {
122 183 : m_renderer->Clear();
123 :
124 : // Draw Floor
125 14823 : for(int x=0; x<80; ++x) m_renderer->Put(x, 29, '#');
126 :
127 : // Draw Entities
128 183 : world.ForEach<physics::TransformComponent>([&](ecs::EntityId id, physics::TransformComponent& t) {
129 : // Map -20..20 to 0..80
130 11081 : int sx = static_cast<int>((t.x + 20.0f) * 2.0f);
131 11081 : int sy = static_cast<int>(30.0f - (t.y + 10.0f));
132 :
133 11081 : if (sx >= 0 && sx < 80 && sy >= 0 && sy < 30)
134 : {
135 10688 : char c = '*';
136 10688 : if (world.GetComponent<physics::AABBComponent>(id)) c = '#'; // Box
137 10688 : if (world.GetComponent<physics::DistanceJointComponent>(id)) c = '.'; // Chain
138 10688 : if (auto* rb = world.GetComponent<physics::RigidBodyComponent>(id)) {
139 10688 : if (rb->mass > 10.0f) c = 'O'; // Wrecking ball
140 : }
141 10688 : m_renderer->Put(sx, sy, c);
142 : }
143 11081 : });
144 :
145 183 : m_renderer->SetHeadless(simlab::IsHeadlessRendering());
146 183 : m_renderer->PresentDiff(out);
147 183 : }
148 :
149 : private:
150 : std::unique_ptr<ascii::TextRenderer> m_renderer;
151 : jobs::JobSystem m_jobSystem;
152 : };
153 :
154 8 : std::unique_ptr<IScenario> CreateWreckingBallScenario()
155 : {
156 8 : return std::make_unique<WreckingBallScenario>();
157 : }
158 : }
|