65 lines
2.3 KiB
C++
65 lines
2.3 KiB
C++
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
module Basic.World;
|
||
|
|
import std;
|
||
|
|
import Basic.DebugShim;
|
||
|
|
namespace Debug = Crafter::Debug;
|
||
|
|
|
||
|
|
void Basic::World::Reset() {
|
||
|
|
Clear();
|
||
|
|
constexpr std::array<float, 5> heights = { 1, 3, 8, 20, 60 };
|
||
|
|
for (std::size_t i = 0; i < heights.size(); ++i) {
|
||
|
|
Spawn(static_cast<float>(i) - 2.0f, heights[i], 0, 0.1f);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void Basic::World::Clear() {
|
||
|
|
balls.clear();
|
||
|
|
time = 0;
|
||
|
|
fastest = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::int32_t Basic::World::Spawn(float x, float y, float vy, float radius) {
|
||
|
|
const std::int32_t id = nextId++;
|
||
|
|
balls.push_back({ .id = id, .x = x, .y = y, .vy = vy, .radius = radius });
|
||
|
|
Debug::Log("world", "ball {} dropped from {:.1f} m", id, y);
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Basic::World::Tick(float dt) {
|
||
|
|
const std::int32_t steps = std::max(1, physics.substeps);
|
||
|
|
const float h = dt / static_cast<float>(steps);
|
||
|
|
const float bottom = floor.top - floor.thickness;
|
||
|
|
fastest = 0;
|
||
|
|
for (Ball& ball : balls) {
|
||
|
|
for (std::int32_t step = 0; step < steps; ++step) {
|
||
|
|
ball.vy += physics.gravity * h;
|
||
|
|
ball.x += ball.vx * h;
|
||
|
|
ball.y += ball.vy * h;
|
||
|
|
if (ball.lost) continue;
|
||
|
|
// A falling ball that overlaps the slab bounces off its top.
|
||
|
|
if (ball.vy < 0 && ball.y - ball.radius < floor.top && ball.y + ball.radius > bottom) {
|
||
|
|
const float speed = -ball.vy;
|
||
|
|
ball.y = floor.top + ball.radius;
|
||
|
|
ball.vy = speed * physics.restitution;
|
||
|
|
if (speed > 1) Debug::Emit("Bounce", BounceEvent{ .ball = ball.id, .speed = speed });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!ball.lost && !Debug::Check(ball.y + ball.radius >= bottom, "physics", "ball {} is below the floor: y {:.2f} m, vy {:.1f} m/s", ball.id, ball.y, ball.vy)) {
|
||
|
|
ball.lost = true;
|
||
|
|
}
|
||
|
|
fastest = std::max(fastest, std::hypot(ball.vx, ball.vy));
|
||
|
|
}
|
||
|
|
Debug::Sample("solver.fastest", fastest);
|
||
|
|
time += dt;
|
||
|
|
}
|
||
|
|
|
||
|
|
const Basic::Ball* Basic::World::Find(std::int32_t id) const {
|
||
|
|
const auto found = std::ranges::find(balls, id, &Ball::id);
|
||
|
|
return found == balls.end() ? nullptr : &*found;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::int32_t Basic::World::Lost() const {
|
||
|
|
return static_cast<std::int32_t>(std::ranges::count(balls, true, &Ball::lost));
|
||
|
|
}
|