From 3989da924c4e33c52f500aead5ae62bb40294781 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sun, 10 Dec 2017 14:07:18 +0100 Subject: [PATCH] allow clicking celestial bodies --- src/app/MasterState.hpp | 2 + src/app/states.cpp | 42 +++++++-- src/blobs.cpp | 4 + src/creature/Creature.hpp | 2 +- src/creature/creature.cpp | 7 +- src/math/geometry.cpp | 32 +++++++ src/math/geometry.hpp | 27 ++++++ src/ui/BodyPanel.hpp | 67 +++++++++++++ src/ui/string.hpp | 1 + src/ui/ui.cpp | 191 +++++++++++++++++++++++++++++++++++--- src/world/Body.hpp | 10 ++ src/world/world.cpp | 12 ++- 12 files changed, 366 insertions(+), 31 deletions(-) create mode 100644 src/ui/BodyPanel.hpp diff --git a/src/app/MasterState.hpp b/src/app/MasterState.hpp index 8290e86..7521d8e 100644 --- a/src/app/MasterState.hpp +++ b/src/app/MasterState.hpp @@ -5,6 +5,7 @@ #include "Assets.hpp" #include "../graphics/Camera.hpp" +#include "../ui/BodyPanel.hpp" #include "../ui/CreaturePanel.hpp" #include "../ui/RecordsPanel.hpp" #include "../ui/TimePanel.hpp" @@ -68,6 +69,7 @@ private: glm::dvec3 cam_orient; bool cam_dragging; + ui::BodyPanel bp; ui::CreaturePanel cp; ui::RecordsPanel rp; ui::TimePanel tp; diff --git a/src/app/states.cpp b/src/app/states.cpp index bec6da1..98add96 100644 --- a/src/app/states.cpp +++ b/src/app/states.cpp @@ -11,6 +11,9 @@ #include +#include +#include + namespace blobs { namespace app { @@ -24,15 +27,17 @@ MasterState::MasterState(Assets &assets, world::Simulation &sim) noexcept , cam_tgt_dist(5.0) , cam_orient(PI * 0.375, PI * 0.25, 0.0) , cam_dragging(false) +, bp(assets) , cp(assets) , rp(sim) , tp(sim) , remain(0) , thirds(0) , paused(false) { - cp.ZIndex(10.0f); - rp.ZIndex(20.0f); - tp.ZIndex(30.0f); + bp.ZIndex(10.0f); + cp.ZIndex(20.0f); + rp.ZIndex(30.0f); + tp.ZIndex(40.0f); } MasterState::~MasterState() noexcept { @@ -108,21 +113,39 @@ void MasterState::OnMouseUp(const SDL_MouseButtonEvent &e) { if (e.button == SDL_BUTTON_LEFT) { glm::dmat4 inverse(glm::inverse(cam.Projection() * cam.View())); math::Ray ray(inverse * App().GetViewport().ShootPixel(e.x, e.y)); - creature::Creature *closest = nullptr; - double closest_dist = 1.0e24; + + creature::Creature *closest_creature = nullptr; + double closest_dist = std::numeric_limits::infinity(); for (creature::Creature *c : sim.LiveCreatures()) { glm::dvec3 normal(0.0); double dist = 0.0; - if (Intersect(ray, c->CollisionBox(), glm::dmat4(cam.Model(c->GetSituation().GetPlanet())) * c->CollisionTransform(), normal, dist) + if (Intersect(ray, c->CollisionBounds(), glm::dmat4(cam.Model(c->GetSituation().GetPlanet())) * c->CollisionTransform(), normal, dist) && dist < closest_dist) { - closest = c; + closest_creature = c; + closest_dist = dist; + } + } + + world::Body *closest_body = nullptr; + for (world::Body *b : sim.Bodies()) { + glm::dvec3 normal(0.0); + double dist = 0.0; + if (Intersect(ray, glm::dmat4(cam.Model(*b)) * b->CollisionBounds(), normal, dist) && dist < closest_dist) { + closest_creature = nullptr; + closest_body = b; closest_dist = dist; } } - if (closest) { - cp.Show(*closest); + + if (closest_creature) { + cp.Show(*closest_creature); + bp.Hide(); + } else if (closest_body) { + bp.Show(*closest_body); + cp.Hide(); } else { cp.Hide(); + bp.Hide(); } } else if (e.button == SDL_BUTTON_RIGHT) { SDL_SetRelativeMouseMode(SDL_FALSE); @@ -223,6 +246,7 @@ void MasterState::OnRender(graphics::Viewport &viewport) { } viewport.ClearDepth(); + bp.Draw(viewport); cp.Draw(viewport); rp.Draw(viewport); tp.Draw(viewport); diff --git a/src/blobs.cpp b/src/blobs.cpp index 1cd3f9f..d032d9d 100644 --- a/src/blobs.cpp +++ b/src/blobs.cpp @@ -20,12 +20,14 @@ int main(int argc, char *argv[]) { app::Assets assets; world::Sun sun; + sun.Name("Sun"); sun.Mass(1.0e14); sun.Radius(20.0); sun.SurfaceTilt(glm::dvec2(PI * 0.25, PI * 0.25)); sun.AngularMomentum(1.0e13); world::Planet planet(25); + planet.Name("Planet"); planet.SetParent(sun); planet.Mass(1.0e10); planet.GetOrbit().SemiMajorAxis(941.7); @@ -34,6 +36,7 @@ int main(int argc, char *argv[]) { planet.AngularMomentum(6.0e10); world::Planet moon(3); + moon.Name("Moon"); moon.SetParent(planet); moon.Mass(1.0e6); moon.GetOrbit().SemiMajorAxis(37.0); @@ -41,6 +44,7 @@ int main(int argc, char *argv[]) { moon.AngularMomentum(1.0e4); world::Planet second_planet(9); + second_planet.Name("Second planet"); second_planet.SetParent(sun); second_planet.Mass(1.0e9); second_planet.GetOrbit().SemiMajorAxis(350.0); diff --git a/src/creature/Creature.hpp b/src/creature/Creature.hpp index 2c0b6b8..b8171ed 100644 --- a/src/creature/Creature.hpp +++ b/src/creature/Creature.hpp @@ -177,7 +177,7 @@ public: Steering &GetSteering() noexcept { return steering; } const Steering &GetSteering() const noexcept { return steering; } - math::AABB CollisionBox() const noexcept; + math::AABB CollisionBounds() const noexcept; glm::dmat4 CollisionTransform() const noexcept; glm::dmat4 LocalTransform() noexcept; diff --git a/src/creature/creature.cpp b/src/creature/creature.cpp index b609fe8..085d70f 100644 --- a/src/creature/creature.cpp +++ b/src/creature/creature.cpp @@ -472,14 +472,13 @@ Situation::Derivative Creature::Step(const Situation::Derivative &ds, double dt) s.vel += ds.acc * dt; glm::dvec3 force(steering.Force(s)); // gravity = antinormal * mass * Gm / r² - double elevation = situation.GetPlanet().DistanceAt(s.pos); glm::dvec3 normal(situation.GetPlanet().NormalAt(s.pos)); force += glm::dvec3( -normal * Mass() * situation.GetPlanet().GravitationalParameter() - / (elevation * elevation)); + / glm::length2(s.pos)); // if net force is applied and in contact with surface - if (!allzero(force) && std::abs(std::abs(elevation) - situation.GetPlanet().Radius()) < 0.001) { + if (!allzero(force) && glm::length2(s.pos) < (situation.GetPlanet().Radius() + 0.01) * (situation.GetPlanet().Radius() + 0.01)) { // apply friction = -|normal force| * tangential force * coefficient glm::dvec3 fn(normal * glm::dot(force, normal)); glm::dvec3 ft(force - fn); @@ -550,7 +549,7 @@ void Creature::TickBrain(double dt) { } } -math::AABB Creature::CollisionBox() const noexcept { +math::AABB Creature::CollisionBounds() const noexcept { return { glm::dvec3(size * -0.5), glm::dvec3(size * 0.5) }; } diff --git a/src/math/geometry.cpp b/src/math/geometry.cpp index 58c97ae..3afa944 100644 --- a/src/math/geometry.cpp +++ b/src/math/geometry.cpp @@ -148,5 +148,37 @@ bool Intersect( return true; } +bool Intersect( + const Ray &r, + const Sphere &s, + glm::dvec3 &normal, + double &dist +) noexcept { + const glm::dvec3 diff(s.origin - r.Origin()); + if (glm::dot(diff, r.Direction()) < 0.0) { + if (glm::length2(diff) > s.radius * s.radius) return false; + if (std::abs(glm::length2(diff) - s.radius * s.radius) < std::numeric_limits::epsilon() * s.radius) { + normal = glm::normalize(-diff); + dist = 0.0; + return true; + } + const glm::dvec3 pc(r.Direction() * glm::dot(r.Direction(), diff) + r.Origin()); + double idist = std::sqrt(s.radius * s.radius - glm::length2(pc - s.origin)); + dist = idist - glm::length(pc - r.Origin()); + normal = glm::normalize((r.Origin() + (r.Direction() * dist)) - s.origin); + return true; + } + const glm::dvec3 pc(r.Direction() * glm::dot(r.Direction(), diff) + r.Origin()); + if (glm::length2(s.origin - pc) > s.radius * s.radius) return false; + double idist = std::sqrt(s.radius * s.radius - glm::length2(pc - s.origin)); + if (glm::length2(diff) > s.radius * s.radius) { + dist = glm::length(pc - r.Origin()) - idist; + } else { + dist = glm::length(pc - r.Origin()) + idist; + } + normal = glm::normalize((r.Origin() + (r.Direction() * dist)) - s.origin); + return true; +} + } } diff --git a/src/math/geometry.hpp b/src/math/geometry.hpp index 8f06284..b4a9f50 100644 --- a/src/math/geometry.hpp +++ b/src/math/geometry.hpp @@ -40,6 +40,7 @@ bool Intersect( glm::dvec3 &normal, double &depth) noexcept; + class Ray { public: @@ -77,6 +78,32 @@ bool Intersect( glm::dvec3 &normal, double &dist) noexcept; + +struct Sphere { + + glm::dvec3 origin; + double radius; + +}; + +/// matrix may scale, but only uniformly +inline Sphere operator *(const glm::dmat4 &m, const Sphere &s) noexcept { + glm::dvec4 o(m * glm::dvec4(s.origin, 1.0)); + glm::dvec4 p(m * glm::dvec4(s.origin + glm::dvec3(s.radius, 0.0, 0.0), 1.0)); + return Sphere{glm::dvec3(o) / o.w, glm::length((glm::dvec3(p) / p.w) - (glm::dvec3(o) / o.w))}; +} + +inline std::ostream &operator <<(std::ostream &out, const Sphere &s) { + return out << "Sphere(" << s.origin << ", " << s.radius << ")"; +} + +/// oriented ray/sphere intersection test +bool Intersect( + const Ray &, + const Sphere &, + glm::dvec3 &normal, + double &dist) noexcept; + } } diff --git a/src/ui/BodyPanel.hpp b/src/ui/BodyPanel.hpp new file mode 100644 index 0000000..ef6d51f --- /dev/null +++ b/src/ui/BodyPanel.hpp @@ -0,0 +1,67 @@ +#ifndef BLOBS_UI_BODYPANEL_HPP_ +#define BLOBS_UI_BODYPANEL_HPP_ + +#include "Panel.hpp" + +namespace blobs { +namespace app { + struct Assets; +} +namespace graphics { + class Viewport; +} +namespace world { + class Body; +} +namespace ui { + +class Label; + +class BodyPanel { + +public: + explicit BodyPanel(app::Assets &); + ~BodyPanel(); + + BodyPanel(const BodyPanel &) = delete; + BodyPanel &operator =(const BodyPanel &) = delete; + + BodyPanel(BodyPanel &&) = delete; + BodyPanel &operator =(BodyPanel &&) = delete; + +public: + void Show(world::Body &); + void Hide() noexcept; + + bool Shown() const noexcept { return body; } + const world::Body &GetBody() const noexcept { return *body; } + + void ZIndex(float z) noexcept { panel.ZIndex(z); } + + void Draw(graphics::Viewport &) noexcept; + +private: + app::Assets &assets; + world::Body *body; + + Label *name; + Label *mass; + Label *radius; + Label *soi; + Label *operiod; + Label *rperiod; + Label *atm; + Label *sma; + Label *ecc; + Label *inc; + Label *asc; + Label *arg; + Label *mna; + Panel panel; + +}; + +} +} + +#endif diff --git a/src/ui/string.hpp b/src/ui/string.hpp index ff20ec1..5397c80 100644 --- a/src/ui/string.hpp +++ b/src/ui/string.hpp @@ -8,6 +8,7 @@ namespace blobs { namespace ui { +std::string AngleString(double a); std::string DecimalString(double n, int p); std::string LengthString(double m); std::string MassString(double kg); diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 126e307..35242d3 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -1,3 +1,4 @@ +#include "BodyPanel.hpp" #include "CreaturePanel.hpp" #include "RecordsPanel.hpp" #include "string.hpp" @@ -8,6 +9,7 @@ #include "../app/Assets.hpp" #include "../creature/Creature.hpp" #include "../graphics/Viewport.hpp" +#include "../math/const.hpp" #include "../world/Body.hpp" #include "../world/Simulation.hpp" @@ -285,6 +287,127 @@ void CreaturePanel::Draw(graphics::Viewport &viewport) noexcept { } +BodyPanel::BodyPanel(app::Assets &assets) +: assets(assets) +, body(nullptr) +, name(new Label(assets.fonts.large)) +, mass(new Label(assets.fonts.medium)) +, radius(new Label(assets.fonts.medium)) +, soi(new Label(assets.fonts.medium)) +, operiod(new Label(assets.fonts.medium)) +, rperiod(new Label(assets.fonts.medium)) +, atm(new Label(assets.fonts.medium)) +, sma(new Label(assets.fonts.medium)) +, ecc(new Label(assets.fonts.medium)) +, inc(new Label(assets.fonts.medium)) +, asc(new Label(assets.fonts.medium)) +, arg(new Label(assets.fonts.medium)) +, mna(new Label(assets.fonts.medium)) +, panel() { + Label *mass_label = new Label(assets.fonts.medium); + mass_label->Text("Mass"); + Label *radius_label = new Label(assets.fonts.medium); + radius_label->Text("Radius"); + Label *soi_label = new Label(assets.fonts.medium); + soi_label->Text("SOI"); + Label *operiod_label = new Label(assets.fonts.medium); + operiod_label->Text("Orb. period"); + Label *rperiod_label = new Label(assets.fonts.medium); + rperiod_label->Text("Rot. period"); + Label *atm_label = new Label(assets.fonts.medium); + atm_label->Text("Atmosphere"); + Label *sma_label = new Label(assets.fonts.medium); + sma_label->Text("SMA"); + Label *ecc_label = new Label(assets.fonts.medium); + ecc_label->Text("Eccentricity"); + Label *inc_label = new Label(assets.fonts.medium); + inc_label->Text("Inclination"); + Label *asc_label = new Label(assets.fonts.medium); + asc_label->Text("Asc. node"); + Label *arg_label = new Label(assets.fonts.medium); + arg_label->Text("Arg. Periapsis"); + Label *mna_label = new Label(assets.fonts.medium); + mna_label->Text("Mean anomaly"); + Panel *label_panel = new Panel; + label_panel + ->Direction(Panel::VERTICAL) + ->Add(mass_label) + ->Add(radius_label) + ->Add(soi_label) + ->Add(operiod_label) + ->Add(rperiod_label) + ->Add(atm_label) + ->Add(sma_label) + ->Add(ecc_label) + ->Add(inc_label) + ->Add(asc_label) + ->Add(arg_label) + ->Add(mna_label); + Panel *value_panel = new Panel; + value_panel + ->Direction(Panel::VERTICAL) + ->Add(mass) + ->Add(radius) + ->Add(soi) + ->Add(operiod) + ->Add(rperiod) + ->Add(atm) + ->Add(sma) + ->Add(ecc) + ->Add(inc) + ->Add(asc) + ->Add(arg) + ->Add(mna); + Panel *info_panel = new Panel; + info_panel + ->Direction(Panel::HORIZONTAL) + ->Spacing(10.0f) + ->Add(label_panel) + ->Add(value_panel); + + panel + .Add(name) + ->Add(info_panel) + ->Padding(glm::vec2(10.0f)) + ->Spacing(10.0f) + ->Direction(Panel::VERTICAL) + ->Background(glm::vec4(1.0f, 1.0f, 1.0f, 0.7f)); +} + +BodyPanel::~BodyPanel() { +} + +void BodyPanel::Show(world::Body &b) { + body = &b; + name->Text(b.Name()); + mass->Text(MassString(b.Mass())); + radius->Text(LengthString(b.Radius())); + soi->Text(LengthString(b.SphereOfInfluence())); + operiod->Text(TimeString(b.OrbitalPeriod())); + rperiod->Text(TimeString(b.RotationalPeriod())); + atm->Text(b.HasAtmosphere() ? assets.data.resources[b.Atmosphere()].label : std::string("none")); + sma->Text(LengthString(b.GetOrbit().SemiMajorAxis())); + ecc->Text(DecimalString(b.GetOrbit().Eccentricity(), 3)); + inc->Text(AngleString(b.GetOrbit().Inclination())); + asc->Text(AngleString(b.GetOrbit().LongitudeAscending())); + arg->Text(AngleString(b.GetOrbit().ArgumentPeriapsis())); + mna->Text(AngleString(b.GetOrbit().MeanAnomaly())); +} + +void BodyPanel::Hide() noexcept { + body = nullptr; +} + +void BodyPanel::Draw(graphics::Viewport &viewport) noexcept { + if (!Shown()) return; + + const glm::vec2 margin(20.0f); + panel.Position(glm::vec2(viewport.Width() - margin.x - panel.Size().x, margin.y)); + panel.Layout(); + panel.Draw(assets, viewport); +} + + RecordsPanel::RecordsPanel(world::Simulation &sim) : sim(sim) , records() @@ -434,6 +557,29 @@ void TimePanel::Draw(graphics::Viewport &viewport) noexcept { } +namespace { +std::ostream &custom_fixed(std::ostream &out, double n, int d) { + double f = n; + int p = d; + while (f > 1.0 && p > 0) { + --p; + f *= 0.1; + } + if (p > 0) { + out << std::fixed << std::setprecision(p) << n; + } else { + out << std::defaultfloat << std::setprecision(d) << n; + } + return out; +} +} + +std::string AngleString(double a) { + std::stringstream s; + s << std::fixed << std::setprecision(2) << std::fmod(a * 180.0 * PI_inv, 360.0) << "°"; + return s.str(); +} + std::string DecimalString(double n, int p) { std::stringstream s; s << std::fixed << std::setprecision(p) << n; @@ -442,28 +588,38 @@ std::string DecimalString(double n, int p) { std::string LengthString(double m) { std::stringstream s; - s << std::fixed << std::setprecision(3); - if (m > 1500.0) { - s << (m * 0.001) << "km"; - } else if (m < 0.1) { - s << (m * 1000.0) << "mm"; + if (m > 1.5e9) { + custom_fixed(s, m * 1.0e-9, 4) << "Gm"; + } else if (m > 1.5e6) { + custom_fixed(s, m * 1.0e-6, 4) << "Mm"; + } else if (m > 1.5e3) { + custom_fixed(s, m * 1.0e-3, 4) << "km"; + } else if (m < 1.5e-3) { + custom_fixed(s, m * 1.0e3, 4) << "mm"; + } else if (m < 1.5) { + custom_fixed(s, m * 1.0e2, 4) << "cm"; } else { - s << m << "m"; + custom_fixed(s, m, 4) << "m"; } return s.str(); } std::string MassString(double kg) { std::stringstream s; - s << std::fixed << std::setprecision(3); - if (kg > 1500.0) { - s << (kg * 0.001) << "t"; + if (kg > 1.5e12) { + custom_fixed(s, kg * 1.0e-12, 4) << "Gt"; + } else if (kg > 1.5e9) { + custom_fixed(s, kg * 1.0e-9, 4) << "Mt"; + } else if (kg > 1.5e6) { + custom_fixed(s, kg * 1.0e-6, 4) << "kt"; + } else if (kg > 1.5e3) { + custom_fixed(s, kg * 1.0e-3, 4) << "t"; + } else if (kg < 1.0e-3) { + custom_fixed(s, kg * 1.0e6, 4) << "mg"; } else if (kg < 1.0) { - s << (kg * 1000.0) << "g"; - } else if (kg < 0.001) { - s << (kg * 1.0e6) << "mg"; + custom_fixed(s, kg * 1.0e3, 4) << "g"; } else { - s << kg << "kg"; + custom_fixed(s, kg, 4) << "kg"; } return s.str(); } @@ -480,16 +636,21 @@ std::string PercentageString(double n) { std::string TimeString(double s) { int is = int(s); + int md = 1; + int sd = 1; std::stringstream ss; + ss << std::setfill('0'); if (is >= 3600) { ss << (is / 3600) << "h "; is %= 3600; + md = 2; } if (is >= 60) { - ss << (is / 60) << "m "; + ss << std::setw(md) << (is / 60) << "m "; is %= 60; + sd = 2; } - ss << is << 's'; + ss << std::setw(sd) << is << 's'; return ss.str(); } diff --git a/src/world/Body.hpp b/src/world/Body.hpp index 165d848..d908065 100644 --- a/src/world/Body.hpp +++ b/src/world/Body.hpp @@ -2,8 +2,10 @@ #define BLOBS_WORLD_BODY_HPP_ #include "Orbit.hpp" +#include "../math/geometry.hpp" #include "../math/glm.hpp" +#include #include @@ -47,6 +49,9 @@ public: const std::vector &Children() const noexcept { return children; } + const std::string &Name() const noexcept { return name; } + void Name(const std::string &n) noexcept { name = n; } + double Mass() const noexcept { return mass; } void Mass(double m) noexcept { mass = m; } @@ -73,6 +78,10 @@ public: double GravitationalParameter() const noexcept; double OrbitalPeriod() const noexcept; double RotationalPeriod() const noexcept; + double SphereOfInfluence() const noexcept; + + math::Sphere CollisionBounds() const noexcept { return math::Sphere{ glm::dvec3(0.0), Radius() }; } + const glm::dmat4 &CollisionTransform() const noexcept { return local; } const glm::dmat4 &LocalTransform() const noexcept { return local; } const glm::dmat4 &InverseTransform() const noexcept { return inverse_local; } @@ -106,6 +115,7 @@ private: Simulation *sim; Body *parent; std::vector children; + std::string name; double mass; double radius; Orbit orbit; diff --git a/src/world/world.cpp b/src/world/world.cpp index 02c142a..c22434a 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -115,6 +115,14 @@ double Body::RotationalPeriod() const noexcept { } } +double Body::SphereOfInfluence() const noexcept { + if (HasParent()) { + return orbit.SemiMajorAxis() * std::pow(Mass() / Parent().Mass(), 2.0 / 5.0); + } else { + return std::numeric_limits::infinity(); + } +} + glm::dmat4 Body::ToUniverse() const noexcept { glm::dmat4 m(1.0); const Body *b = this; @@ -184,13 +192,13 @@ void Body::CheckCollision() noexcept { collisions.clear(); auto end = Creatures().end(); for (auto i = Creatures().begin(); i != end; ++i) { - math::AABB i_box((*i)->CollisionBox()); + math::AABB i_box((*i)->CollisionBounds()); glm::dmat4 i_mat((*i)->CollisionTransform()); for (auto j = (i + 1); j != end; ++j) { glm::dvec3 diff((*i)->GetSituation().Position() - (*j)->GetSituation().Position()); double max_dist = ((*i)->Size() + (*j)->Size()) * 1.74; if (glm::length2(diff) > max_dist * max_dist) continue; - math::AABB j_box((*j)->CollisionBox()); + math::AABB j_box((*j)->CollisionBounds()); glm::dmat4 j_mat((*j)->CollisionTransform()); glm::dvec3 normal; double depth; -- 2.39.2