Compare commits
25 Commits
master
...
event-as-g
Author | SHA1 | Date | |
---|---|---|---|
3469024b23 | |||
8012ff40af | |||
c48d350f58 | |||
01cc102d88 | |||
c4012f2400 | |||
873703e7ce | |||
81b00b576b | |||
3a47b165f2 | |||
b2f98faa93 | |||
8a9845807e | |||
3f93a7729b | |||
56b4b139f1 | |||
10cc4e3b96 | |||
70af8da4a4 | |||
7b7501d61c | |||
2216222a8b | |||
35db808f20 | |||
ec4348b652 | |||
30f4712da8 | |||
09be5680a4 | |||
fb6a8a4df4 | |||
978eb648e2 | |||
ad8d62556e | |||
7505fcfac7 | |||
2d999847b7 |
|
@ -1 +0,0 @@
|
||||||
lib
|
|
|
@ -1,41 +0,0 @@
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: default
|
|
||||||
clone:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: prepare_env
|
|
||||||
image: crystallang/crystal:latest
|
|
||||||
environment:
|
|
||||||
commands:
|
|
||||||
- apt-get update && apt-get install -y libsfml-dev libimgui-dev 'libstdc++6' 'g++' curl
|
|
||||||
- curl https://git.sceptique.eu/Sceptique/ZeroEpsilon/raw/commit/$DRONE_COMMIT/shard.lock -o shard.lock
|
|
||||||
- curl https://git.sceptique.eu/Sceptique/ZeroEpsilon/raw/commit/$DRONE_COMMIT/shard.yml -o shard.yml
|
|
||||||
- shards install
|
|
||||||
- rm shard.yml shard.lock
|
|
||||||
# TODO: create an image of this and use it in clone
|
|
||||||
|
|
||||||
- name: clone
|
|
||||||
image: alpine/git
|
|
||||||
commands:
|
|
||||||
- git clone https://git.sceptique.eu/Sceptique/ZeroEpsilon.git ZeroEpsilon
|
|
||||||
- cd ZeroEpsilon
|
|
||||||
- git checkout $DRONE_COMMIT
|
|
||||||
# TODO: create an image of this and use it in test
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
image: crystallang/crystal:latest
|
|
||||||
environment:
|
|
||||||
commands:
|
|
||||||
- ls
|
|
||||||
- cd ZeroEpsilon
|
|
||||||
- ls
|
|
||||||
- crystal spec
|
|
||||||
|
|
||||||
# steps:
|
|
||||||
# - name: test
|
|
||||||
# image: sceptique/zero_epsilon:latest
|
|
||||||
# commands:
|
|
||||||
# - cd /ZeroEpsilon
|
|
||||||
# - crystal spec
|
|
22
Dockerfile
22
Dockerfile
|
@ -1,22 +0,0 @@
|
||||||
FROM crystallang/crystal:latest AS build
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsfml-dev libimgui-dev 'libstdc++6' 'g++' curl
|
|
||||||
|
|
||||||
# Add this directory to container as /app
|
|
||||||
RUN mkdir /ZeroEpsilon
|
|
||||||
ADD shard.yml shard.lock /ZeroEpsilon/
|
|
||||||
WORKDIR /ZeroEpsilon
|
|
||||||
|
|
||||||
# Install shards
|
|
||||||
RUN shards install
|
|
||||||
|
|
||||||
FROM build AS source
|
|
||||||
# Copy all sources
|
|
||||||
ADD spec /ZeroEpsilon/spec
|
|
||||||
ADD src /ZeroEpsilon/src
|
|
||||||
WORKDIR /ZeroEpsilon
|
|
||||||
|
|
||||||
FROM source AS test
|
|
||||||
# Run tests
|
|
||||||
RUN crystal spec --verbose
|
|
9
Makefile
9
Makefile
|
@ -4,15 +4,11 @@ include .env
|
||||||
all: deps_opt build
|
all: deps_opt build
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) ./$(NAME)
|
./$(NAME)
|
||||||
build:
|
build:
|
||||||
crystal build src/$(NAME).cr --stats --error-trace
|
crystal build src/$(NAME).cr --stats --error-trace
|
||||||
debug:
|
debug:
|
||||||
crystal build src/$(NAME).cr --stats --error-trace # todo add debug flag
|
crystal build src/$(NAME).cr --stats --error-trace # todo add debug flag
|
||||||
sandbox:
|
|
||||||
crystal build src/tests/physics_sandbox.cr --stats --error-trace
|
|
||||||
run-sandbox:
|
|
||||||
LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) crystal run src/tests/physics_sandbox.cr --stats --error-trace
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
crystal build src/$(NAME).cr --stats --release
|
crystal build src/$(NAME).cr --stats --release
|
||||||
|
@ -25,8 +21,7 @@ deps_update:
|
||||||
deps_opt:
|
deps_opt:
|
||||||
@[ -d lib/ ] || make deps
|
@[ -d lib/ ] || make deps
|
||||||
doc:
|
doc:
|
||||||
crystal docs ./src/zero_epsilon.cr ./src/tests/physics_sandbox.cr ./lib/crsfml/src/crsfml.cr ./lib/imgui/src/imgui.cr ./lib/imgui-sfml/src/imgui-sfml.cr
|
crystal docs ./src/zero_epsilon.cr ./src/tests/physics_sandbox.cr ./lib/crsfml/src/crsfml.cr ./lib/imgui/src/imgui.cr
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm $(NAME)
|
rm $(NAME)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# ZeroEpsilon
|
# ZeroEpsilon
|
||||||
|
|
||||||
[![Build Status](https://drone.sceptique.eu/api/badges/Sceptique/ZeroEpsilon/status.svg)](https://drone.sceptique.eu/Sceptique/ZeroEpsilon)
|
|
||||||
|
|
||||||
Inspired by <https://github.com/daid/EmptyEpsilon>
|
Inspired by <https://github.com/daid/EmptyEpsilon>
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
@ -30,8 +28,4 @@ Details on <https://sceptique.eu/about>.
|
||||||
If you already have an account on git.sceptique.eu you can contribute to the upstream <https://git.sceptique.eu/Sceptique/ZeroEpsilon>.
|
If you already have an account on git.sceptique.eu you can contribute to the upstream <https://git.sceptique.eu/Sceptique/ZeroEpsilon>.
|
||||||
I accept all kinds of patch as long as it's not bullshit.
|
I accept all kinds of patch as long as it's not bullshit.
|
||||||
|
|
||||||
- [Bug Tracker](https://git.sceptique.eu/Sceptique/ZeroEpsilon/issues)
|
|
||||||
- [Project Roadmap](https://git.sceptique.eu/Sceptique/ZeroEpsilon/projects)
|
|
||||||
- [Wiki](https://git.sceptique.eu/Sceptique/ZeroEpsilon/wiki)
|
|
||||||
|
|
||||||
If you want to test the sandbox, you can try `crystal run src/tests/physics_sandbox.cr --error-trace`
|
If you want to test the sandbox, you can try `crystal run src/tests/physics_sandbox.cr --error-trace`
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
require "../../../src/engine/gravity/field"
|
require "../../../src/engine/gravity/field"
|
||||||
|
|
||||||
describe Gravity::Field do
|
describe Gravity::Field do
|
||||||
earth = Gravity::Body(2).new(mass: 5972200000000000000000000.0, position: Vector[0.0, 0.0])
|
|
||||||
earth_surface = Vector[6371000.0, 0.0]
|
|
||||||
|
|
||||||
it "accelerate arbitrary points" do
|
it "accelerate arbitrary points" do
|
||||||
|
earth = Gravity::Body(2).new(mass: 5972200000000000000000000.0, position: Vector[0.0, 0.0])
|
||||||
field = Gravity::Field(2).new([earth])
|
field = Gravity::Field(2).new([earth])
|
||||||
field = Gravity::Field(2).new(earth)
|
field = Gravity::Field(2).new(earth)
|
||||||
field = Gravity::Field(2).new({earth})
|
field = Gravity::Field(2).new({earth})
|
||||||
|
earth_surface = Vector[6371000.0, 0.0]
|
||||||
earth.acceleration(earth_surface).magnitude.round(2).should eq(9.82)
|
earth.acceleration(earth_surface).magnitude.round(2).should eq(9.82)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "allow zero gravity field" do
|
|
||||||
g = Gravity::Field(2).zero
|
|
||||||
g.acceleration(earth_surface).magnitude.round(2).should eq(0)
|
|
||||||
g = Gravity::Field(2).new
|
|
||||||
g.acceleration(earth_surface).magnitude.round(2).should eq(0)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
require "../../src/engine/trajectory"
|
|
||||||
|
|
||||||
describe Trajectory do
|
|
||||||
it "test without gravity field nor acceleration" do
|
|
||||||
t = Trajectory(2).new(body: Gravity::MovingBody.new(mass: 1.0, position: Vector[0.0, 0.0], speed: Vector[1.0, 0.5]))
|
|
||||||
points = t.compute(steps: 10, accuracy: Time::Span.new(seconds: 1))
|
|
||||||
points.size.should eq(10)
|
|
||||||
points[0].should eq(Vector[1.0, 0.5])
|
|
||||||
points[1].should eq(Vector[2.0, 1.0])
|
|
||||||
points[2].should eq(Vector[3.0, 1.5])
|
|
||||||
points[3].should eq(Vector[4.0, 2.0])
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -124,20 +124,11 @@ describe Vector do
|
||||||
v2[1].should eq(10)
|
v2[1].should eq(10)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "modulo vectors" do
|
|
||||||
v1 = Vector[1, 2, 3, 4]
|
|
||||||
v2 = v1 % 3
|
|
||||||
v2[0].should eq(1)
|
|
||||||
v2[1].should eq(2)
|
|
||||||
v2[2].should eq(0)
|
|
||||||
v2[3].should eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "magnitude of vector" do
|
it "magnitude of vector" do
|
||||||
Vector[1, 1].magnitude.should eq(Math.sqrt(2))
|
Vector[1, 1].magnitude.should eq(Math.sqrt(2))
|
||||||
Vector[1, 2].magnitude.should eq(Math.sqrt(1**2 + 2**2))
|
Vector[1, 2].magnitude.should eq(Math.sqrt(1**2 + 2**2))
|
||||||
Vector[2, 2].magnitude.should eq(Math.sqrt(2**2 + 2**2))
|
Vector[2, 2].magnitude.should eq(Math.sqrt(2**2 + 2**2))
|
||||||
Vector[2, 2, 2].magnitude.should eq((2**2 + 2**2 + 2**2)**(1.0/2.0))
|
Vector[2, 2, 2].magnitude.should eq((2**2 + 2**2 + 2**2)**(1.0/3.0))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "normalized vector" do
|
it "normalized vector" do
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
require "../../src/engine/vessel"
|
|
||||||
|
|
||||||
describe Vessel do
|
|
||||||
it "complete station scenario" do
|
|
||||||
body = Gravity::MovingBody(2).new(mass: 1_000_000.0)
|
|
||||||
gfield = Gravity::Field(2).zero
|
|
||||||
v = Vessel(2).new(name: "Starbird", gravity_body: body, gravity_field: gfield)
|
|
||||||
v.stations.keys.includes?(:pilot).should be_true
|
|
||||||
|
|
||||||
rotation = 0
|
|
||||||
acceleration = 0
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: 3, group: :rotation) { rotation += 1 }
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: -1, group: :rotation) { rotation += 2 }
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: 1, group: :acceleration) { acceleration += 1 }
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: 2, group: :acceleration) { acceleration += 2 }
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: 0, group: :acceleration) { acceleration += 4 }
|
|
||||||
|
|
||||||
v.stations[:pilot].orders[:acceleration].size.should eq(3)
|
|
||||||
executed = v.stations[:pilot].execute_orders!
|
|
||||||
executed.size.should eq(2)
|
|
||||||
executed[0].group.should eq(:rotation)
|
|
||||||
executed[0].priority.should eq(3)
|
|
||||||
executed[1].group.should eq(:acceleration)
|
|
||||||
executed[1].priority.should eq(2)
|
|
||||||
rotation.should eq(1)
|
|
||||||
acceleration.should eq(2)
|
|
||||||
|
|
||||||
v.stations[:pilot].orders[:acceleration].size.should eq(2)
|
|
||||||
executed = v.stations[:pilot].execute_orders!
|
|
||||||
executed.size.should eq(2)
|
|
||||||
rotation.should eq(3)
|
|
||||||
acceleration.should eq(3)
|
|
||||||
|
|
||||||
v.stations[:pilot].orders[:acceleration].size.should eq(1)
|
|
||||||
executed = v.stations[:pilot].execute_orders!
|
|
||||||
executed.size.should eq(1)
|
|
||||||
rotation.should eq(3)
|
|
||||||
acceleration.should eq(7)
|
|
||||||
|
|
||||||
v.stations[:pilot].orders[:acceleration].size.should eq(0)
|
|
||||||
executed = v.stations[:pilot].execute_orders!
|
|
||||||
executed.size.should eq(0)
|
|
||||||
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: 1, group: :acceleration) { acceleration += 1 }
|
|
||||||
v.stations[:pilot] << Vessel::Order(2).new(priority: 2, group: :acceleration) { |o| o.station.clear_next!(o) }
|
|
||||||
v.stations[:pilot].orders[:acceleration].size.should eq(2)
|
|
||||||
executed = v.stations[:pilot].execute_orders!
|
|
||||||
v.stations[:pilot].orders[:acceleration].size.should eq(0)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,40 +1,79 @@
|
||||||
require "../../src/ui/event_handler"
|
require "../../src/ui/event_handler"
|
||||||
|
|
||||||
describe UI::EventHandler do
|
class TestingEvent; end
|
||||||
|
|
||||||
|
class TestingEventA < TestingEvent
|
||||||
|
getter :code, :alt, :control, :shift, :system, :stuff
|
||||||
|
def initialize(@code = "001", @alt = false, @control = false, @shift = false, @system = false, @stuff = false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestingEventB < TestingEvent
|
||||||
|
getter :code
|
||||||
|
def initialize(@code = "001")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class UI::Event
|
||||||
|
TEST_MAPPING = { TestingEventA => Type::KeyPressed, TestingEventB => Type::KeyReleased, }
|
||||||
|
def initialize(event : TestingEvent)
|
||||||
|
@type = TEST_MAPPING[event.class]? || Type::Unknown
|
||||||
|
@code = event.responds_to?(:code) ? event.code : nil
|
||||||
|
@alt = event.responds_to?(:alt) ? event.alt.to_s : nil
|
||||||
|
@control = event.responds_to?(:control) ? event.control.to_s : nil
|
||||||
|
@shift = event.responds_to?(:shift) ? event.shift.to_s : nil
|
||||||
|
@system = event.responds_to?(:system) ? event.system.to_s : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
class Handler
|
||||||
|
def event_callbacks(event : TestingEvent.class)
|
||||||
|
event = TEST_MAPPING[event]
|
||||||
|
event_callbacks(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(event : TestingEvent)
|
||||||
|
handle(Event.new(event))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe UI::Event::Handler do
|
||||||
it "verifies event accumulates and remove callbacks" do
|
it "verifies event accumulates and remove callbacks" do
|
||||||
a = UI::EventHandler.new
|
|
||||||
|
a = UI::Event::Handler.new
|
||||||
counter = 0
|
counter = 0
|
||||||
a.add(UI::EventHandler::Event::KeyPressed) do |cb|
|
a.add(UI::Event::Type::KeyPressed) do |cb, _|
|
||||||
counter += 1
|
counter += 1
|
||||||
end
|
end
|
||||||
a.add(UI::EventHandler::Event::KeyReleased) do |cb|
|
a.add(UI::Event::Type::KeyReleased) do |cb, _|
|
||||||
counter += 10
|
counter += 10
|
||||||
end
|
end
|
||||||
|
|
||||||
# test having multiple events with different types
|
# test having multiple events with different types
|
||||||
counter.should eq(0)
|
counter.should eq(0)
|
||||||
a.handle(UI::EventHandler::Event::KeyPressed.new)
|
a.handle(TestingEventA.new)
|
||||||
counter.should eq(1)
|
counter.should eq(1)
|
||||||
a.handle(UI::EventHandler::Event::KeyPressed.new)
|
a.handle(TestingEventA.new)
|
||||||
counter.should eq(2)
|
counter.should eq(2)
|
||||||
a.handle(UI::EventHandler::Event::KeyReleased.new)
|
a.handle(TestingEventB.new)
|
||||||
counter.should eq(12)
|
counter.should eq(12)
|
||||||
|
|
||||||
# test 2 callbacks on the same event
|
# test 2 callbacks on the same event
|
||||||
a.add(UI::EventHandler::Event::KeyPressed) do |cb|
|
a.add(UI::Event::Type::KeyPressed) do |cb, _|
|
||||||
counter += 1
|
counter += 1
|
||||||
end
|
end
|
||||||
a.handle(UI::EventHandler::Event::KeyPressed.new)
|
a.handle(TestingEventA.new)
|
||||||
counter.should eq(14)
|
counter.should eq(14)
|
||||||
|
|
||||||
# test a callback with autoremove (useful for keypressing)
|
# test a callback with autoremove (useful for keypressing)
|
||||||
a.add(UI::EventHandler::Event::KeyPressed) do |cb|
|
a.add(UI::Event::Type::KeyPressed) do |cb, _|
|
||||||
counter += 100
|
counter += 100
|
||||||
a.remove(UI::EventHandler::Event::KeyPressed, cb)
|
a.remove(UI::Event::Type::KeyPressed, cb)
|
||||||
end
|
end
|
||||||
a.handle(UI::EventHandler::Event::KeyPressed.new)
|
a.handle(TestingEventA.new)
|
||||||
counter.should eq(116)
|
counter.should eq(116)
|
||||||
a.handle(UI::EventHandler::Event::KeyPressed.new)
|
a.handle(TestingEventA.new)
|
||||||
counter.should eq(118)
|
counter.should eq(118)
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
require "../vector"
|
|
||||||
|
|
||||||
module Gravity
|
module Gravity
|
||||||
# A `Gravity::Body` simulates a heavy object that generate a significant warp of space time.
|
# A `Gravity::Body` simulates a heavy object that generate a significant warp of space time.
|
||||||
# Several bodies can be combined in a single `Gravity::Field`.
|
# Several bodies can be combined in a single `Gravity::Field`.
|
||||||
|
@ -11,15 +9,9 @@ module Gravity
|
||||||
EARTH2 = Gravity::Body(2).new(mass: 5972200000000000000000000.0, position: Vector(Float64, 2).zero)
|
EARTH2 = Gravity::Body(2).new(mass: 5972200000000000000000000.0, position: Vector(Float64, 2).zero)
|
||||||
EARTH3 = Gravity::Body(3).new(mass: 5972200000000000000000000.0, position: Vector(Float64, 3).zero)
|
EARTH3 = Gravity::Body(3).new(mass: 5972200000000000000000000.0, position: Vector(Float64, 3).zero)
|
||||||
|
|
||||||
getter :mass, :position, :g, :name
|
getter :mass, :position, :g
|
||||||
|
|
||||||
@@name_idx = 0
|
def initialize(@mass : Float64, @position : Vector(Float64, N) = Vector(Float64, N).zero, @g : Float64 = G)
|
||||||
def self.next_name
|
|
||||||
@@name_idx += 1
|
|
||||||
"default~#{@@name_idx}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(@mass : Float64, @position : Vector(Float64, N) = Vector(Float64, N).zero, @g : Float64 = G, @name : String = Body(0).next_name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def acceleration(position : Vector(Float64, N)) : Vector(Float64, N)
|
def acceleration(position : Vector(Float64, N)) : Vector(Float64, N)
|
||||||
|
@ -43,5 +35,6 @@ module Gravity
|
||||||
def speed
|
def speed
|
||||||
Vector(Float64, N).zero
|
Vector(Float64, N).zero
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Gravity
|
||||||
getter bodies : Array(Body(N))
|
getter bodies : Array(Body(N))
|
||||||
|
|
||||||
# standard constructor
|
# standard constructor
|
||||||
def initialize(@bodies : Array(Body(N)) = Array(Body(N)).new)
|
def initialize(@bodies : Array(Body(N)))
|
||||||
end
|
end
|
||||||
|
|
||||||
# standard constructor
|
# standard constructor
|
||||||
|
@ -18,10 +18,6 @@ module Gravity
|
||||||
@bodies = bodies.to_a
|
@bodies = bodies.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.zero
|
|
||||||
new(bodies: Array(Body(N)).new)
|
|
||||||
end
|
|
||||||
|
|
||||||
# helping constructor for testing and sandboxes
|
# helping constructor for testing and sandboxes
|
||||||
# - bodies must be a set of `Gravity::Body`
|
# - bodies must be a set of `Gravity::Body`
|
||||||
def initialize(*bodies)
|
def initialize(*bodies)
|
||||||
|
|
|
@ -3,7 +3,6 @@ require "./body"
|
||||||
module Gravity
|
module Gravity
|
||||||
class MovingBody(N) < Body(N)
|
class MovingBody(N) < Body(N)
|
||||||
getter :acceleration, :speed
|
getter :acceleration, :speed
|
||||||
|
|
||||||
def initialize(@acceleration : Vector(Float64, N) = Vector(Float64, N).zero, @speed : Vector(Float64, N) = Vector(Float64, N).zero, *p, **o)
|
def initialize(@acceleration : Vector(Float64, N) = Vector(Float64, N).zero, @speed : Vector(Float64, N) = Vector(Float64, N).zero, *p, **o)
|
||||||
super(*p, **o)
|
super(*p, **o)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
require "./vector"
|
require "./vector"
|
||||||
|
|
||||||
# TODO: clean up this mess
|
|
||||||
module Physics
|
module Physics
|
||||||
# This contains a memory leak because of the recursive nature of the structure
|
|
||||||
# Ths is shit, remove it an reimplement properly with an accelerator
|
|
||||||
class Tick
|
class Tick
|
||||||
property :n, :time, :last
|
property :n, :time, :last
|
||||||
|
def initialize(@n : Int64, @time : Time, @last : Tick? = nil)
|
||||||
def initialize(@n : Int64 = 0, @time : Time = Time.utc, @last : Tick? = nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def timelaps : Time::Span
|
def timelaps : Time::Span
|
||||||
|
@ -37,8 +34,6 @@ module Physics
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# this code is ok but it needs to be cleaner
|
|
||||||
# for instance use a moving_body instead
|
|
||||||
def self.accelerate_by_one_tick!(tick : Tick, speed : Vector(T, N), acceleration : Vector(T, N)) : Vector(T, N) forall T, N
|
def self.accelerate_by_one_tick!(tick : Tick, speed : Vector(T, N), acceleration : Vector(T, N)) : Vector(T, N) forall T, N
|
||||||
fraction = tick.timelaps.total_milliseconds / 1000.0
|
fraction = tick.timelaps.total_milliseconds / 1000.0
|
||||||
speed.add!(acceleration * fraction)
|
speed.add!(acceleration * fraction)
|
||||||
|
@ -54,4 +49,5 @@ module Physics
|
||||||
position.add!(tick_average_speed)
|
position.add!(tick_average_speed)
|
||||||
speed.add!(acceleration * time_fraction)
|
speed.add!(acceleration * time_fraction)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
require "crsfml"
|
|
||||||
|
|
||||||
require "./gravity"
|
|
||||||
|
|
||||||
class Projectable(N)
|
|
||||||
property :gravity_body, :color, :size, :outline_color, :outline_size
|
|
||||||
|
|
||||||
@@hook_new : Proc(Projectable(2), Nil) = ->(obj : Projectable(2)) { nil }
|
|
||||||
def self.set_hook_new(hook_proc : Proc(Projectable(2), Nil))
|
|
||||||
@@hook_new = hook_proc
|
|
||||||
end
|
|
||||||
|
|
||||||
def trigger_hook_new
|
|
||||||
@@hook_new.call(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(
|
|
||||||
@gravity_body : Gravity::Body(N),
|
|
||||||
@color : Tuple(Int32, Int32, Int32),
|
|
||||||
@size : Int32,
|
|
||||||
@outline_color : Tuple(Int32, Int32, Int32) = {0, 0, 0},
|
|
||||||
@outline_size : Int32 = 0
|
|
||||||
)
|
|
||||||
trigger_hook_new
|
|
||||||
end
|
|
||||||
|
|
||||||
def name
|
|
||||||
@gravity_body.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def position
|
|
||||||
@gravity_body.position
|
|
||||||
end
|
|
||||||
|
|
||||||
def acceleration
|
|
||||||
@gravity_body.acceleration
|
|
||||||
end
|
|
||||||
|
|
||||||
def speed
|
|
||||||
@gravity_body.speed
|
|
||||||
end
|
|
||||||
|
|
||||||
def sf_shape(offset : Vector(Float64, N) = Vector(Float64, N).zero)
|
|
||||||
shape = SF::CircleShape.new(@size)
|
|
||||||
shape.fill_color = SF.color(@color[0], @color[1], @color[2])
|
|
||||||
|
|
||||||
shape.outline_thickness = @outline_size
|
|
||||||
shape.outline_color = SF.color(@outline_color[0], @outline_color[1], @outline_color[2])
|
|
||||||
shape.position = sf_position(offset)
|
|
||||||
shape
|
|
||||||
end
|
|
||||||
|
|
||||||
# the sf_position is the projected position of the gravity_body on the graphic plan
|
|
||||||
def sf_position(offset : Vector(Float64, N) = Vector(Float64, N).zero)
|
|
||||||
zoomed_position = @gravity_body.position + offset
|
|
||||||
SF.vector2(zoomed_position[0], zoomed_position[1])
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,49 +0,0 @@
|
||||||
require "./gravity"
|
|
||||||
require "./physics"
|
|
||||||
|
|
||||||
# A `Trajectory` defines discretes positions in space and time of
|
|
||||||
# a body which has a constant acceleration (it may be null)
|
|
||||||
# and submitted to a gravity field (which can be null too).
|
|
||||||
#
|
|
||||||
# A trajectory always exists within a system with N dimensions.
|
|
||||||
class Trajectory(N)
|
|
||||||
# In practice the body will always be a `Gravity::MovingBody` but the
|
|
||||||
# interface allows us to use `Gravity::Body` too.
|
|
||||||
# The gravity field may be empty.
|
|
||||||
def initialize(@body : Gravity::Body(N), @gravity_field : Gravity::Field = Gravity::Field(N).new)
|
|
||||||
end
|
|
||||||
|
|
||||||
getter :body, :gravity_field
|
|
||||||
|
|
||||||
# Generate an array with `steps` points.
|
|
||||||
# First point is the space position at t+accuracy
|
|
||||||
# N point is the space position at t+accuracy*n
|
|
||||||
#
|
|
||||||
# `accuracy` must be a positive time span.
|
|
||||||
def compute(steps : UInt32, accuracy : Time::Span) : DiscreteCurve(N)
|
|
||||||
raise "accuracy must be a positive time span" if accuracy < Time::Span.zero
|
|
||||||
timer = Physics::Tick::Timer.new
|
|
||||||
|
|
||||||
position = @body.position.clone
|
|
||||||
speed = @body.speed.clone
|
|
||||||
acceleration = @body.acceleration.clone
|
|
||||||
|
|
||||||
points = DiscreteCurve(N).new
|
|
||||||
# points << position
|
|
||||||
steps.times do |index|
|
|
||||||
timer.timeskip!(timespan: accuracy)
|
|
||||||
Physics.move_by_one_tick!(
|
|
||||||
tick: timer.tick,
|
|
||||||
position: position,
|
|
||||||
speed: speed,
|
|
||||||
acceleration: acceleration + @gravity_field.acceleration(position),
|
|
||||||
)
|
|
||||||
points << position.clone
|
|
||||||
end
|
|
||||||
|
|
||||||
points
|
|
||||||
end
|
|
||||||
|
|
||||||
class DiscreteCurve(N) < Array(Vector(Float64, N))
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -95,7 +95,7 @@ class Vector(T, N) # should be a struct since it's a simple pointer inside, the
|
||||||
clone
|
clone
|
||||||
end
|
end
|
||||||
|
|
||||||
def -
|
def -()
|
||||||
clone = self.clone
|
clone = self.clone
|
||||||
size.times do |index|
|
size.times do |index|
|
||||||
clone.to_unsafe[index] = -clone.to_unsafe[index]
|
clone.to_unsafe[index] = -clone.to_unsafe[index]
|
||||||
|
@ -147,20 +147,6 @@ class Vector(T, N) # should be a struct since it's a simple pointer inside, the
|
||||||
clone
|
clone
|
||||||
end
|
end
|
||||||
|
|
||||||
def %(right)
|
|
||||||
clone = self.clone
|
|
||||||
size.times do |index|
|
|
||||||
clone.to_unsafe[index] %= right
|
|
||||||
end
|
|
||||||
clone
|
|
||||||
end
|
|
||||||
|
|
||||||
def zero!
|
|
||||||
N.times do |index|
|
|
||||||
self[index] = T.zero
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_unsafe
|
def to_unsafe
|
||||||
@buffer
|
@buffer
|
||||||
end
|
end
|
||||||
|
@ -169,16 +155,13 @@ class Vector(T, N) # should be a struct since it's a simple pointer inside, the
|
||||||
T
|
T
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# I think it's always square root not 1.0/size
|
||||||
def magnitude : Number
|
def magnitude : Number
|
||||||
reduce(T.zero) { |base, elem| base + elem**2 } ** (1.0 / 2.0)
|
reduce(T.zero) { |base, elem| base + elem**2 } ** (1.0 / size)
|
||||||
end
|
end
|
||||||
|
|
||||||
# put the vector on a trigonomic circle so sqrt(x**2 + y**2) = 1
|
|
||||||
def normalize
|
def normalize
|
||||||
|
magnitude = self.magnitude
|
||||||
self / magnitude
|
self / magnitude
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize
|
|
||||||
self.div!(magnitude)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,252 +0,0 @@
|
||||||
require "./gravity"
|
|
||||||
|
|
||||||
class Vessel(N)
|
|
||||||
getter :name, :gravity_body, :gravity_field, :stations
|
|
||||||
getter :pilot, :navigation
|
|
||||||
|
|
||||||
def initialize(@name : String, @gravity_body : Gravity::MovingBody(N), @gravity_field : Gravity::Field(N))
|
|
||||||
@pilot = Pilot(N).new
|
|
||||||
@navigation = Navigation(N).new
|
|
||||||
@stations = {
|
|
||||||
pilot: @pilot,
|
|
||||||
navigation: @navigation,
|
|
||||||
}
|
|
||||||
# -- bottom after instances variables are set, we can reference self --
|
|
||||||
@stations.each { |sn, s| s.vessel = self }
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute_orders!
|
|
||||||
@stations.each { |sn, s| s.execute_orders! }
|
|
||||||
end
|
|
||||||
|
|
||||||
def gravity : Vector(Float64, N)
|
|
||||||
@gravity_field.acceleration(@gravity_body.position)
|
|
||||||
end
|
|
||||||
|
|
||||||
abstract class Station(N)
|
|
||||||
getter orders
|
|
||||||
setter vessel
|
|
||||||
|
|
||||||
def initialize(@vessel : Vessel(N)? = nil)
|
|
||||||
@orders = Hash(Symbol, Array(Order(N))).new
|
|
||||||
end
|
|
||||||
|
|
||||||
def vessel?
|
|
||||||
@vessel
|
|
||||||
end
|
|
||||||
|
|
||||||
def vessel : Vessel(N)
|
|
||||||
vessel = @vessel
|
|
||||||
raise "@vessel cannot be Nil" if vessel.nil?
|
|
||||||
vessel
|
|
||||||
end
|
|
||||||
|
|
||||||
# like a orders[group] but will never throw and always
|
|
||||||
# have an initialized array.
|
|
||||||
# It is not a default value because it can modify the orders state.
|
|
||||||
def orders_at(group : Symbol)
|
|
||||||
@orders[group] ||= Array(Order(N)).new
|
|
||||||
@orders[group]
|
|
||||||
end
|
|
||||||
|
|
||||||
def orders_at(order : Order(N))
|
|
||||||
orders_at(order.group)
|
|
||||||
end
|
|
||||||
|
|
||||||
def <<(order : Order(N))
|
|
||||||
order.station = self
|
|
||||||
orders_at(order.group) << order
|
|
||||||
end
|
|
||||||
|
|
||||||
def order(priority : Int32 = 0, group : Symbol = :default, &block : Order(N) -> Nil)
|
|
||||||
self << Order(N).new(priority: priority, group: group, station: self, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Given one `Order`, this method remove all next orders in the list
|
|
||||||
# so we don't execute them next tick.
|
|
||||||
def clear_next!(order : Order(N))
|
|
||||||
orders_at(order.group).clear
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute_orders! : Array(Order(N))
|
|
||||||
executed = Array(Order(N)).new
|
|
||||||
@orders.each do |group, orders|
|
|
||||||
executed_one = execute_orders_group!(orders)
|
|
||||||
executed << executed_one if !executed_one.nil?
|
|
||||||
end
|
|
||||||
executed
|
|
||||||
end
|
|
||||||
|
|
||||||
# Execute only the most prioritized order and remove it from the order list.
|
|
||||||
# This order may trigger a clear operation if it cancels non executed orders.
|
|
||||||
private def execute_orders_group!(orders : Array(Order(N))) : Order(N)?
|
|
||||||
return nil if orders.empty?
|
|
||||||
orders.sort!
|
|
||||||
order = orders.pop # remove last order and execute it
|
|
||||||
order.execute
|
|
||||||
# orders.unshift order if order.keep
|
|
||||||
order
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# And Order is supposed to write something on the vessel state.
|
|
||||||
# It may be accelerate the vector, use communications antennas, sensors, etc.
|
|
||||||
# And order is executed when executed is called, which allows for the DLS code:
|
|
||||||
#
|
|
||||||
# value = 123.45
|
|
||||||
# vessel.pilot << Order(2).new(priority: 2, group: :acceleration) { |order|
|
|
||||||
# order.vessel.acceleration[0] = value
|
|
||||||
# order.station.clear_next!(order)
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
class Order(N)
|
|
||||||
setter :station
|
|
||||||
getter :priority, :group
|
|
||||||
# property :keep
|
|
||||||
|
|
||||||
def initialize(@priority : Int32 = 0, @group : Symbol = :default, @station : Station(N)? = nil, @keep : Bool = false, &@block : Order(N) -> Nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s(io : IO) : Nil
|
|
||||||
io << "Order[#{station.class}:#{group}:#{priority}]"
|
|
||||||
end
|
|
||||||
|
|
||||||
def pretty_print(pp)
|
|
||||||
pp.text(to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# No order should have a nil station, but there is a very small window in the code where
|
|
||||||
# the station is nil during initialization before being attributed to the station via `Station#<<`.
|
|
||||||
def station : Station(N)
|
|
||||||
station = @station
|
|
||||||
raise "No station set, should not happen" if station.nil?
|
|
||||||
station
|
|
||||||
end
|
|
||||||
|
|
||||||
# shortcut
|
|
||||||
def vessel
|
|
||||||
station.vessel
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
@block.call(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Compare priorities of 2 `Order`
|
|
||||||
def <=>(right)
|
|
||||||
return 0 if @group != right.group # should not happen... raise an exception ?
|
|
||||||
@priority <=> right.priority
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Pilot(N) < Station(N)
|
|
||||||
# these properties handle manual override of trust (either analogic or exact computed)
|
|
||||||
property low_thrust_save, low_thrust_pressed, computer_input_thrust
|
|
||||||
# on the interface, the pilot can select a body to create relative vectors
|
|
||||||
property current_selected_body : Gravity::Body(N)?
|
|
||||||
# this is a hard limit for thrust so the pilot can avoid killing all the people aboard
|
|
||||||
property max_thrust
|
|
||||||
# permanent mode is the default modes to apply at all time
|
|
||||||
property permanent_modes : Array(Mode)
|
|
||||||
# temporary mode must be set at each tick and will be reset each time it is applied
|
|
||||||
property temporary_modes : Array(Mode)
|
|
||||||
|
|
||||||
enum Mode
|
|
||||||
Constant
|
|
||||||
Pulse
|
|
||||||
AntiGrav
|
|
||||||
AntiSpeed
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(*p, **o)
|
|
||||||
super
|
|
||||||
@permanent_modes = Array(Mode).new
|
|
||||||
@temporary_modes = Array(Mode).new
|
|
||||||
@low_thrust_save = 0.0
|
|
||||||
@low_thrust_pressed = false
|
|
||||||
@computer_input_thrust = Vector(Float64, N).zero
|
|
||||||
@max_thrust = 20.0
|
|
||||||
@current_selected_body = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Modify immediatly one elemnt of the acceleration vector
|
|
||||||
def set_exact_acceleration_axis(index : Int32, value : Float64)
|
|
||||||
vessel.gravity_body.acceleration[index] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# Modify immediatly the acceleration vector
|
|
||||||
def set_exact_acceleration_axis(axis : Vector(Float64, N))
|
|
||||||
axis.each_with_index do |value, index|
|
|
||||||
set_exact_acceleration_axis(index, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# set_exact_acceleration_axis({0, 23.0}, {1, 11.2}) # to set x;y at once
|
|
||||||
def set_exact_acceleration_axis(*tuples : Array(Tuple(Int32, Float64)))
|
|
||||||
tuples.each do |tuple|
|
|
||||||
set_exact_acceleration_axis(*tuple)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def immediate_modes : Array(Mode)
|
|
||||||
if !@temporary_modes.empty?
|
|
||||||
@temporary_modes
|
|
||||||
else
|
|
||||||
@permanent_modes
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_current_mode
|
|
||||||
new_acceleration_axis = Vector(Float64, N).zero
|
|
||||||
must_update_acceleration_axis = false
|
|
||||||
if immediate_modes.includes?(Mode::AntiGrav)
|
|
||||||
must_update_acceleration_axis = true
|
|
||||||
new_acceleration_axis.add!(-vessel.gravity)
|
|
||||||
end
|
|
||||||
|
|
||||||
if immediate_modes.includes?(Mode::AntiSpeed)
|
|
||||||
must_update_acceleration_axis = true
|
|
||||||
reset_acceleration = true
|
|
||||||
if (current_selected_body = @current_selected_body)
|
|
||||||
relative_speed = vessel.gravity_body.speed - current_selected_body.speed
|
|
||||||
new_acceleration_axis.add!(-relative_speed)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if new_acceleration_axis.magnitude > @max_thrust
|
|
||||||
new_acceleration_axis.normalize.mult!(@max_thrust)
|
|
||||||
end
|
|
||||||
set_exact_acceleration_axis(new_acceleration_axis) if must_update_acceleration_axis
|
|
||||||
|
|
||||||
@temporary_modes.clear # each time we apply the temporary_mode, reset it
|
|
||||||
end
|
|
||||||
|
|
||||||
def accelerate_current_vector!(coef : Float64)
|
|
||||||
set_exact_acceleration_axis(vessel.gravity_body.speed * coef)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Navigation(N) < Station(N)
|
|
||||||
class Cadran
|
|
||||||
property radius : Array(Float64)
|
|
||||||
def initialize(default_radius : Float64 = 1000.0)
|
|
||||||
@radius = [] of Float64
|
|
||||||
@radius << default_radius if !default_radius.nil?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
property zoom_select : Int32
|
|
||||||
property zoom_position : Vector(Float64, N)
|
|
||||||
property cadran : Cadran
|
|
||||||
property zoom_ratio : Float64
|
|
||||||
|
|
||||||
def initialize(*p, **o)
|
|
||||||
super
|
|
||||||
@zoom_ratio = 1.0
|
|
||||||
@zoom_select = 0
|
|
||||||
@zoom_position = Vector(Float64, N).zero
|
|
||||||
@cadran = Cadran.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -3,7 +3,6 @@ class FrequencyLoop
|
||||||
@sleep = [] of Time::Span
|
@sleep = [] of Time::Span
|
||||||
@span = [] of Time::Span
|
@span = [] of Time::Span
|
||||||
end
|
end
|
||||||
|
|
||||||
getter :sleep, :frequency, :span
|
getter :sleep, :frequency, :span
|
||||||
|
|
||||||
ZERO = Time::Span.zero
|
ZERO = Time::Span.zero
|
||||||
|
|
|
@ -7,8 +7,6 @@ require "crsfml"
|
||||||
require "imgui"
|
require "imgui"
|
||||||
require "imgui-sfml"
|
require "imgui-sfml"
|
||||||
|
|
||||||
require "./physics_sandbox/*"
|
|
||||||
|
|
||||||
require "log"
|
require "log"
|
||||||
Log.setup(:debug)
|
Log.setup(:debug)
|
||||||
|
|
||||||
|
@ -18,137 +16,102 @@ class Log
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO move this to engine
|
||||||
|
class Projectable(N)
|
||||||
|
property :gravity_body, :color, :size, :outline_color, :outline_size
|
||||||
|
def initialize(
|
||||||
|
@gravity_body : Gravity::Body(N),
|
||||||
|
@color : Tuple(Int32, Int32, Int32),
|
||||||
|
@size : Int32,
|
||||||
|
@outline_color : Tuple(Int32, Int32, Int32) = {0, 0, 0},
|
||||||
|
@outline_size : Int32 = 0,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def position
|
||||||
|
@gravity_body.position
|
||||||
|
end
|
||||||
|
|
||||||
|
def acceleration
|
||||||
|
@gravity_body.acceleration
|
||||||
|
end
|
||||||
|
|
||||||
|
def speed
|
||||||
|
@gravity_body.speed
|
||||||
|
end
|
||||||
|
|
||||||
|
def sf_shape
|
||||||
|
shape = SF::CircleShape.new(@size)
|
||||||
|
shape.fill_color = SF.color(@color[0], @color[1], @color[2])
|
||||||
|
|
||||||
|
shape.outline_thickness = @outline_size
|
||||||
|
shape.outline_color = SF.color(@outline_color[0], @outline_color[1], @outline_color[2])
|
||||||
|
shape.position = sf_position(0.5)
|
||||||
|
shape
|
||||||
|
end
|
||||||
|
|
||||||
|
def sf_position(coef : Float64 = 1.0)
|
||||||
|
SF.vector2(@gravity_body.position[0] * coef, @gravity_body.position[1] * coef)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Game
|
class Game
|
||||||
@window : SF::RenderWindow
|
@window : SF::RenderWindow
|
||||||
|
@frequency_nano_rate : Int32
|
||||||
@delta_clock : SF::Clock
|
@delta_clock : SF::Clock
|
||||||
@screen_center_position : Vector(Float64, 2)
|
|
||||||
@center_position : Vector(Float64, 2)
|
|
||||||
@joystick_moved : Vector(Float64, 2)
|
|
||||||
@selected_projectable : Projectable(2)
|
|
||||||
@click_coordinates : Vector(Float64, 2)?
|
|
||||||
|
|
||||||
SECOND_IN_NANO = 1_000_000_000
|
def initialize(framerate = 60, width = 800, height = 600)
|
||||||
DEFAULT_ZOOM = 3/16
|
|
||||||
|
|
||||||
def accuracy_frequency_nano_rate
|
|
||||||
((SECOND_IN_NANO * @timescale) // @framerate).to_u64
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw(item : SF::VertexBuffer, transform : SF::Transform? = nil)
|
|
||||||
states = SF::RenderStates.new
|
|
||||||
if transform
|
|
||||||
states.transform = transform
|
|
||||||
end
|
|
||||||
@window.draw(item, states)
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw(item : SF::Shape, transform : SF::Transform? = nil)
|
|
||||||
states = SF::RenderStates.new
|
|
||||||
if transform
|
|
||||||
states.transform = transform
|
|
||||||
end
|
|
||||||
@window.draw(item, states)
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_viewport
|
|
||||||
SF.float_rect(0.0, 0.0, 1.0, @width / @height)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(@framerate : Int32 = 60, @width : Int32 = 800, @height : Int32 = 600)
|
|
||||||
@window = SF::RenderWindow.new(
|
@window = SF::RenderWindow.new(
|
||||||
SF::VideoMode.new(width, height),
|
SF::VideoMode.new(width, height),
|
||||||
"Sandbox",
|
"Sandbox",
|
||||||
)
|
)
|
||||||
@zoom = 1.0
|
|
||||||
@joystick_moved = Vector[0.0, 0.0]
|
|
||||||
@screen_center_position = Vector[width / 2, height / 2]
|
|
||||||
@center_position = Vector[0.0, 0.0]
|
|
||||||
|
|
||||||
@delta_clock = SF::Clock.new
|
@delta_clock = SF::Clock.new
|
||||||
@time_passed = Time::Span.zero
|
|
||||||
@timescale = 1.0
|
|
||||||
@window_frequency = Time::Span.new(nanoseconds: SECOND_IN_NANO // @framerate)
|
|
||||||
|
|
||||||
ImGui::SFML.init(@window)
|
ImGui::SFML.init(@window)
|
||||||
@window.framerate_limit = @framerate
|
@window.framerate_limit = framerate
|
||||||
|
|
||||||
@view = SF::View.new
|
@frequency_nano_rate = 1_000_000_000 // framerate
|
||||||
@view.viewport = default_viewport # SF.float_rect(0.0, 0.0, 1.0, @width / @height)
|
@frequency = Time::Span.new(nanoseconds: @frequency_nano_rate)
|
||||||
@view.center = {@center_position[0], @center_position[1]}
|
@ui_states = {
|
||||||
@window.view = @view
|
acceleration_digit: {
|
||||||
|
:x => 0.0,
|
||||||
# connected = SF::Joystick.connected?(0)
|
:y => 0.0,
|
||||||
# # How many buttons does joystick #0 support?
|
},
|
||||||
# buttons = SF::Joystick.get_button_count(0)
|
acceleration_log_analogic_pressed: [false], # note: I should probably use pointers instead of this shit
|
||||||
# # Does joystick #0 define a X axis?
|
acceleration_log_analogic_save: [0.0],
|
||||||
# has_x = SF::Joystick.axis?(0, SF::Joystick::X)
|
|
||||||
# # Is button #2 pressed on joystick #0?
|
|
||||||
# pressed = SF::Joystick.button_pressed?(0, 2)
|
|
||||||
# # What's the current position of the Y axis on joystick #0?
|
|
||||||
# position = SF::Joystick.get_axis_position(0, SF::Joystick::Y)
|
|
||||||
|
|
||||||
@click_coordinates = nil
|
|
||||||
|
|
||||||
@projectables = Array(Projectable(2)).new
|
|
||||||
Projectable(2).set_hook_new ->(projectable : Projectable(2)) {
|
|
||||||
@projectables << projectable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sun_offset = Vector[3*10.0**11, 3*10.0**11]
|
|
||||||
@bodies = {
|
@bodies = {
|
||||||
vessel: Gravity::MovingBody(2).new(name: "vessel", mass: 1.0, g: 0.1, position: Vector[10.0, 10.0]),
|
vessel: Gravity::MovingBody(2).new(mass: 1.0, g: 0.1, position: Vector[10.0, 10.0]),
|
||||||
star: Gravity::MovingBody(2).new(name: "star", mass: 300000.0, g: 0.1, position: Vector[1000.0, 1000.0], speed: Vector[0.0, 0.0]),
|
star: Gravity::MovingBody(2).new(mass: 300000.0, g: 0.1, position: Vector[400.0, 400.0]),
|
||||||
pla1: Gravity::MovingBody(2).new(mass: 15000.0, g: 0.1, position: Vector[500.0, 500.0], speed: Vector[12.0, 0.0]),
|
pla1: Gravity::MovingBody(2).new(mass: 15000.0, g: 0.1, position: Vector[200.0, 200.0]),
|
||||||
pla2: Gravity::MovingBody(2).new(mass: 18000.0, g: 0.1, position: Vector[1000.0, 1500.0], speed: Vector[-6.0, -6.0]),
|
pla2: Gravity::MovingBody(2).new(mass: 18000.0, g: 0.1, position: Vector[280.0, 320.0]),
|
||||||
pla3: Gravity::MovingBody(2).new(mass: 12000.0, g: 0.1, position: Vector[1500.0, 1500.0], speed: Vector[-6.0, 0.0]),
|
|
||||||
}
|
}
|
||||||
@gravited = [
|
@vessel = Projectable(2).new(
|
||||||
@bodies[:pla1], @bodies[:pla2], @bodies[:pla3], @bodies[:star],
|
|
||||||
] of Gravity::Body(2)
|
|
||||||
@graviting = [
|
|
||||||
@bodies[:pla1], @bodies[:pla2], @bodies[:pla3], @bodies[:vessel],
|
|
||||||
] of Gravity::MovingBody(2)
|
|
||||||
@vessel_p = Projectable(2).new(
|
|
||||||
gravity_body: @bodies[:vessel],
|
gravity_body: @bodies[:vessel],
|
||||||
color: { 255, 0, 250 },
|
color: { 255, 0, 250 },
|
||||||
size: 5,
|
size: 3,
|
||||||
)
|
)
|
||||||
@star = Projectable(2).new(
|
@star = Projectable(2).new(
|
||||||
gravity_body: @bodies[:star],
|
gravity_body: @bodies[:star],
|
||||||
color: { 200, 200, 50 },
|
color: { 200, 200, 50 },
|
||||||
size: 20,
|
size: 10,
|
||||||
)
|
)
|
||||||
@pla1 = Projectable(2).new(
|
@pla1 = Projectable(2).new(
|
||||||
gravity_body: @bodies[:pla1],
|
gravity_body: @bodies[:pla1],
|
||||||
color: { 150, 50, 50 },
|
color: { 150, 50, 50 },
|
||||||
size: 10,
|
size: 5,
|
||||||
)
|
)
|
||||||
@pla2 = Projectable(2).new(
|
@pla2 = Projectable(2).new(
|
||||||
gravity_body: @bodies[:pla2],
|
gravity_body: @bodies[:pla2],
|
||||||
color: {50, 50, 150},
|
color: { 50, 100, 150 },
|
||||||
size: 10,
|
size: 5,
|
||||||
)
|
)
|
||||||
@pla3 = Projectable(2).new(
|
@g = Gravity::Field(2).new([@star.gravity_body, @pla1.gravity_body, @pla2.gravity_body])
|
||||||
gravity_body: @bodies[:pla3],
|
|
||||||
color: {50, 150, 50},
|
|
||||||
size: 10,
|
|
||||||
)
|
|
||||||
# @projectables = {
|
|
||||||
# @star, @pla1, @pla2, @pla3, @star, @vessel_p,
|
|
||||||
# }
|
|
||||||
@selected_projectable = @vessel_p
|
|
||||||
@g = Gravity::Field(2).new(@gravited)
|
|
||||||
@vessel = Vessel(2).new(name: "Starbird", gravity_body: @bodies[:vessel], gravity_field: @g)
|
|
||||||
@vessel.navigation.zoom_ratio = DEFAULT_ZOOM
|
|
||||||
@timer = Physics::Tick::Timer.new
|
@timer = Physics::Tick::Timer.new
|
||||||
@ui_events_handler = UI::EventHandler.new
|
@ui_events_handler = UI::EventHandler.new
|
||||||
end
|
end
|
||||||
|
|
||||||
MAX_DISTANCE_TO_SELECT_PROJECTABLE = 15
|
|
||||||
|
|
||||||
def handle_events
|
def handle_events
|
||||||
joystick_moved_modified = false
|
|
||||||
|
|
||||||
while event = @window.poll_event
|
while event = @window.poll_event
|
||||||
ImGui::SFML.process_event(@window, event)
|
ImGui::SFML.process_event(@window, event)
|
||||||
|
|
||||||
|
@ -156,88 +119,19 @@ class Game
|
||||||
when SF::Event::Closed
|
when SF::Event::Closed
|
||||||
@window.close
|
@window.close
|
||||||
exit
|
exit
|
||||||
when SF::Event::Resized
|
|
||||||
@width = event.width.to_i32
|
|
||||||
@height = event.height.to_i32
|
|
||||||
@view.viewport = default_viewport
|
|
||||||
@center_position = Vector[0.0, 0.0]
|
|
||||||
@view.center = {@center_position[0], @center_position[1]}
|
|
||||||
@window.view = @view
|
|
||||||
when SF::Event::KeyPressed
|
when SF::Event::KeyPressed
|
||||||
Log.debug "KeyPressed #{event}"
|
puts "KeyPressed #{event}"
|
||||||
when SF::Event::MouseMoved
|
|
||||||
when SF::Event::MouseButtonReleased
|
|
||||||
clicked_on_imgui_item = ImGui.is_any_item_hovered || ImGui.is_any_item_active
|
|
||||||
if !clicked_on_imgui_item
|
|
||||||
# don't match if click on the buttons of ImGui
|
|
||||||
# click_coord = @window.map_pixel_to_coords({event.x, event.y}, @view)
|
|
||||||
# m = (proj.position - Vector[click_coord.x, click_coord.y]).magnitude
|
|
||||||
# mmhhhh it does not work as expected, bugged
|
|
||||||
event_position = Vector[event.x, event.y]
|
|
||||||
nearest_proj = @projectables.map do |projectable|
|
|
||||||
pixel = @window.map_coords_to_pixel({projectable.position[0], projectable.position[1]}, @view)
|
|
||||||
{
|
|
||||||
projectable: projectable,
|
|
||||||
distance: (Vector[pixel.x, pixel.y] - event_position).magnitude,
|
|
||||||
}
|
|
||||||
end.sort_by!{ |tuple| tuple[:distance] }.first
|
|
||||||
if nearest_proj[:distance] <= MAX_DISTANCE_TO_SELECT_PROJECTABLE
|
|
||||||
@selected_projectable.outline_color = {0, 0, 0}
|
|
||||||
@selected_projectable.outline_size = 0
|
|
||||||
@selected_projectable = nearest_proj[:projectable]
|
|
||||||
@selected_projectable.outline_color = {255, 0, 0}
|
|
||||||
@selected_projectable.outline_size = 5
|
|
||||||
pp "Select a new body #{@selected_projectable.name}"
|
|
||||||
@click_coordinates = nil
|
|
||||||
else
|
|
||||||
@selected_projectable.outline_color = {0, 0, 0}
|
|
||||||
@selected_projectable.outline_size = 0
|
|
||||||
@click_coordinates = Vector[event.x.to_f64, event.y.to_f64]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
when SF::Event::MouseButtonEvent
|
when SF::Event::MouseButtonEvent
|
||||||
pp "MouseButtonEvent #{event}"
|
puts "MouseButtonEvent #{event}"
|
||||||
when SF::Event::JoystickButtonPressed
|
|
||||||
@joystick_moved.zero! # reset 0
|
|
||||||
joystick_moved_modified = :vector # force exact set
|
|
||||||
when SF::Event::JoystickMoved
|
|
||||||
case event.axis
|
|
||||||
when SF::Joystick::Axis::U, SF::Joystick::Axis::X
|
|
||||||
@joystick_moved[0] = event.position
|
|
||||||
joystick_moved_modified = :vector
|
|
||||||
when SF::Joystick::Axis::V, SF::Joystick::Axis::Y
|
|
||||||
@joystick_moved[1] = event.position
|
|
||||||
joystick_moved_modified = :vector
|
|
||||||
when SF::Joystick::Axis::Z
|
|
||||||
@joystick_moved[0] = event.position
|
|
||||||
joystick_moved_modified = :slow
|
|
||||||
when SF::Joystick::Axis::R
|
|
||||||
@joystick_moved[0] = event.position
|
|
||||||
joystick_moved_modified = :accelerate
|
|
||||||
end
|
|
||||||
else
|
|
||||||
pp event
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@ui_events_handler.handle(event)
|
@ui_events_handler.handle(event)
|
||||||
end
|
end
|
||||||
|
|
||||||
if joystick_moved_modified == :vector
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(@joystick_moved)
|
|
||||||
elsif joystick_moved_modified == :accelerate
|
|
||||||
# can double the vector each second
|
|
||||||
Log.debug "accelerate #{@joystick_moved}"
|
|
||||||
@vessel.pilot.accelerate_current_vector!(1.0 + (@joystick_moved[0] + 100) / 2 * 0.01)
|
|
||||||
elsif joystick_moved_modified == :slow
|
|
||||||
# can slow
|
|
||||||
Log.debug "slow down #{@joystick_moved}"
|
|
||||||
@vessel.pilot.accelerate_current_vector!(-1.0 - (@joystick_moved[0] + 100) / 2 * 0.01)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_loop
|
def execute_loop
|
||||||
while @window.open? # restart the loop when if it ends
|
while @window.open? # restart the loop when if it ends
|
||||||
FrequencyLoop.new(@window_frequency).loop do |i|
|
FrequencyLoop.new(@frequency).loop do |i|
|
||||||
@window.clear(SF::Color::Black)
|
@window.clear(SF::Color::Black)
|
||||||
handle_events
|
handle_events
|
||||||
ImGui::SFML.update(@window, @delta_clock.restart)
|
ImGui::SFML.update(@window, @delta_clock.restart)
|
||||||
|
@ -248,90 +142,207 @@ class Game
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include ImGui::Helper
|
||||||
|
|
||||||
|
# will yield the value
|
||||||
|
def draw_slider(label : String, data : Float64, max : Float64 = 100.0, min : Float64 = -100.0, flags : ImGui::ImGuiSliderFlags = ImGui::ImGuiSliderFlags::NoRoundToFormat, &)
|
||||||
|
ptr = pointerof(data)
|
||||||
|
if ImGui.slider_scalar(
|
||||||
|
label: label,
|
||||||
|
p_data: ptr,
|
||||||
|
p_min: min,
|
||||||
|
p_max: max,
|
||||||
|
flags: flags,
|
||||||
|
)
|
||||||
|
yield data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# will yield the value
|
||||||
|
def draw_input(label : String, data : Float64, max : Float64 = 100.0, min : Float64 = -100.0, flags : ImGui::ImGuiInputTextFlags = ImGui::ImGuiInputTextFlags::EnterReturnsTrue, &)
|
||||||
|
ptr = pointerof(data)
|
||||||
|
if ImGui.input_scalar(
|
||||||
|
label: label,
|
||||||
|
p_data: ptr,
|
||||||
|
flags: flags,
|
||||||
|
)
|
||||||
|
data = max if data > max
|
||||||
|
data = min if data < min
|
||||||
|
yield data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def accelerate_by_tick(movable_body : Gravity::MovingBody)
|
def accelerate_by_tick(movable_body : Gravity::MovingBody)
|
||||||
gravity_field = @g.acceleration(movable_body.position)
|
gravity_field = @g.acceleration(movable_body.position)
|
||||||
acceleration = movable_body.acceleration + gravity_field
|
acceleration = movable_body.acceleration + gravity_field
|
||||||
Physics.move_by_one_tick!(@timer.tick, movable_body.position, movable_body.speed, acceleration)
|
Physics.move_by_one_tick!(@timer.tick, movable_body.position, movable_body.speed, acceleration)
|
||||||
end
|
end
|
||||||
|
|
||||||
include PhysicsSandboxDraw
|
def draw_pilot
|
||||||
include PhysicsSandboxGrid
|
draw_table(title: "Trusters", headers: {"Name", "X", "Y", "unit"}) do
|
||||||
include PhysicsSandboxCadran
|
draw_table_line(
|
||||||
|
"Analogic acceleration",
|
||||||
|
-> {
|
||||||
|
draw_slider(
|
||||||
|
label: "###acceleration_analogue_x",
|
||||||
|
data: @vessel.acceleration[0],
|
||||||
|
flags: (
|
||||||
|
ImGui::ImGuiSliderFlags::Logarithmic
|
||||||
|
),
|
||||||
|
) do |value|
|
||||||
|
puts "x acceleration was #{@vessel.acceleration[0]}"
|
||||||
|
@vessel.acceleration[0] = value
|
||||||
|
puts "x acceleration is now #{@vessel.acceleration[0]}"
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-> {
|
||||||
|
draw_slider(
|
||||||
|
label: "###acceleration_analogue_y",
|
||||||
|
data: @vessel.acceleration[1],
|
||||||
|
flags: (
|
||||||
|
ImGui::ImGuiSliderFlags::Logarithmic
|
||||||
|
),
|
||||||
|
) do |value|
|
||||||
|
Log.debug "x acceleration was #{@vessel.acceleration[1]}"
|
||||||
|
@vessel.acceleration[1] = value
|
||||||
|
Log.debug "x acceleration is now #{@vessel.acceleration[1]}"
|
||||||
|
end
|
||||||
|
},
|
||||||
|
"m/s²",
|
||||||
|
)
|
||||||
|
# automatically reset acceleration to 0, used for maneuvers
|
||||||
|
draw_table_line(
|
||||||
|
"Low thrust",
|
||||||
|
-> {
|
||||||
|
draw_slider(
|
||||||
|
label: "###acceleration_low_analogic_x",
|
||||||
|
data: @vessel.acceleration[0],
|
||||||
|
) do |value|
|
||||||
|
if !@ui_states[:acceleration_log_analogic_pressed][0]
|
||||||
|
@ui_states[:acceleration_log_analogic_save][0] = @vessel.acceleration[0]
|
||||||
|
end
|
||||||
|
@ui_states[:acceleration_log_analogic_pressed][0] = true
|
||||||
|
@vessel.acceleration[0] = value
|
||||||
|
@ui_events_handler.add(UI::EventHandler::Event::Type::MouseButtonReleased) do |callback, _|
|
||||||
|
@ui_events_handler.remove(UI::EventHandler::Event::Type::MouseButtonReleased, callback)
|
||||||
|
@ui_states[:acceleration_log_analogic_pressed][0] = false
|
||||||
|
@vessel.acceleration[0] = @ui_states[:acceleration_log_analogic_save][0]
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-> {
|
||||||
|
draw_slider(
|
||||||
|
label: "###acceleration_low_analogic_y",
|
||||||
|
data: @vessel.acceleration[1],
|
||||||
|
) do |value|
|
||||||
|
if !@ui_states[:acceleration_log_analogic_pressed][0]
|
||||||
|
@ui_states[:acceleration_log_analogic_save][0] = @vessel.acceleration[1]
|
||||||
|
end
|
||||||
|
@ui_states[:acceleration_log_analogic_pressed][0] = true
|
||||||
|
@vessel.acceleration[1] = value
|
||||||
|
|
||||||
|
lock_callback = @ui_events_handler.add(UI::EventHandler::Event::Type::KeyPressed) do |callback, event|
|
||||||
|
puts "Keypressed #{event}"
|
||||||
|
@ui_states[:acceleration_log_analogic_save][0] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
@ui_events_handler.add(UI::EventHandler::Event::Type::MouseButtonReleased) do |callback, _|
|
||||||
|
@ui_events_handler.remove(UI::EventHandler::Event::Type::MouseButtonReleased, callback)
|
||||||
|
@ui_events_handler.remove(UI::EventHandler::Event::Type::MouseButtonReleased, lock_callback)
|
||||||
|
@ui_states[:acceleration_log_analogic_pressed][0] = false
|
||||||
|
@vessel.acceleration[1] = @ui_states[:acceleration_log_analogic_save][0]
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
},
|
||||||
|
"m/s²",
|
||||||
|
)
|
||||||
|
draw_table_line(
|
||||||
|
"Computer acceleration",
|
||||||
|
-> {
|
||||||
|
draw_input(
|
||||||
|
label: "###acceleration_digit_x",
|
||||||
|
data: @ui_states[:acceleration_digit][:x],
|
||||||
|
) do |value|
|
||||||
|
@ui_states[:acceleration_digit][:x] = value
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-> {
|
||||||
|
draw_input(
|
||||||
|
label: "###acceleration_digit_y",
|
||||||
|
data: @ui_states[:acceleration_digit][:y],
|
||||||
|
) do |value|
|
||||||
|
@ui_states[:acceleration_digit][:y] = value
|
||||||
|
end
|
||||||
|
},
|
||||||
|
"m/s²",
|
||||||
|
)
|
||||||
|
draw_table_line(
|
||||||
|
"",
|
||||||
|
-> {
|
||||||
|
if ImGui.button("Comfirm")
|
||||||
|
@vessel.acceleration[0] = @ui_states[:acceleration_digit][:x]
|
||||||
|
@vessel.acceleration[1] = @ui_states[:acceleration_digit][:y]
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-> {
|
||||||
|
if ImGui.button("Reset")
|
||||||
|
@ui_states[:acceleration_digit][:x] = @vessel.acceleration[0]
|
||||||
|
@ui_states[:acceleration_digit][:y] = @vessel.acceleration[1]
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-> {
|
||||||
|
if ImGui.button("Zero")
|
||||||
|
@ui_states[:acceleration_digit][:x] = 0.0
|
||||||
|
@ui_states[:acceleration_digit][:y] = 0.0
|
||||||
|
end
|
||||||
|
},
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw_navigation(gravity_field)
|
||||||
|
# ImGui.text "Total Accele is : [#{acceleration[0].round(4)}, #{acceleration[1].round(4)}] m/s²"
|
||||||
|
ImGui.text "Pulse Accele is : [#{@vessel.acceleration[0].round(4)}, #{@vessel.acceleration[1].round(4)}] m/s²"
|
||||||
|
ImGui.text "GraviticBody is : [#{gravity_field[0].round(4)}, #{gravity_field[1].round(4)}] m/s²"
|
||||||
|
ImGui.text "Current speed is : #{@vessel.speed.magnitude.round(4)} m/s"
|
||||||
|
|
||||||
|
draw_table(title: "Actual vectors", headers: {"Name", "X", "Y", "unit"}) do
|
||||||
|
draw_table_line "Position", @vessel.position[0].round(3).to_s, @vessel.position[1].round(3).to_s, "m"
|
||||||
|
draw_table_line "Speed", @vessel.speed[0].round(3).to_s, @vessel.speed[1].round(3).to_s, "m/s"
|
||||||
|
draw_table_line "Acceleration", @vessel.acceleration[0].round(3).to_s, @vessel.acceleration[1].round(3).to_s, "m/s²"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def execute(tick : Int)
|
def execute(tick : Int)
|
||||||
@vessel.execute_orders!
|
@timer.timeskip!(nanoseconds: @frequency_nano_rate)
|
||||||
|
|
||||||
@timer.timeskip!(nanoseconds: accuracy_frequency_nano_rate)
|
|
||||||
@time_passed += accuracy_frequency_nano_rate.nanoseconds
|
|
||||||
@vessel.pilot.apply_current_mode
|
|
||||||
gravity_field = @g.acceleration(@bodies[:vessel].position)
|
gravity_field = @g.acceleration(@bodies[:vessel].position)
|
||||||
@graviting.each { |graviting| accelerate_by_tick(graviting) }
|
accelerate_by_tick(@bodies[:vessel])
|
||||||
|
accelerate_by_tick(@bodies[:pla1])
|
||||||
|
accelerate_by_tick(@bodies[:pla2])
|
||||||
|
# accelerate_by_tick(@bodies[:star])
|
||||||
|
|
||||||
draw_bridge(gravity_field)
|
if ImGui.begin("Bridge")
|
||||||
draw_bodies
|
if ImGui.tree_node_ex("Navigation", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
||||||
|
draw_navigation(gravity_field)
|
||||||
|
ImGui.tree_pop
|
||||||
|
end
|
||||||
|
|
||||||
object_position =
|
if ImGui.tree_node_ex("Pilot", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
||||||
if @vessel.navigation.zoom_select == 0
|
draw_pilot
|
||||||
@vessel.navigation.zoom_position = @vessel.gravity_body.position
|
ImGui.tree_pop
|
||||||
elsif @vessel.navigation.zoom_select == 1
|
|
||||||
@vessel.navigation.zoom_position = @star.gravity_body.position
|
|
||||||
elsif @vessel.navigation.zoom_select == 2
|
|
||||||
@vessel.navigation.zoom_position
|
|
||||||
else
|
|
||||||
@vessel.navigation.zoom_position = @screen_center_position
|
|
||||||
end
|
end
|
||||||
@center_position = object_position
|
|
||||||
@view.set_center(
|
|
||||||
@center_position[0],
|
|
||||||
@center_position[1],
|
|
||||||
)
|
|
||||||
if @zoom != @vessel.navigation.zoom_ratio
|
|
||||||
@view.zoom @zoom / @vessel.navigation.zoom_ratio
|
|
||||||
@zoom = @vessel.navigation.zoom_ratio
|
|
||||||
if @zoom == 1.0
|
|
||||||
@view.viewport = default_viewport
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@window.view = @view
|
|
||||||
|
|
||||||
@projectables.each { |projectable| draw(projectable.sf_shape) }
|
ImGui.end
|
||||||
draw_all_trajectories
|
end
|
||||||
# draw_grid
|
|
||||||
@vessel.navigation.cadran.radius.each do |radius|
|
@window.draw(@vessel.sf_shape)
|
||||||
draw_cadran(center: @vessel.gravity_body.position, radius: radius)
|
@window.draw(@star.sf_shape)
|
||||||
|
@window.draw(@pla1.sf_shape)
|
||||||
|
@window.draw(@pla2.sf_shape)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw_all_trajectories
|
Game.new(framerate: 60, width: 800, height: 600).execute_loop
|
||||||
t = Trajectory.new(body: @vessel_p.gravity_body, gravity_field: @g)
|
|
||||||
t_with_gravity = t.compute(steps: 1000, accuracy: 500.millisecond * @timescale)
|
|
||||||
draw_trajectory({255, 0, 0}, t_with_gravity)
|
|
||||||
|
|
||||||
t = Trajectory.new(body: @vessel_p.gravity_body, gravity_field: Gravity::Field(2).zero)
|
|
||||||
t_without_gravity = t.compute(steps: 20, accuracy: 5.second * @timescale)
|
|
||||||
draw_trajectory({255, 255, 0}, t_without_gravity)
|
|
||||||
|
|
||||||
t = Trajectory.new(body: @pla1.gravity_body, gravity_field: @g)
|
|
||||||
t_without_gravity = t.compute(steps: 100, accuracy: 10.second * @timescale)
|
|
||||||
draw_trajectory({255, 0, 0}, t_without_gravity)
|
|
||||||
|
|
||||||
t = Trajectory.new(body: @pla2.gravity_body, gravity_field: @g)
|
|
||||||
t_without_gravity = t.compute(steps: 100, accuracy: 10.second * @timescale)
|
|
||||||
draw_trajectory({0, 0, 255}, t_without_gravity)
|
|
||||||
|
|
||||||
t = Trajectory.new(body: @pla3.gravity_body, gravity_field: @g)
|
|
||||||
t_without_gravity = t.compute(steps: 100, accuracy: 10.second * @timescale)
|
|
||||||
draw_trajectory({0, 255, 0}, t_without_gravity)
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw_trajectory(color : Tuple(Int32, Int32, Int32), trajectory)
|
|
||||||
trajectory.each do |point|
|
|
||||||
sf_point = SF::CircleShape.new(2)
|
|
||||||
sf_point.fill_color = SF.color(*color)
|
|
||||||
sf_point.position = SF.vector2(point[0], point[1])
|
|
||||||
draw(sf_point)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Game.new(framerate: 120, width: 800, height: 600).execute_loop
|
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
module PhysicsSandboxCadran
|
|
||||||
|
|
||||||
def add_line(lines, p1 : SF::Vector2f, p2 : SF::Vector2f, color : SF::Color = SF::Color::White)
|
|
||||||
line1 = SF::VertexBuffer.new(SF::LineStrip)
|
|
||||||
line1.create(2)
|
|
||||||
line1.update(
|
|
||||||
Slice[
|
|
||||||
SF::Vertex.new(position: p1, color: color),
|
|
||||||
SF::Vertex.new(position: p2, color: color),
|
|
||||||
], 0)
|
|
||||||
lines << line1
|
|
||||||
end
|
|
||||||
|
|
||||||
CADRAN_COLOR = SF::Color.new(230, 230, 230, 120)
|
|
||||||
def draw_cadran(center : Vector(Float64, 2), radius : Number)
|
|
||||||
circle = SF::CircleShape.new(radius: radius, point_count: 100)
|
|
||||||
circle.outline_color = CADRAN_COLOR
|
|
||||||
circle.outline_thickness = 1
|
|
||||||
center_position_offset = {
|
|
||||||
center[0] - radius,
|
|
||||||
center[1] - radius,
|
|
||||||
}
|
|
||||||
circle.position = center_position_offset
|
|
||||||
circle.fill_color = SF::Color::Transparent
|
|
||||||
draw(circle)
|
|
||||||
|
|
||||||
lines = Array(SF::VertexBuffer).new
|
|
||||||
add_line(
|
|
||||||
lines: lines,
|
|
||||||
p1: SF.vector2f(center[0] - 2**(1/2)/2 * radius, center[1] - 2**(1/2)/2 * radius),
|
|
||||||
p2: SF.vector2f(center[0] + 2**(1/2)/2 * radius, center[1] + 2**(1/2)/2 * radius),
|
|
||||||
color: CADRAN_COLOR,
|
|
||||||
)
|
|
||||||
add_line(
|
|
||||||
lines: lines,
|
|
||||||
p1: SF.vector2f(center[0] + 2**(1/2)/2 * radius, center[1] - 2**(1/2)/2 * radius),
|
|
||||||
p2: SF.vector2f(center[0] - 2**(1/2)/2 * radius, center[1] + 2**(1/2)/2 * radius),
|
|
||||||
color: CADRAN_COLOR,
|
|
||||||
)
|
|
||||||
add_line(
|
|
||||||
lines: lines,
|
|
||||||
p1: SF.vector2f(center[0] + radius, center[1]),
|
|
||||||
p2: SF.vector2f(center[0] - radius, center[1]),
|
|
||||||
color: CADRAN_COLOR,
|
|
||||||
)
|
|
||||||
add_line(
|
|
||||||
lines: lines,
|
|
||||||
p1: SF.vector2f(center[0], center[1] - radius),
|
|
||||||
p2: SF.vector2f(center[0], center[1] + radius),
|
|
||||||
color: CADRAN_COLOR,
|
|
||||||
)
|
|
||||||
|
|
||||||
lines.each { |line| draw(line) }
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
|
@ -1,387 +0,0 @@
|
||||||
module PhysicsSandboxDraw
|
|
||||||
|
|
||||||
include ImGui::Helper
|
|
||||||
|
|
||||||
# will yield the value
|
|
||||||
def draw_slider(label : String, data : Float64, max : Float64 = 100.0, min : Float64 = -100.0, flags : ImGui::ImGuiSliderFlags = ImGui::ImGuiSliderFlags::NoRoundToFormat, &)
|
|
||||||
ptr = pointerof(data)
|
|
||||||
if ImGui.slider_scalar(
|
|
||||||
label: label,
|
|
||||||
p_data: ptr,
|
|
||||||
p_min: min,
|
|
||||||
p_max: max,
|
|
||||||
flags: flags,
|
|
||||||
)
|
|
||||||
yield data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# will yield the value
|
|
||||||
def draw_input(label : String, data : Float64, max : Float64 = 100.0, min : Float64 = -100.0, flags : ImGui::ImGuiInputTextFlags = ImGui::ImGuiInputTextFlags::EnterReturnsTrue, &)
|
|
||||||
ptr = pointerof(data)
|
|
||||||
if ImGui.input_scalar(
|
|
||||||
label: label,
|
|
||||||
p_data: ptr,
|
|
||||||
flags: flags,
|
|
||||||
)
|
|
||||||
data = max if data > max
|
|
||||||
data = min if data < min
|
|
||||||
yield data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw_captain
|
|
||||||
ImGui.text "Time passed: #{@time_passed}"
|
|
||||||
draw_slider(
|
|
||||||
label: "Set timescale###set_timescale",
|
|
||||||
data: @timescale,
|
|
||||||
min: 0.1,
|
|
||||||
max: 60,
|
|
||||||
# max: 3600 * 24 * 7,
|
|
||||||
flags: (
|
|
||||||
ImGui::ImGuiSliderFlags::Logarithmic
|
|
||||||
),
|
|
||||||
) do |value|
|
|
||||||
@timescale = value
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw_pilot
|
|
||||||
draw_table(title: "Trusters", headers: {"Name", "X", "Y", "unit"}) do
|
|
||||||
draw_table_line(
|
|
||||||
"Analogic acceleration",
|
|
||||||
->{
|
|
||||||
draw_slider(
|
|
||||||
label: "###acceleration_analogue_x",
|
|
||||||
data: @vessel.gravity_body.acceleration[0],
|
|
||||||
min: -@vessel.pilot.max_thrust,
|
|
||||||
max: @vessel.pilot.max_thrust,
|
|
||||||
flags: (
|
|
||||||
ImGui::ImGuiSliderFlags::Logarithmic
|
|
||||||
),
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.order(priority: 10, group: :acceleration_x) do
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(0, value)
|
|
||||||
@vessel.pilot.permanent_modes.clear
|
|
||||||
@vessel.pilot.permanent_modes << Vessel::Pilot::Mode::Constant
|
|
||||||
@vessel.pilot.low_thrust_pressed = false
|
|
||||||
@vessel.pilot.low_thrust_save = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
draw_slider(
|
|
||||||
label: "###acceleration_analogue_y",
|
|
||||||
data: @vessel.gravity_body.acceleration[1],
|
|
||||||
min: -@vessel.pilot.max_thrust,
|
|
||||||
max: @vessel.pilot.max_thrust,
|
|
||||||
flags: (
|
|
||||||
ImGui::ImGuiSliderFlags::Logarithmic
|
|
||||||
),
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.order(priority: 10, group: :acceleration_y) do
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(1, value)
|
|
||||||
@vessel.pilot.permanent_modes.clear
|
|
||||||
@vessel.pilot.permanent_modes << Vessel::Pilot::Mode::Constant
|
|
||||||
@vessel.pilot.low_thrust_pressed = false
|
|
||||||
@vessel.pilot.low_thrust_save = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
"m/s²",
|
|
||||||
)
|
|
||||||
|
|
||||||
# automatically reset acceleration to 0, used for maneuvers
|
|
||||||
draw_table_line(
|
|
||||||
"Low thrust",
|
|
||||||
->{
|
|
||||||
draw_slider(
|
|
||||||
label: "###acceleration_low_analogic_x",
|
|
||||||
data: @vessel.gravity_body.acceleration[0],
|
|
||||||
min: -@vessel.pilot.max_thrust,
|
|
||||||
max: @vessel.pilot.max_thrust,
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.order(priority: 10, group: :maneuvers) do
|
|
||||||
@vessel.pilot.temporary_modes << Vessel::Pilot::Mode::Pulse
|
|
||||||
if !@vessel.pilot.low_thrust_pressed
|
|
||||||
@vessel.pilot.low_thrust_save = @vessel.gravity_body.acceleration[0]
|
|
||||||
end
|
|
||||||
@vessel.pilot.low_thrust_pressed = true
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(0, value)
|
|
||||||
lock_callback = @ui_events_handler.add(SF::Event::KeyPressed) do |callback, event|
|
|
||||||
if event.as(SF::Event::KeyPressed).code == SF::Keyboard::Key::L
|
|
||||||
@vessel.pilot.low_thrust_save = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ui_events_handler.add(SF::Event::MouseButtonReleased) do |callback|
|
|
||||||
@ui_events_handler.remove(SF::Event::MouseButtonReleased, callback)
|
|
||||||
@ui_events_handler.remove(SF::Event::MouseButtonReleased, lock_callback)
|
|
||||||
@vessel.pilot.low_thrust_pressed = false
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(0, @vessel.pilot.low_thrust_save)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
draw_slider(
|
|
||||||
label: "###acceleration_low_analogic_y",
|
|
||||||
data: @vessel.gravity_body.acceleration[1],
|
|
||||||
min: -@vessel.pilot.max_thrust,
|
|
||||||
max: @vessel.pilot.max_thrust,
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.order(priority: 10, group: :maneuvers) do
|
|
||||||
@vessel.pilot.temporary_modes << Vessel::Pilot::Mode::Pulse
|
|
||||||
if !@vessel.pilot.low_thrust_pressed
|
|
||||||
@vessel.pilot.low_thrust_save = @vessel.gravity_body.acceleration[1]
|
|
||||||
end
|
|
||||||
@vessel.pilot.low_thrust_pressed = true
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(1, value)
|
|
||||||
lock_callback = @ui_events_handler.add(SF::Event::KeyPressed) do |callback, event|
|
|
||||||
if event.as(SF::Event::KeyPressed).code == SF::Keyboard::Key::L
|
|
||||||
@vessel.pilot.low_thrust_save = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ui_events_handler.add(SF::Event::MouseButtonReleased) do |callback|
|
|
||||||
@ui_events_handler.remove(SF::Event::MouseButtonReleased, callback)
|
|
||||||
@ui_events_handler.remove(SF::Event::MouseButtonReleased, lock_callback)
|
|
||||||
@vessel.pilot.low_thrust_pressed = false
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(1, @vessel.pilot.low_thrust_save)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
"m/s²",
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_table_line(
|
|
||||||
"Computer acceleration",
|
|
||||||
->{
|
|
||||||
draw_input(
|
|
||||||
label: "###acceleration_digit_x",
|
|
||||||
data: @vessel.pilot.computer_input_thrust[0],
|
|
||||||
min: -@vessel.pilot.max_thrust,
|
|
||||||
max: @vessel.pilot.max_thrust,
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.order(priority: 9, group: :maneuvers) do
|
|
||||||
@vessel.pilot.computer_input_thrust[0] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
draw_input(
|
|
||||||
label: "###acceleration_digit_y",
|
|
||||||
data: @vessel.pilot.computer_input_thrust[1],
|
|
||||||
min: -@vessel.pilot.max_thrust,
|
|
||||||
max: @vessel.pilot.max_thrust,
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.order(priority: 9, group: :maneuvers) do
|
|
||||||
@vessel.pilot.computer_input_thrust[1] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
"m/s²",
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_table_line(
|
|
||||||
"[Security] Max thurst",
|
|
||||||
->{
|
|
||||||
draw_input(
|
|
||||||
label: "###pilot_input_max_thurst",
|
|
||||||
data: @vessel.pilot.max_thrust,
|
|
||||||
min: 9,
|
|
||||||
max: 900,
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.max_thrust = value
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
draw_slider(
|
|
||||||
label: "###pilot_slider_max_thurst",
|
|
||||||
data: @vessel.pilot.max_thrust,
|
|
||||||
min: 9,
|
|
||||||
max: 900,
|
|
||||||
) do |value|
|
|
||||||
@vessel.pilot.max_thrust = value
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
ImGui.text @vessel.pilot.max_thrust > 20 ? "Inertia Dampers needed" : "No danger"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Manual reset
|
|
||||||
draw_table_line(
|
|
||||||
"",
|
|
||||||
->{
|
|
||||||
if ImGui.button("Comfirm")
|
|
||||||
@vessel.pilot.order(priority: 100, group: :maneuvers) do
|
|
||||||
@vessel.pilot.permanent_modes.clear
|
|
||||||
@vessel.pilot.permanent_modes << Vessel::Pilot::Mode::Constant
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(@vessel.pilot.computer_input_thrust)
|
|
||||||
# @vessel.pilot.set_exact_acceleration_axis(0, @vessel.pilot.computer_input_thrust[0])
|
|
||||||
# @vessel.pilot.set_exact_acceleration_axis(1, @vessel.pilot.computer_input_thrust[1])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
if ImGui.button("Reset")
|
|
||||||
@vessel.pilot.order(priority: 100, group: :prepare) do
|
|
||||||
@vessel.pilot.permanent_modes.clear
|
|
||||||
@vessel.pilot.permanent_modes << Vessel::Pilot::Mode::Constant
|
|
||||||
@vessel.pilot.set_exact_acceleration_axis(@vessel.pilot.computer_input_thrust)
|
|
||||||
# @vessel.pilot.computer_input_thrust[0] = @vessel.gravity_body.acceleration[0]
|
|
||||||
# @vessel.pilot.computer_input_thrust[1] = @vessel.gravity_body.acceleration[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
->{
|
|
||||||
if ImGui.button("Zero")
|
|
||||||
@vessel.pilot.order(priority: 10, group: :prepare) do
|
|
||||||
@vessel.pilot.computer_input_thrust[0] = 0.0
|
|
||||||
@vessel.pilot.computer_input_thrust[1] = 0.0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_table_line(
|
|
||||||
"",
|
|
||||||
# AntiGrav
|
|
||||||
->{
|
|
||||||
if !@vessel.pilot.permanent_modes.includes?(Vessel::Pilot::Mode::AntiGrav)
|
|
||||||
if ImGui.button("Anti-grav")
|
|
||||||
@vessel.pilot.order(priority: 10, group: :fly_mode) do
|
|
||||||
@vessel.pilot.permanent_modes << Vessel::Pilot::Mode::AntiGrav
|
|
||||||
# @vessel.gravity_body.acceleration[0] = @vessel.gravity[0]
|
|
||||||
# @vessel.gravity_body.acceleration[1] = @vessel.gravity[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if ImGui.button("Disable anti-grav")
|
|
||||||
@vessel.pilot.order(priority: 11, group: :fly_mode) do
|
|
||||||
if @vessel.pilot.permanent_modes.includes?(Vessel::Pilot::Mode::AntiGrav)
|
|
||||||
@vessel.pilot.permanent_modes.delete Vessel::Pilot::Mode::AntiGrav
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
|
|
||||||
# AntiSpeed
|
|
||||||
->{
|
|
||||||
if !@vessel.pilot.permanent_modes.includes?(Vessel::Pilot::Mode::AntiSpeed)
|
|
||||||
if ImGui.button("Anti-speed")
|
|
||||||
@vessel.pilot.order(priority: 10, group: :fly_mode) do
|
|
||||||
@vessel.pilot.permanent_modes << Vessel::Pilot::Mode::AntiSpeed
|
|
||||||
@vessel.pilot.current_selected_body = @star.gravity_body
|
|
||||||
# @vessel.antispeed[0] = @vessel.speed[0]
|
|
||||||
# @vessel.antispeed[1] = @vessel.speed[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if ImGui.button("Disable anti-speed")
|
|
||||||
@vessel.pilot.order(priority: 11, group: :fly_mode) do
|
|
||||||
if @vessel.pilot.permanent_modes << Vessel::Pilot::Mode::AntiSpeed
|
|
||||||
@vessel.pilot.permanent_modes.delete Vessel::Pilot::Mode::AntiSpeed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw_navigation(gravity_field)
|
|
||||||
# ImGui.text "Total Accele is : [#{acceleration[0].round(4)}, #{acceleration[1].round(4)}] m/s²"
|
|
||||||
draw_table(title: "Applied forces", headers: {"Name", "X", "Y", "unit"}) do
|
|
||||||
draw_table_line "Pulse Accele", "#{@vessel.gravity_body.acceleration[0].round(4)}", "#{@vessel.gravity_body.acceleration[1].round(4)}", "m/s²"
|
|
||||||
draw_table_line "Gravitic Field", "#{gravity_field[0].round(4)}", "#{gravity_field[1].round(4)}", "m/s²"
|
|
||||||
end
|
|
||||||
ImGui.text "Current speed #{@vessel.gravity_body.speed.magnitude.round(4)} m/s"
|
|
||||||
ImGui.text ""
|
|
||||||
|
|
||||||
relative_speed = @vessel.gravity_body.speed - @selected_projectable.speed
|
|
||||||
draw_table(title: "Actual vectors", headers: {"Name", "X", "Y", "unit"}) do
|
|
||||||
draw_table_line "Position", @vessel.gravity_body.position[0].round(3).to_s, @vessel.gravity_body.position[1].round(3).to_s, "m"
|
|
||||||
draw_table_line "Speed", @vessel.gravity_body.speed[0].round(3).to_s, @vessel.gravity_body.speed[1].round(3).to_s, "m/s"
|
|
||||||
draw_table_line "Acceleration", @vessel.gravity_body.acceleration[0].round(3).to_s, @vessel.gravity_body.acceleration[1].round(3).to_s, "m/s²"
|
|
||||||
draw_table_line "Selected body", "#{@selected_projectable.position[0].round(4)}", "#{@selected_projectable.position[1].round(4)}", "m"
|
|
||||||
draw_table_line "", @selected_projectable.name, "", ""
|
|
||||||
draw_table_line "Relative speed", "#{relative_speed[0].round(4)}", "#{relative_speed[1].round(4)}", "m/s"
|
|
||||||
end
|
|
||||||
ImGui.text ""
|
|
||||||
|
|
||||||
ImGui.text "Zoom on"
|
|
||||||
ImGui.radio_button("vessel##navigation_zoom", pointerof(@vessel.navigation.zoom_select), 0)
|
|
||||||
ImGui.same_line
|
|
||||||
ImGui.radio_button("star##navigation_zoom", pointerof(@vessel.navigation.zoom_select), 1)
|
|
||||||
ImGui.same_line
|
|
||||||
ImGui.radio_button("fixed##navigation_zoom", pointerof(@vessel.navigation.zoom_select), 2)
|
|
||||||
ImGui.same_line
|
|
||||||
ImGui.radio_button("zero##navigation_zoom", pointerof(@vessel.navigation.zoom_select), 3)
|
|
||||||
draw_slider(
|
|
||||||
label: "###cadran_slide_zoom_ratio",
|
|
||||||
data: @vessel.navigation.zoom_ratio,
|
|
||||||
max: 10,
|
|
||||||
min: 0.001,
|
|
||||||
flags: ImGui::ImGuiSliderFlags::Logarithmic,
|
|
||||||
) do |value|
|
|
||||||
@vessel.navigation.zoom_ratio = value
|
|
||||||
end
|
|
||||||
|
|
||||||
ImGui.text "Cadran"
|
|
||||||
draw_slider(
|
|
||||||
label: "###cadran_slide_radius",
|
|
||||||
data: @vessel.navigation.cadran.radius[0],
|
|
||||||
max: 10.0**10,
|
|
||||||
min: 0.0,
|
|
||||||
flags: ImGui::ImGuiSliderFlags::Logarithmic,
|
|
||||||
) do |value|
|
|
||||||
@vessel.navigation.cadran.radius[0] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw_bridge(gravity_field)
|
|
||||||
if ImGui.begin("Bridge")
|
|
||||||
if ImGui.tree_node_ex("Captain", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
|
||||||
draw_captain
|
|
||||||
ImGui.tree_pop
|
|
||||||
end
|
|
||||||
|
|
||||||
if ImGui.tree_node_ex("Navigation", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
|
||||||
draw_navigation(gravity_field)
|
|
||||||
ImGui.tree_pop
|
|
||||||
end
|
|
||||||
|
|
||||||
if ImGui.tree_node_ex("Pilot", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
|
||||||
draw_pilot
|
|
||||||
ImGui.tree_pop
|
|
||||||
end
|
|
||||||
|
|
||||||
ImGui.end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def draw_bodies
|
|
||||||
if ImGui.begin("Bodies")
|
|
||||||
if ImGui.tree_node_ex("Bodies", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
|
||||||
|
|
||||||
draw_table(title: "Bodies", headers: {"Name", "X", "Y"}) do
|
|
||||||
@projectables.each do |projectable|
|
|
||||||
draw_table_line projectable.name, projectable.position[0].to_s, projectable.position[1].to_s
|
|
||||||
end
|
|
||||||
if !(click_coordinates = @click_coordinates).nil?
|
|
||||||
draw_table_line "[click]", click_coordinates[0].to_s, click_coordinates[1].to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ImGui.tree_pop
|
|
||||||
end
|
|
||||||
ImGui.end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
|
@ -1,115 +0,0 @@
|
||||||
module PhysicsSandboxGrid
|
|
||||||
|
|
||||||
MAIN_LINES_REAL_SPACE = 1000
|
|
||||||
SECONDARY_LINES_AMOUNT = 3
|
|
||||||
SECONDARY_LINES_SPACE = MAIN_LINES_REAL_SPACE / SECONDARY_LINES_AMOUNT
|
|
||||||
SECONDARY_LINES_COLOR = SF::Color.new(255, 255, 255, 30)
|
|
||||||
|
|
||||||
def absolute_coordinates(pos, offset)
|
|
||||||
SF.vector2f(pos[0] + offset[0], pos[1] + offset[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
# this one will be dynamic to evaluate distances
|
|
||||||
def draw_grid
|
|
||||||
main_lines = Array(SF::VertexBuffer).new
|
|
||||||
secondary_lines = Array(SF::VertexBuffer).new
|
|
||||||
width = @width
|
|
||||||
height = @height
|
|
||||||
max_size = {width, height}.max
|
|
||||||
offset = {
|
|
||||||
-@center_position[0] + (@center_position[0] // MAIN_LINES_REAL_SPACE) * MAIN_LINES_REAL_SPACE,
|
|
||||||
-@center_position[1] + (@center_position[1] // MAIN_LINES_REAL_SPACE) * MAIN_LINES_REAL_SPACE,
|
|
||||||
}
|
|
||||||
|
|
||||||
main_lines_amount = (max_size / MAIN_LINES_REAL_SPACE).ceil.to_i32 + 1
|
|
||||||
start = -main_lines_amount // 2
|
|
||||||
stop = start + main_lines_amount
|
|
||||||
pp({
|
|
||||||
center_position: @center_position,
|
|
||||||
offset: offset,
|
|
||||||
max_size: max_size,
|
|
||||||
start: start,
|
|
||||||
stop: stop,
|
|
||||||
main_lines_amount: main_lines_amount,
|
|
||||||
})
|
|
||||||
|
|
||||||
(start..stop).each do |index|
|
|
||||||
main_line_v = SF::VertexBuffer.new(SF::LineStrip)
|
|
||||||
main_line_h = SF::VertexBuffer.new(SF::LineStrip)
|
|
||||||
main_lines << main_line_v
|
|
||||||
main_lines << main_line_h
|
|
||||||
main_line_v.create(2)
|
|
||||||
main_line_h.create(2)
|
|
||||||
x_start = MAIN_LINES_REAL_SPACE * index
|
|
||||||
x_end = width
|
|
||||||
y_start = MAIN_LINES_REAL_SPACE * index
|
|
||||||
y_end = height
|
|
||||||
# pp({
|
|
||||||
# center_position: @center_position,
|
|
||||||
# index: index,
|
|
||||||
# v_start: v_start,
|
|
||||||
# x_end: x_end,
|
|
||||||
# y_start: y_start,
|
|
||||||
# h_end: h_end,
|
|
||||||
# x_start: x_start,
|
|
||||||
# y_start: y_start,
|
|
||||||
# })
|
|
||||||
main_line_v_buffer_0 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({x_start, start * MAIN_LINES_REAL_SPACE}, offset),
|
|
||||||
)
|
|
||||||
main_line_v_buffer_1 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({x_start, y_end}, offset),
|
|
||||||
)
|
|
||||||
main_line_h_buffer_0 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({start * MAIN_LINES_REAL_SPACE, y_start}, offset),
|
|
||||||
)
|
|
||||||
main_line_h_buffer_1 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({x_end, y_start}, offset),
|
|
||||||
)
|
|
||||||
main_line_v_buffer = Slice[main_line_v_buffer_0, main_line_v_buffer_1]
|
|
||||||
main_line_h_buffer = Slice[main_line_h_buffer_0, main_line_h_buffer_1]
|
|
||||||
main_line_v.update(main_line_v_buffer, 0)
|
|
||||||
main_line_h.update(main_line_h_buffer, 0)
|
|
||||||
|
|
||||||
SECONDARY_LINES_AMOUNT.times do |subindex|
|
|
||||||
secondary_line_v = SF::VertexBuffer.new(SF::LineStrip)
|
|
||||||
secondary_line_h = SF::VertexBuffer.new(SF::LineStrip)
|
|
||||||
secondary_lines << secondary_line_v
|
|
||||||
secondary_lines << secondary_line_h
|
|
||||||
secondary_line_v.create(2)
|
|
||||||
secondary_line_h.create(2)
|
|
||||||
secondary_x_start = SECONDARY_LINES_SPACE * subindex
|
|
||||||
secondary_y_start = SECONDARY_LINES_SPACE * subindex
|
|
||||||
secondary_line_v_buffer_0 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({x_start + secondary_x_start, start * MAIN_LINES_REAL_SPACE}, offset),
|
|
||||||
color: SECONDARY_LINES_COLOR,
|
|
||||||
)
|
|
||||||
secondary_line_v_buffer_1 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({x_start + secondary_x_start, y_end}, offset),
|
|
||||||
color: SECONDARY_LINES_COLOR,
|
|
||||||
)
|
|
||||||
secondary_line_h_buffer_0 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({start * MAIN_LINES_REAL_SPACE, y_start + secondary_y_start}, offset),
|
|
||||||
color: SECONDARY_LINES_COLOR,
|
|
||||||
)
|
|
||||||
secondary_line_h_buffer_1 = SF::Vertex.new(
|
|
||||||
absolute_coordinates({x_end, y_start + secondary_y_start}, offset),
|
|
||||||
color: SECONDARY_LINES_COLOR,
|
|
||||||
)
|
|
||||||
secondary_line_v_buffer = Slice[secondary_line_v_buffer_0, secondary_line_v_buffer_1]
|
|
||||||
secondary_line_h_buffer = Slice[secondary_line_h_buffer_0, secondary_line_h_buffer_1]
|
|
||||||
secondary_line_v.update(secondary_line_v_buffer, 0)
|
|
||||||
secondary_line_h.update(secondary_line_h_buffer, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
main_lines.each do |line|
|
|
||||||
draw(line)
|
|
||||||
end
|
|
||||||
secondary_lines.each do |line|
|
|
||||||
draw(line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
|
@ -1,44 +1,89 @@
|
||||||
require "crsfml"
|
|
||||||
|
|
||||||
module UI
|
module UI
|
||||||
# `EventHandler` is able to accumulate callbacks that will be triggered every
|
class Event
|
||||||
# time a event of a given type is received.
|
getter :type, :code, :alt, :control, :shift, :system
|
||||||
class EventHandler
|
@type : Type
|
||||||
# this is shit, find a better way to implement a generic event
|
@code : String?
|
||||||
alias Event = SF::Event
|
@alt : String?
|
||||||
# A callback takes one argument (itself) and returns nothing
|
@control : String?
|
||||||
alias EventCallback = EventCallback, Event -> Nil
|
@shift : String?
|
||||||
|
@system : String?
|
||||||
|
|
||||||
# NOTE EventClass.class should return Class so it's not yet what I want
|
{% if @top_level.has_constant?(:SF) %}
|
||||||
def initialize
|
def initialize(event : SF::Event)
|
||||||
@events_callbacks = Hash(Event.class, Array(EventCallback)).new
|
@type = SF_TO_TYPE[event.class]? || Type::Unknown
|
||||||
|
@code = event.code
|
||||||
|
@alt = event.alt
|
||||||
|
@control = event.control
|
||||||
|
@shift = event.shift
|
||||||
|
@system = event.system
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
enum Type
|
||||||
|
KeyPressed
|
||||||
|
KeyReleased
|
||||||
|
MouseButtonPressed
|
||||||
|
MouseButtonReleased
|
||||||
|
Unknown
|
||||||
end
|
end
|
||||||
|
|
||||||
def event_callbacks(event : Event.class) : Array(EventCallback)
|
{% if @top_level.has_constant?(:SF) %}
|
||||||
@events_callbacks[event] ||= Array(EventCallback).new
|
# can this be a named tuple instead ?
|
||||||
|
SF_TO_TYPE = {
|
||||||
|
SF::Event::KeyPressed => Type::KeyPressed,
|
||||||
|
SF::Event::KeyReleased => Type::KeyReleased,
|
||||||
|
SF::Event::MouseButtonPressed => Type::MouseButtonPressed,
|
||||||
|
SF::Event::MouseButtonReleased => Type::MouseButtonReleased,
|
||||||
|
}
|
||||||
|
{% end %}
|
||||||
|
# `Event::Handler` is able to accumulate callbacks that will be triggered every
|
||||||
|
# time a event of a given type is received.
|
||||||
|
class Handler
|
||||||
|
# A callback takes one argument (itself) and returns nothing
|
||||||
|
alias Callback = Callback, Event -> Nil
|
||||||
|
|
||||||
|
# NOTE Event.class should return Class so it's not yet what I want
|
||||||
|
def initialize
|
||||||
|
@events_callbacks = Hash(Type, Array(Callback)).new
|
||||||
|
end
|
||||||
|
|
||||||
|
def event_callbacks(event : Type) : Array(Callback)
|
||||||
|
@events_callbacks[event] ||= Array(Callback).new
|
||||||
@events_callbacks[event]
|
@events_callbacks[event]
|
||||||
end
|
end
|
||||||
|
|
||||||
def add(event : Event.class, &block : EventCallback, Event -> Nil)
|
def add(event_type : Type, &block : Callback, Event -> Nil)
|
||||||
event_callbacks(event) << block
|
event_callbacks(event_type) << block
|
||||||
block
|
block
|
||||||
end
|
end
|
||||||
|
|
||||||
def add(event : Event.class, block : EventCallback)
|
def add(event_type : Type, block : Callback)
|
||||||
event_callbacks(event) << block
|
event_callbacks(event_type) << block
|
||||||
block
|
block
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(event : Event.class, &block : EventCallback, Event -> Nil)
|
def remove(event_type : Type, &block : Callback, Event -> Nil)
|
||||||
event_callbacks(event).delete(block)
|
event_callbacks(event_type).delete(block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(event : Event.class, block : EventCallback)
|
def remove(event_type : Type, block : Callback)
|
||||||
event_callbacks(event).delete(block)
|
event_callbacks(event_type).delete(block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle(event : Event)
|
def handle(event : Event)
|
||||||
event_callbacks(event.class).each { |callback| callback.call(callback, event) }
|
event_callbacks(event.type).each { |callback| callback.call(callback, event) }
|
||||||
|
end
|
||||||
|
|
||||||
|
{% if @top_level.has_constant?(:SF) %}
|
||||||
|
def event_callbacks(event : SF::Event.class) : Array(Callback)
|
||||||
|
event = SF_TO_TYPE[event]? || Type::Unknown
|
||||||
|
event_callbacks(event)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(event : SF::Event)
|
||||||
|
handle(Event.new(event))
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
29
x.cr
Normal file
29
x.cr
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class EventHandler(EventClass)
|
||||||
|
macro make_callback(event_class)
|
||||||
|
alias Callback = Callback, {{ event_class }} -> Nil
|
||||||
|
end
|
||||||
|
make_callback(EventClass)
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@s = Hash(EventClass, Array(Callback)).new
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(
|
||||||
|
event,
|
||||||
|
&block : Callback, EventClass -> Nil
|
||||||
|
) : Callback, EventClass -> Nil
|
||||||
|
@s[event.class] ||= Array(Callback).new
|
||||||
|
@s[event.class] << block
|
||||||
|
block
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Event; end
|
||||||
|
class EventA < Event; end
|
||||||
|
class EventB < Event; end
|
||||||
|
|
||||||
|
h = EventHandler.new(Event)
|
||||||
|
a1 = h.add(EventA.new) { |cb, event| puts :EventA }
|
||||||
|
a2 = h.add(EventA.new) { |cb, event| puts :EventA2 }
|
||||||
|
b1 = h.add(EventB.new) { |cb, event| puts :EventB }
|
||||||
|
h.remove(a1)
|
Loading…
Reference in New Issue
Block a user