├── .gitignore
├── .idea
├── .name
├── codeStyleSettings.xml
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── dictionaries
│ └── niguo.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── runConfigurations
│ ├── Build_rust_oids_release.xml
│ ├── Check_rust_oids.xml
│ ├── Clean.xml
│ └── Run_debug_rust_oids.xml
└── vcs.xml
├── .project
├── .rustfmt.toml
├── .settings
├── com.github.rustdt.ide.core.prefs
├── com.github.rustdt.ide.ui.prefs
└── org.eclipse.ltk.core.refactoring.prefs
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── TODO.md
├── build.rs
├── img
├── capture_00003350.png
├── capture_00003401.png
├── capture_00003787.png
├── capture_0551.png
├── capture_1058.png
├── screenshot_001.png
├── screenshot_002.png
├── screenshot_003.png
├── screenshot_004.png
├── screenshot_005.png
├── screenshot_006.png
├── screenshot_007.png
└── screenshot_008.png
├── launch
├── rust-oids-clippy.launch
├── rust-oids-debug.launch
└── rust-oids.launch
├── lib
└── x64
│ └── portaudio.lib
├── resources
├── fonts
│ ├── FreeMono.ttf
│ └── FreeSans.ttf
├── minion_gene_pool.csv
└── shaders
│ ├── effects
│ ├── clip_luminance.frag
│ ├── compose_2.frag
│ ├── exponential_smooth.frag
│ ├── exposure_tone_map.frag
│ ├── gaussian_blur_horizontal.frag
│ ├── gaussian_blur_vertical.frag
│ ├── identity.vert
│ ├── luminance.vert
│ ├── msaa4x_resolve.frag
│ ├── quad_smooth.frag
│ └── simple_blit.frag
│ └── forward
│ ├── lighting.vert
│ ├── lighting_flat.frag
│ ├── lighting_poly.frag
│ ├── lighting_stage.frag
│ ├── point_ball.geom
│ ├── ripple_particle.frag
│ ├── triangle_edge.geom
│ └── unlit.vert
├── rust-oids.iml
└── src
├── app
├── capture.rs
├── constants.rs
├── controller.rs
├── events.rs
├── main.rs
├── mod.rs
├── paint.rs
└── winit_event.rs
├── backend
├── messagebus
│ └── mod.rs
├── mod.rs
├── obj.rs
├── systems
│ ├── ai.rs
│ ├── alife.rs
│ ├── animation.rs
│ ├── game.rs
│ ├── mod.rs
│ ├── particle.rs
│ └── physics.rs
└── world
│ ├── agent.rs
│ ├── alert.rs
│ ├── gen.rs
│ ├── mod.rs
│ ├── particle.rs
│ ├── persist.rs
│ ├── phen.rs
│ ├── segment.rs
│ └── swarm.rs
├── core
├── clock.rs
├── color.rs
├── geometry.rs
├── math.rs
├── mod.rs
├── resource.rs
├── util.rs
└── view.rs
├── frontend
├── audio
│ ├── mod.rs
│ └── multiplexer.rs
├── gfx_window_glutin.rs
├── input
│ ├── gamepad.rs
│ └── mod.rs
├── mod.rs
├── render
│ ├── effects.rs
│ ├── formats.rs
│ ├── forward.rs
│ └── mod.rs
└── ui
│ ├── conrod_gfx.rs
│ ├── conrod_ui.rs
│ ├── mod.rs
│ └── theme.rs
└── main.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | out/
3 | .idea/workspace.xml
4 | .idea/misc.xml
5 | *.bk
6 | *.log
7 | nohup.out
8 | /core
9 | *.VC.db
10 | .cargo/config
11 | *.trace
12 | *.profile
13 | capture/
14 | capture.ntp
15 | cmake-build-debug/
16 | .idea
17 | .DS_Store
18 |
19 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | rust_oids
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/dictionaries/niguo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | rgba
5 | xunit
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Build_rust_oids_release.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Check_rust_oids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Clean.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Run_debug_rust_oids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | rust-oids
4 |
5 |
6 |
7 |
8 |
9 |
10 | org.eclipse.cdt.core.cnature
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | unstable_features = true
2 |
3 | hard_tabs = true
4 | tab_spaces = 4
5 | max_width = 120
6 |
7 | indent_style = "Block"
8 | force_multiline_blocks = false
9 | brace_style = "PreferSameLine"
10 |
11 | wrap_comments = true
12 |
13 | format_macro_matchers = true
14 | format_macro_bodies = true
15 |
16 | use_small_heuristics = "Max"
17 | use_field_init_shorthand = true
18 |
19 | where_single_line = true
20 | fn_single_line = true
21 | struct_lit_single_line = true
22 | empty_item_single_line = true
23 | match_arm_blocks = false
24 | overflow_delimited_expr = true
25 |
--------------------------------------------------------------------------------
/.settings/com.github.rustdt.ide.core.prefs:
--------------------------------------------------------------------------------
1 | build_targets=\n\n\n\n\n\n\n\n\n\n\n\n\n\n
2 | eclipse.preferences.version=1
3 | format_onSave=false
4 | racer_path=/home/nico/.cargo/bin/racer
5 | rainicorn_path=/home/nico/.cargo/bin/parse_describe
6 | rustfmt_path=/home/nico/.cargo/bin/rustfmt
7 | sdk_path=/home/nico/.cargo/
8 | sdk_src_path=/usr/local/src/rustc-nightly/src
9 | toolchain_prefs.use_project_settings=false
10 |
--------------------------------------------------------------------------------
/.settings/com.github.rustdt.ide.ui.prefs:
--------------------------------------------------------------------------------
1 | daemon_showDialogIfErrors=true
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.ltk.core.refactoring.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
3 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-oids"
3 | version = "0.14.0"
4 | readme = "README.md"
5 | description = "A Rust-based A-life playground"
6 | license = "Apache 2.0"
7 | authors = [
8 | "Nico Orru "
9 | ]
10 |
11 | [features]
12 | default = []
13 | profiler = []
14 | capture = []
15 |
16 | [[bin]]
17 | name = "rust-oids"
18 | path = "src/main.rs"
19 |
20 | [dependencies]
21 | num ="*"
22 | num-traits="*"
23 | log="*"
24 | itertools = "*"
25 | log4rs = "*"
26 | cgmath="*"
27 | rand = "0.3"
28 | chrono="*"
29 | enum_primitive="*"
30 | bitflags="1.0"
31 | bit-set="*"
32 | csv = "0.15"
33 | rustc-serialize="*"
34 | image = "*"
35 | wrapped2d = "0.4.0"
36 | gfx = "0.18"
37 | gfx_device_gl = "0.16.2"
38 | #sound
39 | dasp="0.11.0"
40 | dasp_slice="0.11.0"
41 | dasp_sample="0.11.0"
42 | dasp_signal="0.11.0"
43 | pitch_calc="*"
44 | portaudio="*"
45 |
46 | #sys
47 | ctrlc = "*"
48 | dirs = "*"
49 | getopts = "*"
50 | cpuprofiler = "*"
51 | rayon = "*"
52 |
53 | #serialization
54 | serde = "*"
55 | serde_derive = "*"
56 | serde_json = "*"
57 |
58 | #gui
59 | gl = "*"
60 | winit = "0.10"
61 | glutin = "0.12"
62 | conrod = { features = ["winit"], version = "0.58" }
63 | #conrod = { features = ["winit"], git = "https://github.com/itadinanta/conrod", branch = "branch/winit_0.10.0" }
64 |
65 | #controller
66 | gilrs = "*"
67 |
68 | #
69 | [target.'cfg(target_os="linux")'.dependencies]
70 | thread-priority = "*"
71 |
72 | [package.metadata.deb]
73 | maintainer = "Nico Orru "
74 | copyright = "2016-2020, Nico Orru "
75 | license-file = ["LICENSE", "4"]
76 | extended-description = """A Rust-based A-life playground. \
77 | https://github.com/itadinanta/rust-oids"""
78 | depends = "$auto,libportaudio2,libasound2,libudev1"
79 | section = "games"
80 | priority = "optional"
81 | assets = [
82 | ["target/release/rust-oids", "usr/bin/", "755"],
83 | ["resources/minion_gene_pool.csv", "usr/share/rust-oids/resources/", "644"],
84 | ["resources/fonts/*", "usr/share/rust-oids/resources/fonts/", "644"],
85 | ["resources/shaders/effects/*", "usr/share/rust-oids/resources/shaders/effects/", "644"],
86 | ["resources/shaders/forward/*", "usr/share/rust-oids/resources/shaders/forward/", "644"],
87 | ["README.md", "usr/share/doc/rust-oids/", "644"],
88 | ["TODO.md", "usr/share/doc/rust-oids/", "644"],
89 | ["LICENSE", "usr/share/doc/rust-oids/", "644"],
90 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Rust-oids
3 |
4 | A Rust-based A-life playground, for Linux (tested on Devuan Ceres) and Windows 10.
5 |
6 | ## Simulation
7 |
8 | Here's roughly how the simulation works. There are 3 types of agents in the world:
9 |
10 | ### Resources.
11 | These are spawned at a fixed rate by Emitters. Their lifespan is very short and their only purpose is to provide nourishment for the Minions. Minions can detect nearby Resources with their sensor, and detect the nearest Emitters at any distance.
12 |
13 | ### Minions.
14 | These are the little rustoid critters.
15 | - Each Minion shape and behaviour are determined by its, practically unique, **genotype**, which is just a string of bits.
16 | - Body plan, limb geometry and mass distribution are fully simulated via the box2d **physics** engine.
17 | - Body plan, gender, appearance, and brain aspects of the **phenotype** of each Minion are fully determined by its genetic code.
18 | - Each Minion's **brain** is implemented via a simple 3 layer neural network. Brain has no learning capabilities, all behaviour is hardcoded at birth by genotype alone.
19 | - Each Minion has a **sensor** to detect nearby Resources and the nearest Emitter, among other variables.
20 | - Up to 4 **inputs** from the **sensor** determine the **outputs** of the brain which enable **actuators** if their value exceed certain **personality**-dependent **thresholds**. Left and right **rudders** which exert pull, **thrusters** push, and a linear **brake** reduces forward speed.
21 | - Each action by a Minion, including waiting idle and reproducing, consumes a certain amount of **energy**. When energy is depleted, the Minion **dies** and some of its body is released back as Resources.
22 | - Minions who **eat** resources can top-up their energy pool, survive longer and **grow**
23 | - Minions who grow enough to reach **maturity** will **reproduce** via **spores**.
24 | - Minions who are unsuccessful at finding and eating food will not leave offspring driving their lineage **extinct**.
25 |
26 | ### Spores.
27 | - The little 5-lobed balls produced by the Minions by means of which they **reproduce**.
28 | - During reproduction, the genotype is transmitted but the process introduces a variable number of **mutations**. Each mutation flips a random bit of the genotype.
29 | - After a short time, Spores **hatch** into Minions.
30 | - If an unfertilized Spore is touched by a Minion of a different **gender**, of which there are four, it acquires its genetic material and the resulting Minion will have a gene which is a **crossover** of the two.
31 |
32 | The intriguing bit about all of this is that AI, body shape and brain are **bred** via *artificial natural selection* - for want of a better name. Practically all observed behaviour is **emergent**.
33 |
34 | Eventually I plan to plug in some sort of gameplay and release as a free game. Strictly evening/weekend toy project: don't hold your breath.
35 |
36 | ### Interaction
37 |
38 | You can interact with the simulation by shooting Resources at the minions. **Gamepad** supported.
39 |
40 | ## Feedback
41 | - feel free to post [issues on GitHub](https://github.com/itadinanta/rust-oids/issues)
42 | - also send me interesting gene pools (F6/F7 to get snapshots, see instructions below)
43 |
44 | ## Acknowledgements
45 |
46 | This project started as a test bed for the Rust language and toolchain, [GFX](https://github.com/gfx-rs/gfx) and [box2d wrapper](https://github.com/Bastacyclop/rust_box2d), and could have not existed without those.
47 |
48 | ## Videos/Screenshots
49 |
50 | Some rust-oids competing for territory and resources, and the player messing up with them!
51 |
52 | [](https://youtu.be/pXLk-0ZZCEQ "Click to watch video")
53 |
54 | 
55 |
56 | 
57 |
58 | ## Prerequisites/platforms
59 |
60 | I've built on Ubuntu GNU/Linux, Devuan Ceres GNU/Linux and Windows 10.
61 |
62 | On Linux, aside from the full Rust toolchain, the following packages are required (I use `apt install`, adjust for your own distro):
63 |
64 | For Box2D:
65 |
66 | - cmake
67 |
68 | For audio:
69 |
70 | - portaudio19-dev
71 | - libasound2-dev
72 |
73 | For gamepad:
74 |
75 | - libudev-dev
76 |
77 | ### Windows
78 |
79 | `cmake` can be installed via https://scoop.sh/ typing `scoop install cmake`
80 |
81 | Building Windows dependencies is a tedious yak shaving exercise. For convenience, I am redistributing parts of open source projects in the form of headers and prebuilt x64 static libs for Windows 10.
82 | Links to the source code are provided below as for licences:
83 |
84 | - `portaudio` http://www.portaudio.com/, http://www.portaudio.com/license.html
85 |
86 | ## Build/run
87 |
88 | - Clone this repo and ```cd``` into its root
89 | - ```cargo run --release``` run with defaults
90 | - ```cargo run --release -- [] [options]```
91 |
92 | Options:
93 |
94 | ``
95 | : use the specified gene pool (`DDDDMMYYY_hhmmss.csv`).
96 |
97 | `-t`
98 | : text mode, headless. Simulates as fast as possible, saves every 5 minutes.
99 |
100 | `-f I`
101 | : runs in fullscreen on given monitor index I (`-f 0`)
102 |
103 | `-w W`, `-h H`
104 | : optional window size (`-w 1280 -h 900`)
105 |
106 | `-i `
107 | : Load from specific snapshot (`-i ~/.config/rust-oids/saved_state/20180423_234300.json`)
108 |
109 | `-n`
110 | : Ignore last snapshot, start from new population"
111 |
112 | `-a `
113 | : Audio device index (portaudio) (`-a 0`)
114 |
115 |
116 | ## How to play
117 |
118 | Gamepad is supported (tested with DS4, in Windows via [DS4Windows](http://ds4windows.com/))
119 |
120 | - Left stick, WASD: move
121 | - Right stick, Q, E: aim
122 | - L2: fire rate
123 | - R2, Spacebar: shoot
124 | - L1, G: slow down
125 | - R1, H: fast forward
126 | - L3, F1: toggle HUD
127 | - LMB: aim and shoot
128 | - Ctrl + MMB: new random rustoid.
129 | - Ctrl + LMB: select minion for tracing
130 | - MMB: new rustoid from current gene pool.
131 | - RMB drag: camera pan
132 | - Z: deselect minion for tracing
133 | - F1, L3: toggle HUD
134 | - F5: reload shaders (development)
135 | - F6: snapshot current gene pool into the **resources** folder
136 | - F7: quick save
137 | - F8: reload last save
138 | - F12, Gamepad select/share: toggle screen sequence capture (EXPERIMENTAL)
139 | - V,B: set background tone
140 | - K,L: change light intensity
141 | - Scroll wheel up, +, Gamepad Up: zoom in
142 | - Scroll wheel down, -, Gamepad Down: zoom out
143 | - 1, R3: zoom reset
144 | - 0, Home: reset camera pan
145 |
146 | ### License
147 |
148 | Copyright 2016-2020 Nicola Orru
149 |
150 | Licensed under the Apache License, Version 2.0 (the "License");
151 | you may not use this file except in compliance with the License.
152 | You may obtain a copy of the License at
153 |
154 | http://www.apache.org/licenses/LICENSE-2.0
155 |
156 | Unless required by applicable law or agreed to in writing, software
157 | distributed under the License is distributed on an "AS IS" BASIS,
158 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
159 | See the License for the specific language governing permissions and
160 | limitations under the License.
161 |
162 | ### Other licences
163 |
164 | For convenience, I have added some `FreeFont` assets, which are used in the Conrod GUI.
165 | https://www.gnu.org/software/freefont/
166 |
167 | ## Keywords
168 |
169 | rust rustlang testbed ai alife ann neural network genetic algorithm gfx box2d wrapper2d hdr msaa alpha tonemapping shader fragment pixel vertex geometry pso gamedev
170 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | - DESIGN alternative body plans
2 | - DESIGN minions vs enemies
3 | - DESIGN distinguish between friend and foe
4 | - DESIGN levels/puzzles
5 | - TECH remove MSAA (optional?)
6 |
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 | use std::path::Path;
3 |
4 | fn main() {
5 | let target = env::var_os("TARGET").expect("TARGET is not defined");
6 | if target.to_str().expect("Invalid TARGET value").ends_with("x86_64-pc-windows-msvc") {
7 | let current_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
8 | // Library paths can be set as linking is a downstream op
9 | println!(
10 | "cargo:rustc-link-search=native={}",
11 | Path::new(¤t_dir).join("lib/x64").to_str().expect("Invalid library path")
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/img/capture_00003350.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/capture_00003350.png
--------------------------------------------------------------------------------
/img/capture_00003401.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/capture_00003401.png
--------------------------------------------------------------------------------
/img/capture_00003787.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/capture_00003787.png
--------------------------------------------------------------------------------
/img/capture_0551.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/capture_0551.png
--------------------------------------------------------------------------------
/img/capture_1058.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/capture_1058.png
--------------------------------------------------------------------------------
/img/screenshot_001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_001.png
--------------------------------------------------------------------------------
/img/screenshot_002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_002.png
--------------------------------------------------------------------------------
/img/screenshot_003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_003.png
--------------------------------------------------------------------------------
/img/screenshot_004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_004.png
--------------------------------------------------------------------------------
/img/screenshot_005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_005.png
--------------------------------------------------------------------------------
/img/screenshot_006.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_006.png
--------------------------------------------------------------------------------
/img/screenshot_007.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_007.png
--------------------------------------------------------------------------------
/img/screenshot_008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/img/screenshot_008.png
--------------------------------------------------------------------------------
/launch/rust-oids-clippy.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/launch/rust-oids-debug.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/launch/rust-oids.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/x64/portaudio.lib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/lib/x64/portaudio.lib
--------------------------------------------------------------------------------
/resources/fonts/FreeMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/resources/fonts/FreeMono.ttf
--------------------------------------------------------------------------------
/resources/fonts/FreeSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itadinanta/rust-oids/af8151d5ff182011cfff4273500f314048741988/resources/fonts/FreeSans.ttf
--------------------------------------------------------------------------------
/resources/minion_gene_pool.csv:
--------------------------------------------------------------------------------
1 | uvRGK9CZzzph6C0EVCJ//S2ZWn+ym9LYmc2maDzd0TkLD5klR8k9V09YAarODtxvJILwSXwGRr8Ph4s1nVG+pLTtEHygQ333u4XbiQlUTu7Ctm0akSGrovAehLhA4ymLXB3gpw==
2 | uvRGK9CZjzphyCgEQSI//z+RWH+QGtLYjf2laDxd2TkDD5EBT8s6zU5MGKrODt5vBISnrFonX6f2j7MxvRC/ILyNEiWg8998tAZfiTl1SO2atGReEWQhonAOBLhA8ymLXA3grw==
3 | xPVGK/CZjzphyCgEQSI//x+RUj+SGtLYjd2uaDzd2RkDT5GIzYkwDUtaOKrODtJvhSanrFonXyP2i7MhvRC+ALyNEi2g4998tAZfiTlUSeyatGReEWQh6JL6BrkBZGkOXP9gDw==
4 | hLVGK3CZjz5jaDwEFCN//S2Zen+ym9LYmc2uaDzd1TkLD5klR8k9V09YAarODtxvJILwSXwGRr8Ph4s1nVG+pLTtEHygQ333u4XbiQlUTu7Cti0akSGrovAegLhA4ymLXJ3gpw==
5 | hPVGK3CZjz5jaDwEFCN//S2ZWn+ym9LYmc2maDzd0TkLD5klR8k9V09YAarODtxvJILwSXwGRr8Ph4s1nVG+pLTtEHygQ333u4XbiQlUTu7Ctm0akSGrovAehLhA4ymLXB3gpw==
6 | 79UGI/CZjztj+CwEQSD6fR+RUj+SGtLYid2saDzd2RkDT5GIzYkwDUtaOKrODtJvhSanrFonXyP2i7MhvRC+ALyNEi2g4998tAZfiTlUSeyatGReEWQh6JL6BrkBZGkOXP9gDw==
7 | uvRGK9Cdjzph6C0EVCI//S0RGn6yG+bInd2mYDwd0TkDD5CpzUgwV09YAaoeDt4nBIanrFonX6f2j7MxvRS/ILydEiWg8998tAZfiTl1SO2atGReEWQhqnAGBrhA8ymLXA3grw==
8 | r/UGIzCZjzpj+CwEQSB//D+RUn+SGtLYnd2maDzd0TkDD5EBR8gwl01QAaoeDt4nRIOwSVosTyZezoGx3HC/ZL7dAiwAQ333mwXS+a11rsRWtm0akSGrovAeBLhA8yuLXA3gpw==
9 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDzd2TkDD5EBTUgwF09QAaoeDt4nRIOwSVosTyZez4Ox3FC+ZLzNAiwAQ333mwXS+a1VrsRWtm0akSGrovAeBLhA8ymLXA3gpw==
10 | 77UHIrDZjzpj6C0EVCI//W2ZWn8yG9bYOc3mSDzd2TkLD5CKzUgwXU5QAaoOjt0nBIOwyV4GTyfOj8ExjXC+LLzNAnwg4/33q6dWya8VSO2atGReEWQhonAOBLhA8y2LXA/grw==
11 | uvRGK9CZjzph6C0EVCI//S0ZWn+yG9LYmc2maDzd2TkDD5CITUgwF09QAaoeDt4nRIOwSVosTyZez4Ox3FC+JLzNAiwAQ333mwXS+a1VrsRWtGReEXwh6LPbD71E4iuJfA0krw==
12 | r/QGIzCdizpj+iwEQCB/fT+RUl+SGtKYnd2laDxd2TkDD5UBT8s6zU5MGKrODt5vBIanrFonX6X2j7MxvRS/ILyNEiWg8998tAZdiTF3SOmatGRcEWQhqnAGBLhA8ymLXA3grw==
13 | r/QGIzCdjzpj+mwEQCB//T+RUF2SGtKYnd2laDxd2TkDD5UBT8s6zU5MGKrODt5vBIanrFonX6f2j7MxvRS/ILydEiWg8998tAZfiTl1SO2atGReEWQhqnAGBrhA8ymLXA3grw==
14 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDzd2TkDD5EBTUgwF09QAaoeDt4nRIOwSVosTyZez4Ox3FC+ZLzNAiwAQ333mwLbyQlUTm7CtOZeEWQhaLJ6BrkBZGkOXP9gjw==
15 | uvRGK9CZjzph6C2EVCI//S0ZWn+yH9LYmc2maDzd2TkDD5MBV8s6zU5KGKrODt5vBIanrFonXyP2h4MxnVC+JLTtGHwis998sAPfiblVTOzStHQcEWQh6LL6DzhE4iubfA3gLw==
16 | xPVGL/CZjzpgyCgEQSI/vx+RUj+SG9LYjd2uaDzdyRkDT5GJzYkwDUNaOKrODtJvhSanrFonXyP2i7MhvRC+ALyNEi2g4998tAZfiTlUSeyatGReEWQhomT6BrkBZGkOXP9gjw==
17 | r/QGIzCdjzpj+iwEQCB//T+RUl+SGtKYnd2laDxd2TkDD5UBT8s6zU5MGKrODt5vBIanrFonX6f2j7MxvRS/ILyNEiWg8998tAZfiTl1SO2atGReEWQhqnAGBLhA8ymLXA3grw==
18 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDjd2TkDD5EBTUgwF09QAaoeDt4nRIOwSVosTyZez4Ox3FC+bLzNAiwAQ333mwXS+a1VrsRWtmsakSGrovAehLxA8ymLXA3gpw==
19 | 7/0GIvCZjztj+SwEQaD6/RuRUj+SGtPYjd2uaDTV2VkDT5GITYkwDUtaGKrODt5vBIanrnonX6P2j7MxvBC+ALyNEi2gs99+swXS/a1V7sRWti0NEWUh6LPbD71E4CuJfA0krw==
20 | uvVGK3Gdjzpj+CwEQSI//T+RU3+yO/bYnd2maDyd2TkDD5MBV8s6zU5KGKrODt5vBIanrFonXyP2h4MxnVC+JLTtGHwis998sAPfiblVTOzStHQcEWQh6LL6DzhE4iubfA3gLw==
21 | +vVGK/CZ3zpr6CwEwSq+/RuRUj+SGtPYjd2uaDzd2RkDT5GIT4kwDUtaWKrODt5vBIanrHonX6P2h7MxvBC+ALSNEi2gs99+swXS/a1V7sRWti0NEWQh6LPbD71E4CuJfA0krw==
22 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDzd2TkDD5EBT8s6zW5KGKrODt5vBIanrFonX6P2j7MxvBC+ALyNEi2g899+swXS/a1V7sRWpi0NEWQB6LPbD71E4iuJXA0krw==
23 | r/EGK7A8XzsHriAEQbO//y8RWn6yG+bInd2mYDwd0TkDD5CJzUgwV09YAaoeDt4nBIOwSVoGTyfej8MxnVC+JDzNAnwg4/13q4dX4a0djsRCtm0akSGrovAeBL1QsKuLcI2grw==
24 | r/EGK7A8XzsDrigEQSI//S0ZWn+yGtKYnd2naDzdWTkDD7EBT8s6zU5KGKrOj95vBIanrHonX6Pnj7MxvBC+ALyNEi0g89/+swXS/a1V7sRWpi0NEWQB6LPbD73E4i+JXA0kjw==
25 | uvVGK2Gdjzpj+CwEQSI//T+RU3+yO/bYnd2maDyd2TkDD5MBV8s6zU5KGKrODt5vBIanrFonXyP2h4MxnVK+JLTtEHwiu998sELXiblVTOzS9GQcEWQx6LL6DzhE4iuLfAzgjw==
26 | 7/UGI/CZjztj+CwEQSD6/R+RUj+SGtLYnd2maDzd2RkDT5GIzYkwDUtaGarODt9vDIanrFonXyP2j7MxvRC+Ab2NEi2g4998uwXS+a1VrsRWti0dEGQh6rPbD73E5GgOXP9gjw==
27 | 7/UGI/CZjztj+CwEQSD6/R+RUj+SGtLYid2uaDzd2RkDT5GIzYkwDUtaGKrODt5vBIanrHonX6P2j7MxvBC+ALyNEi2g899+swXS/a1V7sRWti0NESGrovAeBL1QsKuLcI2grw==
28 | uvXGe7CLjzph6C0EVCI//S2ZWn+SGtLYnd2maDzd2TsDD5EBR8s6zW5KGKrPDt5vBI6nvFolXyPejyMxjXC+LLzNAnwg4/33o6dWya8dSO7+ti0OE2SqsuLrTr1Q8kxrVL+E7w==
29 | r7QGI7Cdjzpj+iwEQCB//T+RUl+SGtKInd2laDxd2TkDD7EBT8syzU5MGKrODt5vBISnrFojX6fei4MxjVL+B7xUFGwgs998sALbyQlUTm7CtOZeEWQhaLL6BrkBZGkOXP9gjw==
30 | uvVGe7CJjz5iui0ERCI/3S8R2X+wGtPYjd2maBzZ0TULD5kFR8k6jW5KGKrODt9vBIanrFonX+f2n7MxvRC/ALyJEiWg88t8tCZfiThVSOyatGReEWQhonAGBLhA8ymKXA3gpg==
31 | 7/UGI/CZjztj+CwEQSD6/R+RUj+SGtLYnd2maDzd2RkDT5GIzYkwDUtaGarODt9vDIanrFonXyP2j7MxvRC+Ab2NEi2g4998uwdW6a0djsRCtm0akSGronAOBLhAsymLXA3gpg==
32 | r/EGK7A8XzsDrigEQSI//z+BW3+SGtDcld2mYD3d2T0DD5sBV8s6zU5KmKrOLt5nBOOwSV4GTyfej4MxjVC+B7xUEGwg8998sALbiQkQTu7CtGReEWQhaLP6BrkBZGkOXP9ojw==
33 | xOUGq/Cczzph6S0EVCI//S2ZWn+aGtKYnd2naDz92TkDD5MBV8s6zU5KGKrODt5vBIanrFonXyP2h4MxnVC+JLTtGHwis998sAPfiblVTOzStHQcEWQh6LL6DzhE4iubfA3gLw==
34 | uvRGK9CZjzphyCgEQSI//S2ZWn+ym9LYmc2maDzd0TkLD5klR8k9V09YAarODtxtJILwSX0GRr8Ph4s1nVG+pLTtEHygQ333u4XbiQlUTu7Ctm0akSGrovAehLhA4ymLXB3gpw==
35 | xPVGK/CZzzph6S0EVCI//S0ZWn+SGtKYnd2naDzdWTkDD5EBT8s6zU5KGKrOj95vBIanrHonX6Pnj7MxvBC+ALyNEi0g89/+swXS/a1V7sRWpi0NEWQB6LPbD71E4iuJXA0krw==
36 | 7/UGK3CZjztz+CwEQSD6/R+RVn+SGtLYjZ2maDzdmRkDT5GIzYkwDUt6fKrOTt5vAIanrFonXyP2j7MxvRC+ALyNEi2o+998swXS+a1VrsRWti8akSGrovAGB71E4iuJXA0krw==
37 | xPVGq/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDzd2TkDD5GIzYkwDUt6fKruztpvAIenLF4nXyHyj7MzvSC+BLyNEj2o+9t8swXS+Y1VrsRWti8ekSGrovAGBLBAsymLXAngpg==
38 | uvVGK3Gdjzpj+CwEQSI//T+RU3+yO/bYnd2maDyd2TkDD5MBV8s6zU5KGKrODt5vBIanrFonXyP2h4MxnVC+JLTtGHwis998sALfiblVTOzStGQcEWQh6LL6DzhE4iuLfA3grw==
39 | xPVOe7CJjz5iui0EVCA/1S8R2X+wGtfajZ2maBzZ2TULD5kFR8klV09YAaoeDM4nBcOwCV4GTydej4MxjVi+LLztCnwg4/13K4dWqTl1SO2atGReEWAhonAOALhE8ymLTA3hrw==
40 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2n6Dzd2TkDD5EBT8s6zW5KGKrODt5vBIanrFonX6f2j7MxvRC/ALyNEiWg8998tAZfiTl1SOyatGReEWQhonAOBLhA8y2LXA/grw==
41 | r/UGIzCZjzpj+CwEQSB//T+RUn+SGtLYnd2maDzd2TkDD5EBR8gwF09QAaoeDt4nRIOwSVosTyZezoGx3FC/ZLzNAiwAQ333mwXS+a11rsRWtm0akSGrovAeBLhA8ymLXA3gpw==
42 | +tVGK/CZ3zpr+CwEwSq+/RuRUr+SGtPYjd2ueDzd2RkDT5GIbYkwDUtaGCrODN5vBIanrHonX6O2j7cxvBC+ALyNEi2h899+swXS/a1V7sRWti0NEWQh6LPbD71E8CuZfA0krw==
43 | 7/UGI/CZjzpj6CwEQSD6/x+RUj+SGtLYidyuaDzd2RkDT5GIzYkwDUNaGKrODt5vBIanrHonf6P2r7sxvBC+ALyNEi2g899+swXS/a1V7sRWti0NESGrovAeBL1QsKuLcI2grw==
44 | +vVGK/CJ3zpr6CwEwSq+fRuRUj+SGtPYjd2uaDzd2RkDT5GIT4kwDUtaWKrODt5vBIanrHonX6P2h7MxnBC+ALSNEi2gs99+swXS/a1V7sRWti0NEWQh6LPbD71E4CuJfA0krw==
45 | uvWGe7CLjzphyC0EVCM//S2ZWn+SGtLYnd2maDzdyTsDH5EBR8s6zW5KGKrPDt5vpY6nvFolXyPejyMxnVC+LL7NAnwg4/1nq4dW6a0djsRCtm0akSGronAOBLhBsymLXC3gpg==
46 | r/QGI7Cdjzpj+iwEQCB//T+RUl+SGtKYnd2laDxd2TkDD5EBT8s6zU5MGKrODt5vBISnrFonX6f2j7MxvRC/ILyNEiWg8998tAZfiTl1SO2atGReEWQhonAOBLhA8ymLXA3grw==
47 | uvVGK2Gdjzpj+CwEQSI//T+RU3+yO/bYnd2maDyd2TkDD5MBV8s6zU5KGKrODt5vBIanrFonXyP2h4MxnVK+JLTtEHwiu998sELfiblVTOzStGQcEWQx6LL6DzhE4iuLfA3gjw==
48 | uvVGK/CZ3zpr+CwEwSK//T+RW3+SmtLYndWmaG3d2TkDD5MBV8s6zU5KGLrODt5vBIanrFomXmfej4M1nUS+JL3NAjgAQ3nn68XW6S0XrMXSFi0OE2QhInAOBLxA8yiLXAXgpg==
49 | r/UGITCZjzpj+CyEQSB//T+RUn+SGtKYnd2maDzdyXkTD5EBR8gwFk9QAaoeDt4nRIOwSVosTyZezoGx3FK/ZLzNAmwAQ333mwXS+a11rsRWtm0akSGrovAbD71E4iuJXA0krw==
50 | xPVGK/CZjzphyCgEQSI//x+RUj+SGtLYjd2uaDzd2RkDT5GIzYkwDUtaOKrODtZvBILwS3wGRr8Ph4M1ndG+ILyNEiWg8998tAZfCTl1SO2atGReEWAhonAOBLlE8ymLXA3hrw==
51 | 7/UGI/CZjztj+C0EQSD6/R+RUj+SGtLYid2uaDzd2RkDT5GIzYkwDUtaGKrODt5vBIanrHonX6P2j7MxvBC+ALyNEi2g899+swXS/a1V7sRWti0NEWQh6LPfD71E4iuJfA0krw==
52 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDjd2TkDD5EBTUgwF09QAaoeDt4nRIOwSVosTyZez4Ox3FC+bLzNAiwAQ333mwXS+a1VrsRWtmsakSGrovAehLhA8ymLXA3gpw==
53 | xPVGK/CZzzph6S0EVCI//S2ZWn+SGtKYnd2naDzd2TkDD5EBT8s6zW5KGKrODt5vBIanrFonX6f2j7MxvRC/ALyNEiWg8998tAZfiTlVSOyatGReEWQhonAOBLhA8ymLXA3gpg==
54 | 7/0GI/CZjztj+CwEQSD6/RuRUj+SGtPYjd2uaDzd2RkDT5GITYkwDUtaGKrODt5vBIanrHonX6P2j7MxvBC+ALyNEi2gs99+swXS/a1V7sRWti0NEWQh6LPbD71E4CuJfA0krw==
55 | 7/UCI/CZjzpj+CwEQSI//z+RWH+QGtLYjP2maDrd2TkDD5sBV8s6zUpKGKrODt5nBOOwCV4GTyfej4MxjVC+LLzNAnwg4/13q4dW6a0djsRCtm0akSGronAOBLhAsymLXA3gpg==
56 | uvVOe7CJjz5iui0EVCI/3S8R2X+wGtfajd2maBzZ2TULD5kFR8klV09YAaoeDM4nBcOwCV4GTydej4MxjVC+LLzNAnwg4/13K4dW6Tl1SO2atGReEWAhonAOBLhE8ymLXA3hrw==
57 | 7/0GI/CZjztj+CgEQSD6/RuRUj+SGtPYjd2uaDzd2RkDT5GITYkwDUtaGKrODt5vBIanrHonX6Pmj6MxvBC+ALyNEi2gs89+swXW/a1V7sRWti0NUWQh6LPbD71E4CuJXA0krw==
58 | r/AGIzCdizpj+iwEQCB/fTeRQl+SGtKYnd2laDxdyTkDD5UBT8s6zU5MGKrODp5vBIanrFonX6X2j7MxvRS/ILyNE2WgY998tAZdiTF3SOmatGRcEWQhqnAGHLhA8ymLXA3gLw==
59 | //UGI/CZjzphyCgEQSI//x+RUh+SGNLYjd2uaDzV2RkDT5uIzYkwDUtaOKrODtJvhSanrFomXmdez4M1z1S+YL/Mgjggx3zz2ALbiUlUTu7Csm0KESGrovAehLhQ6SmLVK3opw==
60 |
--------------------------------------------------------------------------------
/resources/shaders/effects/clip_luminance.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | out vec4 o_Color;
7 |
8 | const float MAX_LUM = 10.0;
9 |
10 | vec4 lum_clip(float x, float y) {
11 | vec4 src = texture(t_Source, vec2(x, y), 0);
12 | float l = max((dot(vec3(0.2126, 0.7152, 0.0722), src.rgb) - 1.), 0.);
13 | // return l <= 0 ? src : vec4(src.rgb / l * min(MAX_LUM, l), src.a);
14 | return vec4(src.rgb * min(MAX_LUM, l), src.a);
15 | }
16 |
17 | void main() {
18 | vec2 d = 1.0 / textureSize(t_Source, 0);
19 | float x1 = v_TexCoord.x - d.x / 2;
20 | float x2 = x1 + d.x;
21 | float y1 = v_TexCoord.y - d.y / 2.;
22 | float y2 = y1 + d.y;
23 |
24 | o_Color = (lum_clip(x1, y1) + lum_clip(x2, y1) + lum_clip(x1, y2)
25 | + lum_clip(x2, y2)) / 4.0;
26 | }
27 |
--------------------------------------------------------------------------------
/resources/shaders/effects/compose_2.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source1;
4 | uniform sampler2D t_Source2;
5 |
6 | in vec2 v_TexCoord;
7 | out vec4 o_Color;
8 |
9 | void main() {
10 | o_Color = texture(t_Source1, v_TexCoord, 0)
11 | + texture(t_Source2, v_TexCoord, 0);
12 | }
13 |
--------------------------------------------------------------------------------
/resources/shaders/effects/exponential_smooth.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Value;
4 | uniform sampler2D t_Acc;
5 |
6 | layout (std140) uniform cb_FragmentArgs {
7 | float u_ExpAlpha;
8 | };
9 |
10 | in vec2 v_TexCoord;
11 | out vec4 o_Smooth;
12 |
13 | void main() {
14 | vec4 value = texture(t_Value, v_TexCoord, 0);
15 | vec4 acc = texture(t_Acc, v_TexCoord, 0);
16 |
17 | o_Smooth = max(u_ExpAlpha * value, vec4(0.))
18 | + max((1. - u_ExpAlpha) * acc, vec4(0.));
19 | }
20 |
--------------------------------------------------------------------------------
/resources/shaders/effects/exposure_tone_map.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | in float v_Exposure;
7 | out vec4 o_Color;
8 |
9 | void main() {
10 | vec4 src = texture(t_Source, v_TexCoord, 0);
11 | vec4 linear_color = v_Exposure * src;
12 | // TODO: this should be done by RGBA pixelformat
13 | vec4 gamma_corrected = pow(linear_color, vec4(1/1.2));
14 | o_Color = vec4(gamma_corrected.rgb, src.a);
15 | }
16 |
--------------------------------------------------------------------------------
/resources/shaders/effects/gaussian_blur_horizontal.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | out vec4 o_Color;
7 |
8 | const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
9 |
10 | void main() {
11 | vec2 tex_offset = 1.0 / textureSize(t_Source, 0); // gets size of single texel
12 | vec3 result = texture(t_Source, v_TexCoord).rgb * weight[0]; // current fragment's contribution
13 |
14 | result += texture(t_Source, v_TexCoord + vec2(tex_offset.x * 1, 0.0)).rgb
15 | * weight[1];
16 | result += texture(t_Source, v_TexCoord - vec2(tex_offset.x * 1, 0.0)).rgb
17 | * weight[1];
18 |
19 | result += texture(t_Source, v_TexCoord + vec2(tex_offset.x * 2, 0.0)).rgb
20 | * weight[2];
21 | result += texture(t_Source, v_TexCoord - vec2(tex_offset.x * 2, 0.0)).rgb
22 | * weight[2];
23 |
24 | result += texture(t_Source, v_TexCoord + vec2(tex_offset.x * 3, 0.0)).rgb
25 | * weight[3];
26 | result += texture(t_Source, v_TexCoord - vec2(tex_offset.x * 3, 0.0)).rgb
27 | * weight[3];
28 |
29 | result += texture(t_Source, v_TexCoord + vec2(tex_offset.x * 4, 0.0)).rgb
30 | * weight[4];
31 | result += texture(t_Source, v_TexCoord - vec2(tex_offset.x * 4, 0.0)).rgb
32 | * weight[4];
33 |
34 | o_Color = vec4(result, 1.0);
35 | }
36 |
--------------------------------------------------------------------------------
/resources/shaders/effects/gaussian_blur_vertical.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | out vec4 o_Color;
7 |
8 | const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
9 |
10 | void main() {
11 | vec2 tex_offset = 1.0 / textureSize(t_Source, 0); // gets size of single texel
12 | vec3 result = texture(t_Source, v_TexCoord).rgb * weight[0]; // current fragment's contribution
13 |
14 | result += texture(t_Source, v_TexCoord + vec2(0.0, tex_offset.y * 1)).rgb
15 | * weight[1];
16 | result += texture(t_Source, v_TexCoord - vec2(0.0, tex_offset.y * 1)).rgb
17 | * weight[1];
18 |
19 | result += texture(t_Source, v_TexCoord + vec2(0.0, tex_offset.y * 2)).rgb
20 | * weight[2];
21 | result += texture(t_Source, v_TexCoord - vec2(0.0, tex_offset.y * 2)).rgb
22 | * weight[2];
23 |
24 | result += texture(t_Source, v_TexCoord + vec2(0.0, tex_offset.y * 3)).rgb
25 | * weight[3];
26 | result += texture(t_Source, v_TexCoord - vec2(0.0, tex_offset.y * 3)).rgb
27 | * weight[3];
28 |
29 | result += texture(t_Source, v_TexCoord + vec2(0.0, tex_offset.y * 4)).rgb
30 | * weight[4];
31 | result += texture(t_Source, v_TexCoord - vec2(0.0, tex_offset.y * 4)).rgb
32 | * weight[4];
33 |
34 | o_Color = vec4(result, 1.0);
35 | }
36 |
--------------------------------------------------------------------------------
/resources/shaders/effects/identity.vert:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | in vec2 a_Pos;
4 | in vec2 a_TexCoord;
5 | out vec2 v_TexCoord;
6 |
7 | void main() {
8 | v_TexCoord = a_TexCoord;
9 | gl_Position = vec4(a_Pos, 0.0, 1.0);
10 | }
11 |
--------------------------------------------------------------------------------
/resources/shaders/effects/luminance.vert:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_VertexLuminance;
4 |
5 | layout (std140) uniform cb_VertexArgs {
6 | float u_White;
7 | float u_Black;
8 | };
9 |
10 | in vec2 a_Pos;
11 | in vec2 a_TexCoord;
12 | out vec2 v_TexCoord;
13 | out float v_Exposure;
14 |
15 | void main() {
16 | v_TexCoord = a_TexCoord;
17 | float luminance = dot(vec3(0.2126, 0.7152, 0.0722),
18 | texture(t_VertexLuminance, vec2(0.5, 0.5)).rgb);
19 |
20 | // TODO: interpolate flat
21 | v_Exposure = 1.0 / (u_Black + (u_White * luminance));
22 | gl_Position = vec4(a_Pos, 0.0, 1.0);
23 | }
24 |
--------------------------------------------------------------------------------
/resources/shaders/effects/msaa4x_resolve.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2DMS t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | out vec4 o_Color;
7 |
8 | void main() {
9 | vec2 d = textureSize(t_Source);
10 | ivec2 i = ivec2(d * v_TexCoord);
11 | o_Color = (texelFetch(t_Source, i, 0) + texelFetch(t_Source, i, 1)
12 | + texelFetch(t_Source, i, 2) + texelFetch(t_Source, i, 3)) / 4.0;
13 | }
14 |
--------------------------------------------------------------------------------
/resources/shaders/effects/quad_smooth.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | out vec4 o_Color;
7 |
8 | void main() {
9 | vec2 d = 1.0 / textureSize(t_Source, 0); // gets size of single texel
10 | float x1 = v_TexCoord.x - d.x / 2;
11 | float x2 = x1 + d.x;
12 | float y1 = v_TexCoord.y - d.y / 2.;
13 | float y2 = y1 + d.y;
14 | o_Color = (texture(t_Source, vec2(x1, y1), 0)
15 | + texture(t_Source, vec2(x1, y2), 0)
16 | + texture(t_Source, vec2(x2, y1), 0)
17 | + texture(t_Source, vec2(x2, y2), 0)) / 4.0;
18 | }
19 |
--------------------------------------------------------------------------------
/resources/shaders/effects/simple_blit.frag:
--------------------------------------------------------------------------------
1 | #version 150 core
2 |
3 | uniform sampler2D t_Source;
4 |
5 | in vec2 v_TexCoord;
6 | out vec4 o_Color;
7 |
8 | void main() {
9 | o_Color = texture(t_Source, v_TexCoord, 0);
10 | }
11 |
--------------------------------------------------------------------------------
/resources/shaders/forward/lighting.vert:
--------------------------------------------------------------------------------
1 | // lighting.vert
2 | #version 150 core
3 |
4 | #define MAX_NUM_SHAPES 256
5 |
6 | layout (std140) uniform cb_CameraArgs {
7 | uniform mat4 u_Proj;
8 | uniform mat4 u_View;
9 | };
10 |
11 | struct Model {
12 | mat4 transform;
13 | };
14 |
15 | layout (std140) uniform u_ModelArgs {
16 | Model u_Model[MAX_NUM_SHAPES];
17 | };
18 |
19 | in vec3 a_Pos;
20 | in vec3 a_Normal;
21 | in vec3 a_Tangent;
22 | in vec2 a_TexCoord;
23 | in int a_PrimIndex;
24 |
25 | out VertexData {
26 | vec4 Position;
27 | vec3 Normal;
28 | mat3 TBN;
29 | vec2 TexCoord;
30 | vec3 BaryCoord;
31 | flat int PrimIndex;
32 | }v_Out;
33 |
34 | void main() {
35 | mat4 model4 = u_Model[a_PrimIndex].transform;
36 | mat3 model = mat3(model4);
37 | v_Out.Position = model4 * vec4(a_Pos, 1.0);
38 | vec3 normal = normalize(model * a_Normal);
39 |
40 | v_Out.Normal = normal;
41 | vec3 tangent = normalize(model * a_Tangent);
42 | vec3 bitangent = cross(normal, tangent);
43 |
44 | v_Out.TBN = mat3(tangent, bitangent, normal);
45 | v_Out.BaryCoord = vec3(1/3.,1/3.,1/3.);
46 |
47 | v_Out.TexCoord = a_TexCoord;
48 | v_Out.PrimIndex = a_PrimIndex;
49 | gl_Position = u_Proj * u_View * v_Out.Position;
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/resources/shaders/forward/lighting_flat.frag:
--------------------------------------------------------------------------------
1 | // lighting_flat.frag
2 | #version 150 core
3 |
4 | #define MAX_NUM_TOTAL_LIGHTS 16
5 | #define MAX_NUM_SHAPES 256
6 |
7 | const float PI = 3.1415926535897932384626433832795;
8 | const float PI_2 = 1.57079632679489661923;
9 |
10 |
11 | struct Material {
12 | vec4 u_Emissive;
13 | vec4 u_Effect;
14 | };
15 |
16 | struct Light {
17 | vec4 propagation;
18 | vec4 center;
19 | vec4 color;
20 | };
21 |
22 | layout (std140) uniform cb_FragmentArgs {
23 | int u_LightCount;
24 | };
25 |
26 | layout (std140) uniform cb_MaterialArgs {
27 | Material material[MAX_NUM_SHAPES];
28 | };
29 |
30 | layout (std140) uniform u_Lights {
31 | Light light[MAX_NUM_TOTAL_LIGHTS];
32 | };
33 |
34 | in VertexData {
35 | vec4 Position;
36 | vec3 Normal;
37 | mat3 TBN;
38 | vec2 TexCoord;
39 | vec3 BaryCoord;
40 | flat int PrimIndex;
41 | }v_In;
42 |
43 | out vec4 o_Color;
44 |
45 | void main() {
46 | vec4 kd = vec4(0.2, 0.2, 0.2, 1.0);
47 | vec4 ks = vec4(1.0, 1.0, 1.0, 1.0);
48 | vec4 kp = vec4(64.0, 32.0, 64.0, 1.0);
49 |
50 | float dx = 2 * clamp(v_In.TexCoord.x, 0, 1) - 1;
51 | float dy = 2 * clamp(v_In.TexCoord.y, 0, 1) - 1;
52 | float r = min(1, dx * dx + dy * dy);
53 |
54 | vec4 u_Emissive = material[v_In.PrimIndex].u_Emissive;
55 | vec4 u_Effect = material[v_In.PrimIndex].u_Effect;
56 |
57 | float f = clamp(u_Effect.x * 2, 0, 1);
58 | float e = clamp(abs(cos(r - u_Effect.y) + sin(dy - 2 * u_Effect.y)), 0, 1);
59 | vec4 color = u_Emissive * e * f;
60 | o_Color.rgb = color.rgb * color.a;
61 | o_Color.a = 0;
62 | }
63 |
--------------------------------------------------------------------------------
/resources/shaders/forward/lighting_poly.frag:
--------------------------------------------------------------------------------
1 | // lightinh_poly.frag
2 | #version 150 core
3 |
4 | #define MAX_NUM_TOTAL_LIGHTS 16
5 | #define MAX_NUM_SHAPES 256
6 |
7 | const float PI = 3.1415926535897932384626433832795;
8 | const float PI_2 = 1.57079632679489661923;
9 |
10 | struct Material {
11 | vec4 u_Emissive;
12 | vec4 u_Effect;
13 | };
14 |
15 | struct Light {
16 | vec4 propagation;
17 | vec4 center;
18 | vec4 color;
19 | };
20 |
21 | layout (std140) uniform cb_FragmentArgs {
22 | int u_LightCount;
23 | };
24 |
25 | layout (std140) uniform cb_MaterialArgs {
26 | Material material[MAX_NUM_SHAPES];
27 | };
28 |
29 | layout (std140) uniform u_Lights {
30 | Light light[MAX_NUM_TOTAL_LIGHTS];
31 | };
32 |
33 | in VertexData {
34 | vec4 Position;
35 | vec3 Normal;
36 | mat3 TBN;
37 | vec2 TexCoord;
38 | vec3 BaryCoord;
39 | flat int PrimIndex;
40 | } v_In;
41 |
42 | out vec4 o_Color;
43 |
44 | const float EDGE_WIDTH = 0.15;
45 | const float SPOKE_WIDTH = 0.1;
46 | const float SPOKE_SCALE = 0.1;
47 | const float SPOKE_OFFSET = 0.1;
48 | const float SPOKE_POWER = 6.0;
49 | const float BASE_ALPHA = 0.0;
50 | const float NORMAL_SLOPE = 0.6;
51 | const float EFFECT_BIAS = 0.5;
52 | const float EFFECT_GAIN = 4.0;
53 | const float DIFFUSE_GAIN = 0.25;
54 |
55 | const float FRESNEL_BIAS = 0.0;
56 | const float FRESNEL_SCALE = 0.5;
57 | const float FRESNEL_POWER = 3.0;
58 |
59 | void main() {
60 | vec4 kd = vec4(0.2, 0.2, 0.2, 1.0);
61 | vec4 ks = vec4(1.0, 1.0, 1.0, 1.0);
62 | vec4 kp = vec4(64.0, 32.0, 64.0, 1.0);
63 |
64 | float dx = 2 * clamp(v_In.TexCoord.x, 0, 1) - 1;
65 | float dy = 2 * clamp(v_In.TexCoord.y, 0, 1) - 1;
66 |
67 | vec4 u_Emissive = material[v_In.PrimIndex].u_Emissive;
68 | // u_Effect.x: energy left
69 | // u_Effect.y: anuimation phase (0-2PI)
70 | vec4 u_Effect = material[v_In.PrimIndex].u_Effect;
71 |
72 | // plasma-like animation effects
73 | float f = clamp(u_Effect.x * 2, 0, 1);
74 | float r = min(1, dx * dx + dy * dy);
75 | float e = clamp(abs(cos(r - u_Effect.y) + sin(dy - 2 * u_Effect.y)), 0, 1);
76 | float p = 1 - abs(cos(u_Effect.y));
77 |
78 | // soft edge
79 | float r_mask = smoothstep(1, 1 - EDGE_WIDTH, max(r + p * 0.2, 1 - v_In.BaryCoord.x));
80 | // highlight, spokes
81 | float h_mask = smoothstep(SPOKE_WIDTH, 0, abs(p - r * pow(1 - v_In.BaryCoord.y * v_In.BaryCoord.z, SPOKE_POWER)));
82 | //float h_mask = smoothstep(p + SPOKE_WIDTH, p - SPOKE_WIDTH, r * e * (v_In.BaryCoord.y * v_In.BaryCoord.z) * f);
83 | //clamp(1 - r / f, 0, 1) * smoothstep(SPOKE_WIDTH * e, 0, pow(r, f) * min(v_In.BaryCoord.y, v_In.BaryCoord.z)); // insets highlight
84 |
85 | // some lighting
86 | vec4 color_diffuse = vec4(u_Emissive.rgb, clamp(f, 0, 1)) * DIFFUSE_GAIN;
87 | vec4 color_lambert = vec4(0,0,0,1);
88 | vec4 color_specular = vec4(0,0,0,1);
89 | vec4 highlight_color = u_Emissive * (e + EFFECT_BIAS) * f * EFFECT_GAIN;
90 |
91 | vec3 normal = v_In.TBN * normalize(vec3(dx, dy, NORMAL_SLOPE * sqrt(1 - r - e * 0.2)));
92 |
93 | vec3 viewDir = vec3(0.0, 0.0, 1.0); // ortho, normalize(-v_In.Position.xyz); perspective
94 | float fresnel = max(0, min(1, FRESNEL_BIAS + FRESNEL_SCALE * pow(1.0 + dot(-viewDir, normal), FRESNEL_POWER)));
95 |
96 | for (int i = 0; i < u_LightCount; i++) {
97 | vec4 delta = light[i].center - v_In.Position;
98 | float dist = length(delta);
99 | float inv_dist = 1. / dist;
100 | vec4 light_to_point_normal = delta * inv_dist;
101 | // attenuation, 2nd deg polynomial
102 | float intensity = dot(light[i].propagation.xyz, vec3(1., inv_dist, inv_dist * inv_dist));
103 | float lambert = max(0, dot(light_to_point_normal, vec4(normal, 0.0)));
104 |
105 | vec4 specular;
106 | if (lambert >= 0.0) {
107 | // Blinn-Phong:
108 | vec3 lightDir = light_to_point_normal.xyz;
109 | vec3 halfDir = normalize(lightDir + viewDir); // can be done in vertex shader
110 | float specAngle = max(dot(halfDir, normal), 0.0);
111 | specular = pow(vec4(specAngle), kp);
112 | } else {
113 | specular = vec4(0.0);
114 | }
115 | vec4 light_intensity = light[i].color * intensity;
116 | color_lambert += light_intensity * kd * lambert;
117 | color_specular += light_intensity * ks * specular;
118 | }
119 |
120 | vec4 solid_color = color_diffuse + color_lambert + color_specular + vec4(fresnel);
121 |
122 | //o_Color.rgb = color_lambert.rgb;
123 | o_Color.rgb = r_mask * (h_mask * highlight_color.rgb + solid_color.rgb);
124 | //o_Color.rgb = vec3(h_mask);
125 | o_Color.a = r_mask * color_diffuse.a;
126 | }
127 |
--------------------------------------------------------------------------------
/resources/shaders/forward/lighting_stage.frag:
--------------------------------------------------------------------------------
1 | // lighting_stage.frag
2 | #version 150 core
3 |
4 | #define MAX_NUM_TOTAL_LIGHTS 16
5 | #define MAX_NUM_SHAPES 256
6 |
7 | const float PI = 3.1415926535897932384626433832795;
8 | const float PI_2 = 1.57079632679489661923;
9 |
10 | struct Material {
11 | vec4 u_Emissive;
12 | vec4 u_Effect;
13 | };
14 |
15 | struct Light {
16 | vec4 propagation;
17 | vec4 center;
18 | vec4 color;
19 | };
20 |
21 | layout (std140) uniform cb_FragmentArgs {
22 | int u_LightCount;
23 | };
24 |
25 | layout (std140) uniform cb_MaterialArgs {
26 | Material material[MAX_NUM_SHAPES];
27 | };
28 |
29 | layout (std140) uniform u_Lights {
30 | Light light[MAX_NUM_TOTAL_LIGHTS];
31 | };
32 |
33 | in VertexData {
34 | vec4 Position;
35 | vec3 Normal;
36 | mat3 TBN;
37 | vec2 TexCoord;
38 | vec3 BaryCoord;
39 | flat int PrimIndex;
40 | } v_In;
41 |
42 | out vec4 o_Color;
43 |
44 | const float FREQ_X = 25.0;
45 | const float FREQ_Y = 19.1;
46 |
47 | void main() {
48 | const vec4 kd = vec4(0.2, 0.2, 0.2, 1.0);
49 | const vec4 ks = vec4(1.0, 1.0, 1.0, 1.0);
50 | const vec4 kp = vec4(64.0, 32.0, 64.0, 1.0);
51 |
52 |
53 | float dx = 2 * clamp(v_In.TexCoord.x, 0, 1) - 1;
54 | float dy = 2 * clamp(v_In.TexCoord.y, 0, 1) - 1;
55 | float r = min(1, dx * dx + dy * dy);
56 |
57 | vec4 u_Emissive = material[v_In.PrimIndex].u_Emissive;
58 | vec4 u_Effect = material[v_In.PrimIndex].u_Effect;
59 |
60 | float a = 2 * PI * (r - u_Effect.y);
61 | float f = u_Effect.x;
62 |
63 | vec4 color = u_Emissive;
64 |
65 | dx += (1 - r) * cos(a * FREQ_X) * f;
66 | dy += (1 - r) * sin(a * FREQ_Y) * f;
67 | r = min(1, dx * dx + dy * dy);
68 | vec3 normal = v_In.TBN * normalize(vec3(dx, dy, sqrt(1-r)));
69 |
70 | for (int i = 0; i < u_LightCount; i++) {
71 | vec4 delta = light[i].center - v_In.Position;
72 | float dist = length(delta);
73 | float inv_dist = 1. / dist;
74 | vec4 light_to_point_normal = delta * inv_dist;
75 | float intensity = dot(light[i].propagation.xyz,
76 | vec3(1., inv_dist, inv_dist * inv_dist));
77 | float lambert = max(0, dot(light_to_point_normal, vec4(normal, 0.0)));
78 |
79 | vec4 specular;
80 | if (lambert >= 0.0) {
81 | // Blinn-Phong:
82 | vec3 lightDir = light_to_point_normal.xyz;
83 | vec3 viewDir = vec3(0.0, 0.0, 1.0); // ortho, normalize(-v_In.Position.xyz); perspective
84 | vec3 halfDir = normalize(lightDir + viewDir); // can be done in vertex shader
85 | float specAngle = max(dot(halfDir, normal), 0.0);
86 | specular = pow(vec4(specAngle), kp);
87 | } else {
88 | specular = vec4(0.0);
89 | }
90 | color += light[i].color * intensity * (kd * lambert + ks * specular);
91 | }
92 |
93 | o_Color.rgb = color.rgb * color.a;
94 | o_Color.a = 0;
95 | }
96 |
--------------------------------------------------------------------------------
/resources/shaders/forward/point_ball.geom:
--------------------------------------------------------------------------------
1 | // point_ball.geom
2 | #version 150 core
3 |
4 | layout(triangles) in;
5 | layout(triangle_strip, max_vertices = 30) out;
6 |
7 | in VertexData {
8 | vec4 Position;
9 | vec3 Normal;
10 | mat3 TBN;
11 | vec2 TexCoord;
12 | vec3 BaryCoord;
13 | flat int PrimIndex;
14 | }v_In[3];
15 |
16 | out VertexData {
17 | vec4 Position;
18 | vec3 Normal;
19 | mat3 TBN;
20 | vec2 TexCoord;
21 | vec3 BaryCoord;
22 | flat int PrimIndex;
23 | }v_Out;
24 |
25 | struct V {
26 | vec4 GlPosition;
27 | vec4 Position;
28 | vec3 Normal;
29 | mat3 TBN;
30 | vec2 TexCoord;
31 | vec3 BaryCoord;
32 | int PrimIndex;
33 | };
34 |
35 | void emit_vertex(V v) {
36 | gl_Position = v.GlPosition;
37 | v_Out.Position = v.Position;
38 | v_Out.Normal = v.Normal;
39 | v_Out.TBN = v.TBN;
40 | v_Out.TexCoord = v.TexCoord;
41 | v_Out.BaryCoord = v.BaryCoord;
42 | v_Out.PrimIndex = v.PrimIndex;
43 | EmitVertex();
44 | }
45 |
46 | const float PI = 3.1415926535897932384626433832795;
47 | const int N = 15;
48 | const float D = 2 * PI / N;
49 | const mat2 R = mat2(cos(D), -sin(D), sin(D), cos(D));
50 |
51 | V read_vert(int i) {
52 | V result;
53 | result.GlPosition = gl_in[i].gl_Position;
54 | result.Position = v_In[i].Position;
55 | result.TexCoord = v_In[i].TexCoord;
56 | result.Normal = v_In[i].Normal;
57 | result.TBN = v_In[i].TBN;
58 | result.BaryCoord = v_In[i].BaryCoord;
59 | result.PrimIndex = v_In[i].PrimIndex;
60 | return result;
61 | }
62 |
63 | void main() {
64 | // triangle o, u, v -> (o, u) and (o, v) are a vector
65 | // basis for local "ball space"
66 | V o = read_vert(0);
67 | V u = read_vert(1);
68 | V v = read_vert(2);
69 |
70 | V u0 = u;
71 | u.GlPosition -= o.GlPosition;
72 | u.Position -= o.Position;
73 | u.TexCoord -= o.TexCoord;
74 |
75 | v.GlPosition -= o.GlPosition;
76 | v.Position -= o.Position;
77 | v.TexCoord -= o.TexCoord;
78 |
79 | // we use a clock "hand" and go round the circle,
80 | // rotating the "hand" counterclockwise
81 | // of D radians, D = 2*pi/N, each step
82 | vec2 unit = vec2(1, 0);
83 | V prev;
84 | V hand = u0;
85 | // we build a triangle fan of N triangles using (o, hand(i), hand(i-1))
86 | // as the vertices.
87 | for (int i = 0; i < N; ++i) {
88 | prev = hand;
89 | unit = R * unit;
90 |
91 | hand.GlPosition = unit.x * u.GlPosition + unit.y * v.GlPosition
92 | + o.GlPosition;
93 | hand.Position = unit.x * u.Position + unit.y * v.Position + o.Position;
94 | hand.TexCoord = unit.x * u.TexCoord + unit.y * v.TexCoord + o.TexCoord;
95 |
96 | if (i % 3 != 0) {
97 | emit_vertex(o);
98 | emit_vertex(prev);
99 | emit_vertex(hand);
100 | EndPrimitive();
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/resources/shaders/forward/ripple_particle.frag:
--------------------------------------------------------------------------------
1 | // ripple_particle.frag
2 | #version 150 core
3 |
4 | #define MAX_NUM_TOTAL_LIGHTS 16
5 | #define MAX_NUM_SHAPES 256
6 |
7 | const float PI = 3.1415926535897932384626433832795;
8 | const float PI_2 = 1.57079632679489661923;
9 |
10 |
11 | struct Material {
12 | vec4 u_Emissive;
13 | vec4 u_Effect;
14 | };
15 |
16 | struct Light {
17 | vec4 propagation;
18 | vec4 center;
19 | vec4 color;
20 | };
21 |
22 | layout (std140) uniform cb_FragmentArgs {
23 | int u_LightCount;
24 | };
25 |
26 | layout (std140) uniform cb_MaterialArgs {
27 | Material material[MAX_NUM_SHAPES];
28 | };
29 |
30 | layout (std140) uniform u_Lights {
31 | Light light[MAX_NUM_TOTAL_LIGHTS];
32 | };
33 |
34 | in VertexData {
35 | vec4 Position;
36 | vec3 Normal; // unused
37 | mat3 TBN; // unused
38 | vec2 TexCoord;
39 | vec3 BaryCoord;
40 | flat int PrimIndex;
41 | }v_In;
42 |
43 | out vec4 o_Color;
44 |
45 | void main() {
46 | vec4 u_Emissive = material[v_In.PrimIndex].u_Emissive;
47 | vec4 u_Effect = material[v_In.PrimIndex].u_Effect;
48 |
49 | float intensity = u_Effect.x;
50 | float phase = u_Effect.y;
51 | float frequency = u_Effect.z;
52 | float ratio = u_Effect.w;
53 |
54 | float dx = 2 * clamp(v_In.TexCoord.x, 0, 1) - 1;
55 | float dy = 2 * clamp(v_In.TexCoord.y, 0, 1) - 1;
56 | float r = min(1, sqrt(dx * dx + dy * dy * ratio));
57 |
58 | float e = intensity; // or something
59 | float w = cos((phase - r) * frequency);
60 | float f = exp(-r) * w * w * float(r < 1);
61 |
62 | vec4 color = u_Emissive * e * f;
63 | o_Color.rgb = color.rgb * color.a;
64 | o_Color.a = 0;
65 | }
66 |
--------------------------------------------------------------------------------
/resources/shaders/forward/triangle_edge.geom:
--------------------------------------------------------------------------------
1 | // triangle_edge.geom
2 | #version 150 core
3 |
4 | layout(triangles) in;
5 | layout(triangle_strip, max_vertices=9) out;
6 |
7 | in VertexData {
8 | vec4 Position;
9 | vec3 Normal;
10 | mat3 TBN;
11 | vec2 TexCoord;
12 | vec3 BaryCoord;
13 | flat int PrimIndex;
14 | }v_In[3];
15 |
16 | out VertexData {
17 | vec4 Position;
18 | vec3 Normal;
19 | mat3 TBN;
20 | vec2 TexCoord;
21 | vec3 BaryCoord;
22 | flat int PrimIndex;
23 | }v_Out;
24 |
25 | struct V {
26 | vec4 GlPosition;
27 | vec4 Position;
28 | vec3 Normal;
29 | mat3 TBN;
30 | vec2 TexCoord;
31 | vec3 BaryCoord;
32 | int PrimIndex;
33 | };
34 |
35 | void emit_vertex(V v) {
36 | gl_Position = v.GlPosition;
37 | v_Out.Position = v.Position;
38 | v_Out.Normal = v.Normal;
39 | v_Out.TBN = v.TBN;
40 | v_Out.TexCoord = v.TexCoord;
41 | v_Out.BaryCoord = v.BaryCoord;
42 | v_Out.PrimIndex = v.PrimIndex;
43 | EmitVertex();
44 | }
45 |
46 | V read_vert(int i) {
47 | V result;
48 | result.GlPosition = gl_in[i].gl_Position;
49 | result.Position = v_In[i].Position;
50 | result.TexCoord = v_In[i].TexCoord;
51 | result.Normal = v_In[i].Normal;
52 | result.TBN = v_In[i].TBN;
53 | result.PrimIndex = v_In[i].PrimIndex;
54 | return result;
55 | }
56 |
57 | const float SCALE = 1.1;
58 | const float SCALE_UV = 1.1;
59 |
60 | void main() {
61 | V o = read_vert(0);
62 | V u = read_vert(1);
63 | V v = read_vert(2);
64 |
65 | V bc;
66 | bc.Position = (o.Position + u.Position + v.Position) / 3.0;
67 | bc.GlPosition = (o.GlPosition + u.GlPosition + v.GlPosition) / 3.0;
68 | bc.Normal = (o.Normal + u.Normal + v.Normal) / 3.0;
69 | bc.TBN = o.TBN;
70 | bc.TexCoord = (o.TexCoord + u.TexCoord + v.TexCoord) / 3.0;
71 | bc.PrimIndex = o.PrimIndex;
72 |
73 | u.GlPosition.xy = (u.GlPosition.xy - o.GlPosition.xy) * SCALE + o.GlPosition.xy;
74 | v.GlPosition.xy = (v.GlPosition.xy - o.GlPosition.xy) * SCALE + o.GlPosition.xy;
75 |
76 | u.TexCoord = (u.TexCoord - o.TexCoord) * SCALE_UV + o.TexCoord;
77 | v.TexCoord = (v.TexCoord - o.TexCoord) * SCALE_UV + o.TexCoord;
78 |
79 | o.BaryCoord = vec3(1,0,0);
80 | u.BaryCoord = vec3(0,1,0);
81 | v.BaryCoord = vec3(0,0,1);
82 |
83 | emit_vertex(o);
84 | emit_vertex(u);
85 | emit_vertex(v);
86 | EndPrimitive();
87 | }
88 |
--------------------------------------------------------------------------------
/resources/shaders/forward/unlit.vert:
--------------------------------------------------------------------------------
1 | // unlit.vert
2 | #version 150 core
3 |
4 | #define MAX_NUM_SHAPES 256
5 |
6 | layout (std140) uniform cb_CameraArgs {
7 | uniform mat4 u_Proj;
8 | uniform mat4 u_View;
9 | };
10 |
11 | struct Model {
12 | mat4 transform;
13 | };
14 |
15 | layout (std140) uniform u_ModelArgs {
16 | Model u_Model[MAX_NUM_SHAPES];
17 | };
18 |
19 | in vec3 a_Pos;
20 | in vec3 a_Normal;
21 | in vec3 a_Tangent;
22 | in vec2 a_TexCoord;
23 | in int a_PrimIndex;
24 |
25 | out VertexData {
26 | vec4 Position;
27 | vec3 Normal;
28 | mat3 TBN;
29 | vec2 TexCoord;
30 | vec3 BaryCoord;
31 | flat int PrimIndex;
32 | }v_Out;
33 |
34 | void main() {
35 | mat4 model4 = u_Model[a_PrimIndex].transform;
36 | v_Out.Position = model4 * vec4(a_Pos, 1.0);
37 |
38 | v_Out.Normal = vec3(1,0,0);
39 | v_Out.TBN = mat3(1);
40 | v_Out.BaryCoord = vec3(1/3.,1/3.,1/3.);
41 | v_Out.TexCoord = a_TexCoord;
42 | v_Out.PrimIndex = a_PrimIndex;
43 |
44 | gl_Position = u_Proj * u_View * v_Out.Position;
45 | }
46 |
--------------------------------------------------------------------------------
/rust-oids.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/app/capture.rs:
--------------------------------------------------------------------------------
1 | use app::constants::*;
2 | use chrono::DateTime;
3 | use chrono::Utc;
4 | use gl;
5 | use glutin;
6 | use glutin::GlContext;
7 | use image;
8 | use image::ImageBuffer;
9 | use num::Integer;
10 | use rayon;
11 | use std::fs::create_dir_all;
12 | use std::path::PathBuf;
13 |
14 | pub struct Capture {
15 | seq: usize,
16 | capture_path: PathBuf,
17 | capture_prefix: String,
18 | enabled: bool,
19 | w: u32,
20 | h: u32,
21 | }
22 |
23 | impl Capture {
24 | // Initializes capture system
25 | pub fn init(window: &glutin::GlWindow) -> Capture {
26 | //use gl;
27 | gl::ReadPixels::load_with(|s| window.get_proc_address(s) as *const _);
28 | let (w, h) = window.get_inner_size().unwrap();
29 | let now: DateTime = Utc::now();
30 | Capture {
31 | seq: 0,
32 | capture_path: PathBuf::from(CAPTURE_FOLDER).join(now.format(CAPTURE_FOLDER_TIMESTAMP_PATTERN).to_string()),
33 | capture_prefix: String::from(CAPTURE_FILENAME_PREFIX),
34 | enabled: false,
35 | w,
36 | h,
37 | }
38 | }
39 |
40 | // Capture current framebuffer if recording is enabled
41 | pub fn screen_grab(&mut self) {
42 | if self.enabled {
43 | let w = self.w;
44 | let h = self.h;
45 | let mut buf: Vec<[u8; 3]> = vec![[0u8; 3]; (w * h) as usize];
46 | unsafe {
47 | gl::ReadPixels(0, 0, w as i32, h as i32, gl::RGB, gl::UNSIGNED_BYTE, buf.as_mut_ptr() as *mut _);
48 | }
49 | self.seq += 1;
50 | let filename = self.capture_prefix.clone() + &format!("{:08}.png", self.seq);
51 | let full_path = self.capture_path.join(filename);
52 | rayon::spawn(move || {
53 | // throws it into the background
54 | let mut img = ImageBuffer::new(w, h);
55 | for (idx, rgb) in (0u32..).zip(buf) {
56 | let (i, j) = idx.div_mod_floor(&w);
57 | img.put_pixel(j, h - i - 1, image::Rgb(rgb));
58 | }
59 | match img.save(full_path.clone()) {
60 | Ok(_) => println!("Saved image {}", full_path.to_str().unwrap()),
61 | Err(_) => println!("Could not save image {}", full_path.to_str().unwrap()),
62 | }
63 | });
64 | }
65 | }
66 |
67 | // Remote control, detects state changes
68 | pub fn enable(&mut self, enabled: bool) {
69 | if enabled != self.enabled {
70 | self.toggle()
71 | }
72 | }
73 |
74 | // Starts/restarts recording
75 | pub fn start(&mut self) {
76 | match create_dir_all(self.capture_path.clone()) {
77 | Ok(_) => self.enabled = true,
78 | Err(msg) => error!("Could not create capture directory {}: {}", self.capture_path.to_str().unwrap(), msg),
79 | }
80 | }
81 |
82 | // Stops recording and flushes
83 | pub fn stop(&mut self) { self.enabled = false; }
84 |
85 | pub fn enabled(&self) -> bool { self.enabled }
86 |
87 | pub fn toggle(&mut self) {
88 | if self.enabled {
89 | self.stop();
90 | } else {
91 | self.start();
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/app/constants.rs:
--------------------------------------------------------------------------------
1 | use core::clock::{SecondsValue, SpeedFactor};
2 | use frontend::input::AxisValue;
3 | use std::f32::consts;
4 |
5 | pub const DEFAULT_WINDOW_WIDTH: u32 = 1280;
6 | pub const DEFAULT_WINDOW_HEIGHT: u32 = 720;
7 | pub const VIEW_SCALE_BASE: f32 = 100.0;
8 | pub const VIEW_ZOOM_MAX: f32 = 8.0;
9 | pub const VIEW_ZOOM_MIN: f32 = 1. / 4.0;
10 | pub const VIEW_ZOOM_MULTIPLIER: f32 = consts::SQRT_2;
11 | pub const VIEW_ZOOM_DURATION: f32 = 0.25;
12 | pub const CAMERA_IMPULSE: f32 = 5.0;
13 | pub const CAMERA_INERTIA: f32 = 4.0;
14 | pub const CAMERA_LIMIT: f32 = 0.5;
15 | pub const FRAME_SMOOTH_COUNT: usize = 120;
16 | pub const FRAME_TIME_TARGET: SecondsValue = 1. / 60.;
17 | pub const LOG_INTERVAL: SecondsValue = 5.0;
18 | pub const SAVE_INTERVAL: SecondsValue = 300.0;
19 | pub const DEAD_ZONE: AxisValue = 0.3f32;
20 | pub const TURN_SPEED: f32 = consts::PI * 200.;
21 | pub const DEBUG_DRAW_BRAKE_SCALE: f32 = 0.05;
22 | pub const DEBUG_DRAW_MOVE_SCALE: f32 = 0.05;
23 | pub const MIN_FRAME_LENGTH: SecondsValue = (1.0 / 1000.0) as SecondsValue;
24 | pub const MAX_FRAME_LENGTH: SecondsValue = (1.0 / 30.0) as SecondsValue;
25 | pub const THRUST_POWER: f32 = 5000.;
26 | pub const POWER_BOOST: f32 = 100.;
27 | pub const DRAG_COEFFICIENT: f32 = 0.000_001;
28 | #[allow(unused)]
29 | pub const COMPASS_SPRING_POWER: f32 = 1000.0;
30 | pub const JOINT_UPPER_ANGLE: f32 = consts::PI / 6.;
31 | pub const JOINT_LOWER_ANGLE: f32 = -consts::PI / 6.;
32 | pub const JOINT_FREQUENCY: f32 = 5.0;
33 | pub const JOINT_DAMPING_RATIO: f32 = 0.9;
34 | pub const LINEAR_DAMPING_DEFAULT: f32 = 0.8;
35 | pub const LINEAR_DAMPING_PLAYER: f32 = 2.0;
36 | pub const ANGULAR_DAMPING: f32 = 0.9;
37 | pub const PICK_EPS: f32 = 0.001f32;
38 | pub const DEFAULT_RESOURCE_CHARGE: f32 = 0.8;
39 | pub const DEFAULT_SPORE_CHARGE: f32 = 0.8;
40 | pub const DEFAULT_MINION_CHARGE: f32 = 0.3;
41 | pub const INITIAL_SPAWN_RADIUS_RATIO: f32 = 0.1;
42 | pub const INITIAL_SPAWN_RADIUS_SLICES: f32 = 19.;
43 | pub const INITIAL_SPAWN_RADIUS_INCREMENT: f32 = 0.5;
44 | pub const MATURITY_MINION_DEFAULT: f32 = 0.5;
45 | pub const MATURITY_DEFAULT: f32 = 1.0;
46 | pub const GROWTH_COST_RATIO: f32 = 0.1;
47 | pub const SPAWN_COST_THRESHOLD: f32 = 0.95;
48 | pub const SPAWN_COST_RATIO: f32 = 0.75;
49 | pub const COLLISION_BASE_COST: f32 = 0.5;
50 | pub const WORLD_RADIUS: f32 = 80.;
51 | pub const DEFAULT_CHARGE_DECAY_TIME: SecondsValue = 0.5;
52 | pub const MINION_CHARGE_DECAY_TIME: SecondsValue = 0.25;
53 | pub const PLAYER_CHARGE_DECAY_TIME: SecondsValue = 0.1;
54 | pub const PLAYER_CHARGE_INITIAL_VALUE: f32 = 25.0;
55 | pub const PLAYER_CHARGE_REST_VALUE: f32 = 0.05;
56 | pub const EMITTER_DISTANCE: f32 = 40.;
57 | pub const EMITTER_PERIOD: SecondsValue = 0.2;
58 | #[allow(unused)]
59 | pub const EMITTER_SPREAD_ANGLE: f32 = consts::PI / 12.;
60 | pub const EMITTER_SPREAD_JITTER: f32 = 0.1;
61 | pub const EMITTER_INTENSITY_DECAY: f32 = 1.0;
62 | pub const BULLET_SPEED_SCALE: f32 = 100.;
63 | pub const BULLET_FIRE_RATE_SCALE: SecondsValue = 0.5;
64 | pub const BULLET_FULL_CHARGE: SecondsValue = 1.0;
65 | pub const BULLET_FIRE_RATE: SecondsValue = 45.0;
66 | pub const DENSITY_DEFAULT: f32 = 1.0;
67 | pub const DENSITY_RESOURCE: f32 = DENSITY_DEFAULT;
68 | pub const DENSITY_PLAYER: f32 = 1.0;
69 | pub const DENSITY_MINION: f32 = 0.2;
70 | pub const DENSITY_SPORE: f32 = 0.5;
71 | pub const RESTITUTION_DEFAULT: f32 = 0.6;
72 | pub const RESTITUTION_PLAYER: f32 = 0.1;
73 | pub const FRICTION_DEFAULT: f32 = 0.7;
74 | pub const FRICTION_PLAYER: f32 = 0.6;
75 | pub const B2_LINEAR_SLOP: f32 = 0.005;
76 | pub const DEFAULT_MINION_GENE_POOL_FILE: &str = "minion_gene_pool.csv";
77 | pub const DEFAULT_MINION_GENE_POOL: &[&str] = &[
78 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
79 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
80 | "GzB2lQVwM00tTAm5gwajjf4wc0a5GzB2lQVwM00tTAm5gwajjf4wc0a5",
81 | "GzB2lQdwM10vQEu5zwaPgDhfq2v8GzB2lQdwM10vQEu5zwaPgDhfq2v8",
82 | ];
83 |
84 | pub const COLOR_SUNSHINE: [f32; 4] = [400.0, 90.0, 1.0, 1.0];
85 | pub const COLOR_TRANSPARENT: [f32; 4] = [0.; 4];
86 | pub const COLOR_WHITE: [f32; 4] = [1.; 4];
87 | #[allow(unused)]
88 | pub const COLOR_BLACK: [f32; 4] = [0., 0., 0., 1.];
89 |
90 | pub const DEFAULT_RESOURCE_GENE_POOL: &[&str] = &["GyA21QoQ", "M00sWS0M"];
91 |
92 | pub const CONFIG_DIR_HOME: &str = ".config/rust-oids";
93 | pub const CONFIG_DIR_SAVED_STATE: &str = "saved_state";
94 | pub const CONFIG_DIR_RESOURCES: &str = "resources";
95 | pub const DUMP_FILE_PATTERN_CSV: &str = "%Y%m%d_%H%M%S.csv";
96 | pub const DUMP_FILE_PATTERN_JSON: &str = "%Y%m%d_%H%M%S.json";
97 |
98 | pub const CAPTURE_FOLDER_TIMESTAMP_PATTERN: &str = "%Y%m%d_%H%M%S";
99 | pub const CAPTURE_FOLDER: &str = "capture";
100 | pub const CAPTURE_FILENAME_PREFIX: &str = "capture_";
101 |
102 | pub const AMBIENT_LIGHTS: &[[f32; 4]] = &[
103 | [1.0, 1.0, 1.0, 1.0],
104 | [3.1, 3.1, 3.1, 1.0],
105 | [10.0, 10.0, 10.0, 1.0],
106 | [31.0, 31.0, 31.0, 1.0],
107 | [100.0, 100.0, 100.0, 1.0],
108 | [0.001, 0.001, 0.001, 1.0],
109 | [0.01, 0.01, 0.01, 1.0],
110 | [0.1, 0.1, 0.1, 1.0],
111 | [0.31, 0.31, 0.31, 0.5],
112 | ];
113 |
114 | pub const SPEED_FACTORS: &[SpeedFactor] = &[1.0, 0.5, 0.2, 0.1, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0];
115 |
116 | pub const BACKGROUNDS: &[[f32; 4]] = &[
117 | [0.05, 0.07, 0.1, 1.0],
118 | [0.5, 0.5, 0.5, 0.5],
119 | [1.0, 1.0, 1.0, 1.0],
120 | [3.1, 3.1, 3.1, 1.0],
121 | [10.0, 10.0, 10.0, 1.0],
122 | [0., 0., 0., 1.0],
123 | [0.01, 0.01, 0.01, 1.0],
124 | ];
125 |
--------------------------------------------------------------------------------
/src/app/controller.rs:
--------------------------------------------------------------------------------
1 | use super::events::Event;
2 | use super::events::VectorDirection;
3 | use app::constants::DEAD_ZONE;
4 | use app::constants::*;
5 | use core::clock::Seconds;
6 | use core::geometry::*;
7 | use core::view::ViewTransform;
8 | use core::view::WorldTransform;
9 | use frontend::input;
10 |
11 | use super::events::Event::*;
12 | use frontend::input::Axis::*;
13 | use frontend::input::Key::*;
14 |
15 | const KEY_HELD_MAP: &[(input::Key, Event)] = &[(W, CamUp(1.)), (S, CamDown(1.)), (A, CamLeft(1.)), (D, CamRight(1.))];
16 |
17 | const KEY_PRESSED_ONCE_MAP: &[(input::Key, Event)] = &[
18 | (F5, Reload),
19 | (F1, ToggleGui),
20 | (GamepadL3, ToggleGui),
21 | (N0, CamReset),
22 | (Home, CamReset),
23 | (GamepadSelect, ToggleCapture),
24 | (GamepadStart, TogglePause),
25 | (KpHome, CamReset),
26 | (GamepadDPadUp, ZoomIn),
27 | (GamepadDPadDown, ZoomOut),
28 | (GamepadR3, ZoomReset),
29 | (Plus, ZoomIn),
30 | (Minus, ZoomOut),
31 | (N1, ZoomReset),
32 | (F6, SaveGenePoolToFile),
33 | (F7, SaveWorldToFile),
34 | (F8, RestartFromCheckpoint),
35 | (F10, ToggleDebug),
36 | (F12, ToggleCapture),
37 | (GamepadStart, ToggleDebug),
38 | (Z, DeselectAll),
39 | (L, NextLight),
40 | (B, NextBackground),
41 | (K, PrevLight),
42 | (V, PrevBackground),
43 | (G, PrevSpeedFactor),
44 | (GamepadL1, PrevSpeedFactor),
45 | (H, NextSpeedFactor),
46 | (GamepadR1, NextSpeedFactor),
47 | (P, TogglePause),
48 | (Esc, AppQuit),
49 | (MouseScrollUp, ZoomIn),
50 | (MouseScrollDown, ZoomOut),
51 | ];
52 |
53 | pub struct DefaultController {}
54 |
55 | pub trait InputController {
56 | fn update(input_state: &I, view_transform: &V, world_transform: &W, dt: Seconds) -> Vec
57 | where
58 | V: ViewTransform,
59 | W: WorldTransform,
60 | I: input::InputRead;
61 | }
62 |
63 | impl DefaultController {
64 | fn interpret_key_press(input_state: &I, events: &mut Vec)
65 | where I: input::InputRead {
66 | for (key_held, event) in KEY_HELD_MAP {
67 | if input_state.key_pressed(*key_held) {
68 | events.push(*event);
69 | }
70 | }
71 |
72 | for (key_pressed, event) in KEY_PRESSED_ONCE_MAP {
73 | if input_state.key_pressed(*key_pressed) {
74 | events.push(*event);
75 | }
76 | }
77 | }
78 |
79 | fn interpret_mouse_move(
80 | input_state: &I,
81 | events: &mut Vec,
82 | view_transform: &V,
83 | world_transform: &W,
84 | dt: Seconds,
85 | ) -> Position
86 | where
87 | V: ViewTransform,
88 | W: WorldTransform,
89 | I: input::InputRead,
90 | {
91 | let mouse_window_pos = input_state.mouse_position();
92 | let mouse_view_pos = view_transform.to_view(mouse_window_pos);
93 | let mouse_world_pos = world_transform.to_world(mouse_view_pos);
94 |
95 | if input_state.key_once(MouseLeft) && input_state.any_ctrl_pressed() {
96 | events.push(Event::PickMinion(mouse_world_pos));
97 | };
98 |
99 | if input_state.key_once(MouseMiddle) {
100 | if input_state.any_ctrl_pressed() {
101 | events.push(Event::RandomizeMinion(mouse_world_pos));
102 | } else {
103 | events.push(Event::NewMinion(mouse_world_pos));
104 | }
105 | }
106 |
107 | match input_state.dragging() {
108 | input::Dragging::Begin(_, from) => {
109 | let from = world_transform.to_world(from);
110 | events.push(Event::BeginDrag(from, from));
111 | }
112 | input::Dragging::Dragging(_, from, to) => {
113 | events.push(Event::Drag(world_transform.to_world(from), world_transform.to_world(to)));
114 | }
115 | input::Dragging::End(_, from, to, prev) => {
116 | let mouse_vel = (view_transform.to_view(prev) - to) / dt.into();
117 | events.push(Event::EndDrag(world_transform.to_world(from), world_transform.to_world(to), mouse_vel));
118 | }
119 | _ => {}
120 | }
121 | mouse_world_pos
122 | }
123 |
124 | fn interpret_trigger_fire(input_state: &I, events: &mut Vec)
125 | where I: input::InputRead {
126 | let mouse_left_pressed = input_state.key_pressed(MouseLeft) && !input_state.any_ctrl_pressed();
127 | let firerate = input_state.gamepad_axis(0, L2);
128 | let firepower = input_state.gamepad_axis(0, R2);
129 | if firepower >= DEAD_ZONE {
130 | events.push(Event::PrimaryTrigger(firepower, f64::from(firerate)));
131 | } else if input_state.key_pressed(Space) || mouse_left_pressed {
132 | events.push(Event::PrimaryTrigger(1.0, 1.0));
133 | }
134 | }
135 |
136 | fn interpret_movement(input_state: &I, events: &mut Vec, mouse_world_pos: Position)
137 | where I: input::InputRead {
138 | let yaw = Position { x: input_state.gamepad_axis(0, RStickX), y: input_state.gamepad_axis(0, RStickY) };
139 |
140 | let thrust = Position {
141 | x: if input_state.key_pressed(Right) {
142 | 1.
143 | } else if input_state.key_pressed(Left) {
144 | -1.
145 | } else {
146 | input_state.gamepad_axis(0, LStickX)
147 | },
148 |
149 | y: if input_state.key_pressed(Up) {
150 | 1.
151 | } else if input_state.key_pressed(Down) {
152 | -1.
153 | } else {
154 | input_state.gamepad_axis(0, LStickY)
155 | },
156 | };
157 |
158 | use cgmath::InnerSpace;
159 | let magnitude = thrust.magnitude2();
160 | let mouse_left_pressed = input_state.key_pressed(MouseLeft) && !input_state.any_ctrl_pressed();
161 | events.push(Event::VectorThrust(
162 | if magnitude >= DEAD_ZONE { Some(thrust / magnitude.max(1.)) } else { None },
163 | if input_state.key_pressed(PageUp) {
164 | VectorDirection::Turn(TURN_SPEED)
165 | } else if input_state.key_pressed(PageDown) {
166 | VectorDirection::Turn(-TURN_SPEED)
167 | } else if yaw.magnitude() >= DEAD_ZONE {
168 | VectorDirection::Orientation(yaw)
169 | } else if mouse_left_pressed {
170 | VectorDirection::LookAt(mouse_world_pos)
171 | } else if magnitude > 0.1 {
172 | VectorDirection::FromVelocity
173 | } else {
174 | VectorDirection::None
175 | },
176 | ));
177 | }
178 | }
179 |
180 | impl InputController for DefaultController {
181 | fn update(input_state: &I, view_transform: &V, world_transform: &W, dt: Seconds) -> Vec
182 | where
183 | V: ViewTransform,
184 | W: WorldTransform,
185 | I: input::InputRead, {
186 | let mut events = Vec::new();
187 |
188 | Self::interpret_key_press(input_state, &mut events);
189 | let mouse_world_pos = Self::interpret_mouse_move(input_state, &mut events, view_transform, world_transform, dt);
190 | Self::interpret_trigger_fire(input_state, &mut events);
191 | Self::interpret_movement(input_state, &mut events, mouse_world_pos);
192 |
193 | events
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/app/events.rs:
--------------------------------------------------------------------------------
1 | use core::clock::*;
2 | use core::geometry::*;
3 |
4 | #[derive(Clone, Copy, Debug)]
5 | pub enum VectorDirection {
6 | None,
7 | Orientation(Position),
8 | LookAt(Position),
9 | Turn(Angle),
10 | FromVelocity,
11 | }
12 |
13 | #[derive(Clone, Copy, Debug)]
14 | pub enum Event {
15 | CamUp(f32),
16 | CamDown(f32),
17 | CamLeft(f32),
18 | CamRight(f32),
19 |
20 | ZoomIn,
21 | ZoomOut,
22 | ZoomReset,
23 |
24 | VectorThrust(Option, VectorDirection),
25 | PrimaryTrigger(f32, SecondsValue),
26 | PrimaryFire(f32, SecondsValue),
27 |
28 | CamReset,
29 |
30 | NextLight,
31 | PrevLight,
32 |
33 | NextBackground,
34 | PrevBackground,
35 |
36 | NextSpeedFactor,
37 | PrevSpeedFactor,
38 |
39 | Reload,
40 | SaveGenePoolToFile,
41 | SaveWorldToFile,
42 | RestartFromCheckpoint,
43 | ToggleDebug,
44 |
45 | TogglePause,
46 | ToggleGui,
47 | ToggleCapture,
48 |
49 | AppQuit,
50 |
51 | NewMinion(Position),
52 | RandomizeMinion(Position),
53 |
54 | PickMinion(Position),
55 | SelectMinion(usize),
56 | DeselectAll,
57 |
58 | BeginDrag(Position, Position),
59 | Drag(Position, Position),
60 | EndDrag(Position, Position, Velocity),
61 | }
62 |
--------------------------------------------------------------------------------
/src/app/main.rs:
--------------------------------------------------------------------------------
1 | use frontend::audio::{self, SoundSystem};
2 | use frontend::gfx_window_glutin;
3 | use frontend::input::EventMapper;
4 | use frontend::input::GamepadEventLoop;
5 | use frontend::render;
6 | use frontend::render::{formats, Overlay, Renderer};
7 | use frontend::ui;
8 | use std::path;
9 |
10 | use conrod;
11 |
12 | use core::clock::{seconds, Hourglass, SecondsValue, SystemTimer};
13 | use core::math::Directional;
14 | use core::resource::filesystem::ResourceLoader;
15 | use core::resource::filesystem::ResourceLoaderBuilder;
16 | use ctrlc;
17 | use std::sync::atomic::{AtomicBool, Ordering};
18 | use std::sync::Arc;
19 |
20 | use app;
21 | use app::capture::Capture;
22 | use app::constants::*;
23 | use glutin;
24 | use glutin::GlContext;
25 | use winit::{self, KeyboardInput, VirtualKeyCode, WindowEvent};
26 |
27 | pub fn make_resource_loader(config_home: &path::Path) -> ResourceLoader {
28 | ResourceLoaderBuilder::new()
29 | .add(path::Path::new(CONFIG_DIR_RESOURCES))
30 | .add(config_home.join(CONFIG_DIR_RESOURCES).as_path())
31 | .add(config_home.join(CONFIG_DIR_SAVED_STATE).as_path())
32 | .add(path::Path::new("/usr/local/share/rust-oids").join(CONFIG_DIR_RESOURCES).as_path())
33 | .add(path::Path::new("/usr/share/rust-oids").join(CONFIG_DIR_RESOURCES).as_path())
34 | .build()
35 | }
36 |
37 | pub fn main_loop(
38 | minion_gene_pool: &str,
39 | config_home: path::PathBuf,
40 | world_file: Option,
41 | fullscreen: Option,
42 | width: Option,
43 | height: Option,
44 | audio_device: Option,
45 | ) {
46 | let mut events_loop = winit::EventsLoop::new();
47 | let mut maybe_gamepad = GamepadEventLoop::new();
48 |
49 | let builder = winit::WindowBuilder::new().with_title("Rust-oids".to_string());
50 | let builder = if let Some(monitor_index) = fullscreen {
51 | let monitor = events_loop.get_available_monitors().nth(monitor_index).expect("Please enter a valid monitor ID");
52 | println!("Using {:?}", monitor.get_name());
53 | builder.with_fullscreen(Some(monitor))
54 | } else {
55 | builder.with_dimensions(width.unwrap_or(DEFAULT_WINDOW_WIDTH), height.unwrap_or(DEFAULT_WINDOW_HEIGHT))
56 | };
57 | let context_builder = glutin::ContextBuilder::new().with_vsync(true);
58 |
59 | let (window, mut device, mut factory, mut frame_buffer, mut depth_buffer) =
60 | gfx_window_glutin::init::(
61 | builder,
62 | context_builder,
63 | &events_loop,
64 | );
65 | let (w, h, _, _) = frame_buffer.get_dimensions();
66 | let mut capture = Capture::init(&window);
67 |
68 | let mut encoder = factory.create_command_buffer().into();
69 |
70 | let res = make_resource_loader(&config_home);
71 |
72 | let renderer = &mut render::ForwardRenderer::new(&mut factory, &mut encoder, &res, &frame_buffer).unwrap();
73 | let mapper = app::WinitEventMapper::new();
74 |
75 | // Create a new game and run it.
76 | let mut app =
77 | app::App::new(u32::from(w), u32::from(h), VIEW_SCALE_BASE, config_home, &res, minion_gene_pool, world_file);
78 |
79 | let mut ui = ui::conrod_ui::Ui::new(&res, &mut factory, &frame_buffer, f64::from(window.hidpi_factor()))
80 | .expect("Unable to create UI");
81 |
82 | let audio = audio::ThreadedSoundSystem::new(audio_device).expect("Failure in audio initialization");
83 | let mut no_audio = ui::NullAlertPlayer::new();
84 | let mut audio_alert_player = audio::ThreadedAlertPlayer::new(audio);
85 | app.init(app::SystemMode::Interactive);
86 |
87 | 'main: loop {
88 | maybe_gamepad = maybe_gamepad.map(|mut gamepad| {
89 | gamepad.poll_events(|event| app.on_input_event(&event));
90 | gamepad
91 | });
92 |
93 | events_loop.poll_events(|event| {
94 | if app.has_ui_overlay() {
95 | if let Some(event) = conrod::backend::winit::convert_event(event.clone(), window.window()) {
96 | ui.push_event(event);
97 | }
98 | }
99 | if let winit::Event::WindowEvent { event, .. } = event {
100 | match event {
101 | WindowEvent::Resized(new_width, new_height) => {
102 | gfx_window_glutin::update_views(&window, &mut frame_buffer, &mut depth_buffer);
103 | renderer.resize_to(&frame_buffer).expect("Unable to resize window");
104 | ui.resize_to(&frame_buffer).expect("Unable to resize window");
105 | app.on_resize(new_width, new_height);
106 | }
107 | WindowEvent::Closed => app.quit(),
108 | WindowEvent::KeyboardInput {
109 | input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::F5), .. },
110 | ..
111 | } => renderer.rebuild().unwrap(),
112 | e =>
113 | if let Some(i) = mapper.translate(&e) {
114 | app.on_input_event(&i)
115 | },
116 | }
117 | }
118 | });
119 |
120 | capture.enable(app.is_capturing());
121 |
122 | if !app.is_running() {
123 | capture.stop();
124 | app.save_world_to_file();
125 | break 'main;
126 | }
127 |
128 | let speed_factor = app.speed_factors.get();
129 | let frame_update = if capture.enabled() || speed_factor > 5.0 {
130 | // forces 60Hz simulation for frame capture and fast forward
131 | app.update_with_quantum(Some(FRAME_TIME_TARGET))
132 | } else {
133 | // update and measure, let the app determine the appropriate frame length
134 | app.update()
135 | };
136 |
137 | let camera = render::Camera::ortho(app.camera.position(), app.viewport.scale, app.viewport.ratio);
138 |
139 | let environment = app.environment();
140 |
141 | renderer.setup_frame(&camera, environment.background_color, &environment.lights);
142 | // draw a frame
143 | renderer.begin_frame();
144 | // draw the scene
145 | app.paint(renderer);
146 | // post-render effects and tone mapping
147 | renderer.resolve_frame_buffer();
148 |
149 | if app.has_ui_overlay() {
150 | let screen = ui::Screen::Main(frame_update);
151 | renderer.overlay(|_, encoder| {
152 | ui.update_and_draw_screen(&screen, encoder);
153 | });
154 | ui.handle_events();
155 |
156 | for app_event in ui.drain_app_events() {
157 | app.interact(app_event)
158 | }
159 | }
160 |
161 | if speed_factor < 10.0 {
162 | app.play_alerts(&mut audio_alert_player);
163 | } else {
164 | app.play_alerts(&mut no_audio);
165 | };
166 |
167 | // push the commands
168 | renderer.end_frame(&mut device);
169 | capture.screen_grab();
170 |
171 | window.swap_buffers().expect("swap_buffers() failed");
172 | renderer.cleanup(&mut device);
173 | }
174 | }
175 |
176 | pub fn main_loop_headless(minion_gene_pool: &str, config_home: path::PathBuf, world_file: Option) {
177 | const WIDTH: u32 = 1024;
178 | const HEIGHT: u32 = 1024;
179 | let res = make_resource_loader(&config_home);
180 |
181 | let mut app = app::App::new(WIDTH, HEIGHT, VIEW_SCALE_BASE, config_home, &res, minion_gene_pool, world_file);
182 | let mut no_audio = ui::NullAlertPlayer::new();
183 | app.init(app::SystemMode::Batch);
184 |
185 | let running = Arc::new(AtomicBool::new(true));
186 | let r = running.clone();
187 |
188 | ctrlc::set_handler(move || {
189 | r.store(false, Ordering::SeqCst);
190 | })
191 | .expect("Error setting Ctrl-C handler");
192 |
193 | let wall_clock = SystemTimer::new();
194 | let mut output_hourglass = Hourglass::new(seconds(LOG_INTERVAL), &wall_clock);
195 | let mut save_hourglass = Hourglass::new(seconds(SAVE_INTERVAL), &wall_clock);
196 |
197 | const FRAME_SIMULATION_LENGTH: SecondsValue = FRAME_TIME_TARGET;
198 | 'main: loop {
199 | if !app.is_running() {
200 | break 'main;
201 | }
202 |
203 | if !running.load(Ordering::SeqCst) {
204 | eprintln!("Interrupted, exiting");
205 | app.save_world_to_file();
206 | break 'main;
207 | }
208 | // update and measure
209 | let simulation_update = app.simulate(seconds(FRAME_SIMULATION_LENGTH));
210 | if save_hourglass.flip_if_expired(&wall_clock) {
211 | app.save_world_to_file();
212 | }
213 |
214 | app.play_alerts(&mut no_audio);
215 | if output_hourglass.flip_if_expired(&wall_clock) {
216 | info!(
217 | "C: {} E: {:.3} FT: {:.2} P: {} X: {}",
218 | simulation_update.count,
219 | simulation_update.elapsed,
220 | simulation_update.dt,
221 | simulation_update.population,
222 | simulation_update.extinctions
223 | )
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/app/paint.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use frontend::render;
3 | use frontend::render::Draw;
4 | use frontend::render::Style;
5 |
6 | impl App {
7 | pub fn environment(&self) -> Environment {
8 | let light_color = self.lights.get();
9 |
10 | let mut emitter_lights = self
11 | .world
12 | .feeders()
13 | .iter()
14 | .map(|e| {
15 | let intensity = e.intensity();
16 | render::Light::PointLight {
17 | position: e.transform().position,
18 | color: [
19 | light_color[0] * intensity,
20 | light_color[1] * intensity,
21 | light_color[2] * intensity,
22 | light_color[3],
23 | ],
24 | attenuation: [0.2, 0.8, 0.1, 0.1],
25 | }
26 | })
27 | .collect::>();
28 | if let Some(ref segment) = self.world.get_player_segment() {
29 | let position = segment.transform.position;
30 | let intensity = segment.state.charge();
31 | emitter_lights.push(render::Light::PointLight {
32 | position,
33 | color: [
34 | COLOR_SUNSHINE[0] * intensity,
35 | COLOR_SUNSHINE[1] * intensity,
36 | COLOR_SUNSHINE[2] * intensity,
37 | 1.0,
38 | ],
39 | attenuation: [0.0, 0.0, 0.1, 0.05],
40 | });
41 | }
42 |
43 | Environment { background_color: self.backgrounds.get(), lights: emitter_lights.into_boxed_slice() }
44 | }
45 |
46 | fn paint_particles(&self, renderer: &mut R)
47 | where R: render::DrawBuffer {
48 | let mut batch = render::PrimitiveBuffer::new();
49 | for particle in self.world.particles() {
50 | let appearance = render::Appearance::new(particle.color(), particle.effect());
51 | let transform = Self::from_transform(&particle.transform()) * Matrix4::from_scale(particle.scale());
52 | batch.draw_quad(Some(Style::Particle), transform, 1.0, appearance);
53 | }
54 | renderer.draw_buffer(batch);
55 | }
56 |
57 | fn paint_particles_trails(&self, renderer: &mut R)
58 | where R: render::DrawBuffer {
59 | let mut batch = render::PrimitiveBuffer::new();
60 | for particle in self.world.particles() {
61 | use cgmath::SquareMatrix;
62 | let appearance = render::Appearance::new(particle.color(), particle.effect());
63 | batch.draw_lines(None, Matrix4::identity(), particle.trail(), appearance);
64 | }
65 | renderer.draw_buffer(batch);
66 | }
67 |
68 | fn paint_minions(&self, renderer: &mut R)
69 | where R: render::DrawBuffer {
70 | for (_, swarm) in self.world.swarms().iter() {
71 | let mut batch_buffer = render::PrimitiveBuffer::new();
72 | for (_, agent) in swarm.agents().iter() {
73 | let energy_left = agent.state.energy_ratio();
74 | let phase = agent.state.phase();
75 | for segment in agent.segments() {
76 | let body_transform = Self::from_transform(&segment.transform());
77 |
78 | let mesh = &segment.mesh();
79 | let fixture_scale = Matrix4::from_scale(segment.growing_radius());
80 | let transform = body_transform * fixture_scale;
81 |
82 | let appearance = render::Appearance::new(segment.color(), [energy_left, phase, 0., 0.]);
83 |
84 | match mesh.shape {
85 | obj::Shape::Ball { .. } => {
86 | batch_buffer.draw_ball(None, transform, appearance);
87 | }
88 | obj::Shape::Star { .. } => {
89 | batch_buffer.draw_star(Some(Style::Lit), transform, &mesh.vertices[..], appearance);
90 | }
91 | obj::Shape::Poly { .. } => {
92 | batch_buffer.draw_star(Some(Style::Lit), transform, &mesh.vertices[..], appearance);
93 | }
94 | obj::Shape::Box { ratio, .. } => {
95 | batch_buffer.draw_quad(Some(Style::Lit), transform, ratio, appearance);
96 | }
97 | obj::Shape::Triangle { .. } => {
98 | batch_buffer.draw_triangle(Some(Style::Lit), transform, &mesh.vertices[0..3], appearance);
99 | }
100 | }
101 | }
102 | }
103 | renderer.draw_buffer(batch_buffer);
104 | }
105 | }
106 |
107 | fn paint_extent(&self, renderer: &mut R)
108 | where R: render::Draw {
109 | use cgmath::SquareMatrix;
110 | let extent = &self.world.extent;
111 | let points = &[
112 | extent.min,
113 | Position::new(extent.min.x, extent.max.y),
114 | extent.max,
115 | Position::new(extent.max.x, extent.min.y),
116 | extent.min,
117 | ];
118 | renderer.draw_lines(None, Matrix4::identity(), points, render::Appearance::rgba(self.lights.get()));
119 | renderer.draw_quad(
120 | Some(Style::Stage),
121 | Matrix4::from_scale(extent.max.x - extent.min.x),
122 | 1.,
123 | render::Appearance::new(self.backgrounds.get(), self.world.phase()),
124 | );
125 | }
126 |
127 | fn paint_feeders(&self, renderer: &mut R)
128 | where R: render::DrawBuffer {
129 | let mut batch_buffer = render::PrimitiveBuffer::new();
130 | for e in self.world.feeders() {
131 | let transform = Self::from_transform(&e.transform());
132 | batch_buffer.draw_ball(None, transform, render::Appearance::rgba(self.lights.get()));
133 | }
134 | renderer.draw_buffer(batch_buffer)
135 | }
136 |
137 | fn paint_hud(&self, renderer: &mut R)
138 | where R: render::DrawBuffer {
139 | if self.debug_flags.contains(DebugFlags::DEBUG_TARGETS) {
140 | let mut batch_buffer = render::PrimitiveBuffer::new();
141 | use cgmath::*;
142 | for (_, agent) in self.world.agents(world::agent::AgentType::Minion).iter() {
143 | if agent.state.selected() {
144 | let sensor = agent.first_segment(segment::Flags::HEAD).unwrap();
145 | let p0 = sensor.transform.position;
146 | let a0 = sensor.transform.angle;
147 | let radar_range = sensor.growing_radius() * 10.;
148 | let p1 = agent.state.target_position();
149 | batch_buffer.draw_lines(
150 | Some(Style::DebugLines),
151 | Matrix4::identity(),
152 | &[p0, p1],
153 | render::Appearance::rgba([1., 1., 0., 1.]),
154 | );
155 |
156 | let t0 = p1 - p0;
157 | let t = t0.normalize_to(t0.magnitude().min(radar_range));
158 | let m = Matrix2::from_angle(Rad(a0));
159 |
160 | let v = m * (-Position::unit_y());
161 | let p2 = p0 + v.normalize_to(t.dot(v));
162 | batch_buffer.draw_lines(
163 | Some(Style::DebugLines),
164 | Matrix4::identity(),
165 | &[p0, p2],
166 | render::Appearance::rgba([0., 1., 0., 1.]),
167 | );
168 |
169 | let u = m * (-Position::unit_x());
170 | let p3 = p0 + u.normalize_to(t.perp_dot(v));
171 | batch_buffer.draw_lines(
172 | Some(Style::DebugLines),
173 | Matrix4::identity(),
174 | &[p0, p3],
175 | render::Appearance::rgba([0., 1., 0., 1.]),
176 | );
177 |
178 | let trajectory = agent.state.trajectory();
179 | let appearance = render::Appearance::new(sensor.color(), [2.0, 1.0, 0., 0.]);
180 | batch_buffer.draw_lines(Some(Style::DebugLines), Matrix4::identity(), &trajectory, appearance);
181 |
182 | for segment in agent.segments().iter() {
183 | match segment.state.intent {
184 | segment::Intent::Brake(v) => {
185 | let p0 = segment.transform.position;
186 | let p1 = p0 + v * DEBUG_DRAW_BRAKE_SCALE;
187 | batch_buffer.draw_lines(
188 | Some(Style::DebugLines),
189 | Matrix4::identity(),
190 | &[p0, p1],
191 | render::Appearance::rgba([2., 0., 0., 1.]),
192 | );
193 | }
194 | segment::Intent::Move(v) => {
195 | let p0 = segment.transform.position;
196 | let p1 = p0 + v * DEBUG_DRAW_MOVE_SCALE;
197 | batch_buffer.draw_lines(
198 | Some(Style::DebugLines),
199 | Matrix4::identity(),
200 | &[p0, p1],
201 | render::Appearance::rgba([0., 0., 2., 1.]),
202 | );
203 | }
204 | _ => {}
205 | }
206 | }
207 | }
208 | }
209 | renderer.draw_buffer(batch_buffer)
210 | };
211 | }
212 |
213 | pub fn paint(&self, renderer: &mut R)
214 | where R: render::Draw + render::DrawBatch + render::DrawBuffer {
215 | self.paint_feeders(renderer);
216 | self.paint_minions(renderer);
217 | self.paint_particles(renderer);
218 | self.paint_particles_trails(renderer);
219 | self.paint_extent(renderer);
220 | self.paint_hud(renderer);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/app/winit_event.rs:
--------------------------------------------------------------------------------
1 | use core::geometry::Position;
2 | use frontend::input;
3 | use frontend::input::Key;
4 | use winit;
5 | use winit::{KeyboardInput, MouseScrollDelta, WindowEvent};
6 |
7 | pub struct WinitEventMapper;
8 |
9 | impl WinitEventMapper {
10 | pub fn new() -> Self { WinitEventMapper {} }
11 | }
12 |
13 | impl input::EventMapper for WinitEventMapper {
14 | fn translate(&self, e: &winit::WindowEvent) -> Option {
15 | fn keymap(vk: winit::VirtualKeyCode) -> Option {
16 | macro_rules! winit_map (
17 | [$($gkey:ident -> $ekey:ident),*] => (
18 | match vk {
19 | $(winit::VirtualKeyCode::$gkey => Some(Key::$ekey)),
20 | *,
21 | _ => None,
22 | }
23 | )
24 | );
25 | winit_map![
26 | Key0 -> N0,
27 | Key1 -> N1,
28 | Key2 -> N2,
29 | Key3 -> N3,
30 | Key4 -> N4,
31 | Key5 -> N5,
32 | Key6 -> N6,
33 | Key7 -> N7,
34 | Key8 -> N8,
35 | Key9 -> N9,
36 | F1 -> F1,
37 | F2 -> F2,
38 | F3 -> F3,
39 | F4 -> F4,
40 | F5 -> F5,
41 | F6 -> F6,
42 | F7 -> F7,
43 | F8 -> F8,
44 | F9 -> F9,
45 | F10 -> F10,
46 | F11 -> F11,
47 | F12 -> F12,
48 | Home -> Home,
49 | Down -> Down,
50 | Up -> Up,
51 | Left -> Left,
52 | Right -> Right,
53 | PageUp -> PageUp,
54 | PageDown -> PageDown,
55 | LControl -> LCtrl,
56 | RControl -> RCtrl,
57 | LShift -> LShift,
58 | RShift -> RShift,
59 | LAlt -> LAlt,
60 | RAlt -> RAlt,
61 | LWin -> LSuper,
62 | RWin -> RSuper,
63 | A -> A,
64 | B -> B,
65 | C -> C,
66 | D -> D,
67 | E -> E,
68 | F -> F,
69 | G -> G,
70 | H -> H,
71 | I -> I,
72 | J -> J,
73 | K -> K,
74 | L -> L,
75 | M -> M,
76 | N -> N,
77 | O -> O,
78 | P -> P,
79 | Q -> Q,
80 | R -> R,
81 | S -> S,
82 | T -> T,
83 | U -> U,
84 | V -> V,
85 | W -> W,
86 | X -> X,
87 | Y -> Y,
88 | Z -> Z,
89 | Equals -> Plus,
90 | Subtract -> Minus,
91 | Space -> Space,
92 | Escape -> Esc
93 | ]
94 | }
95 | fn mousewheelmap(_: f32, dy: f32) -> Option {
96 | if dy > 0. {
97 | Some(input::Key::MouseScrollUp)
98 | } else if dy < 0. {
99 | Some(input::Key::MouseScrollDown)
100 | } else {
101 | None
102 | }
103 | }
104 | fn mousemap(button: winit::MouseButton) -> Option {
105 | match button {
106 | winit::MouseButton::Left => Some(input::Key::MouseLeft),
107 | winit::MouseButton::Right => Some(input::Key::MouseRight),
108 | winit::MouseButton::Middle => Some(input::Key::MouseMiddle),
109 | winit::MouseButton::Other(5) => Some(input::Key::MouseScrollUp),
110 | winit::MouseButton::Other(6) => Some(input::Key::MouseScrollDown),
111 | _ => None,
112 | }
113 | }
114 | fn state_map(element_state: winit::ElementState) -> input::State {
115 | match element_state {
116 | winit::ElementState::Pressed => input::State::Down,
117 | winit::ElementState::Released => input::State::Up,
118 | }
119 | }
120 | match *e {
121 | WindowEvent::KeyboardInput {
122 | input: KeyboardInput { state: element_state, virtual_keycode: vk, .. },
123 | ..
124 | } => vk.and_then(keymap).map(|key| input::Event::Key(state_map(element_state), key)),
125 | WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(dx, dy), .. } =>
126 | mousewheelmap(dx, dy).map(|key| input::Event::Key(input::State::Down, key)),
127 | WindowEvent::MouseInput { state: element_state, button, .. } =>
128 | mousemap(button).map(|key| input::Event::Key(state_map(element_state), key)),
129 | WindowEvent::CursorMoved { position: (x, y), .. } =>
130 | Some(input::Event::Mouse(Position::new(x as f32, y as f32))),
131 | _ => None,
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/backend/messagebus/mod.rs:
--------------------------------------------------------------------------------
1 | use app::Event;
2 | use backend::world::alert::Alert;
3 | use backend::world::particle::Emitter;
4 | use std::sync::mpsc;
5 | use std::sync::mpsc::{Receiver, Sender};
6 |
7 | #[derive(Clone)]
8 | pub enum Message {
9 | Alert(Alert),
10 | Event(Event),
11 | NewEmitter(Emitter),
12 | }
13 |
14 | impl From for Message {
15 | fn from(value: Emitter) -> Self { Message::NewEmitter(value) }
16 | }
17 |
18 | impl From for Message {
19 | fn from(value: Event) -> Self { Message::Event(value) }
20 | }
21 |
22 | impl From for Message {
23 | fn from(value: Alert) -> Self { Message::Alert(value) }
24 | }
25 |
26 | impl Into> for Message {
27 | fn into(self) -> Option {
28 | match self {
29 | Message::NewEmitter(emitter) => Some(emitter),
30 | _ => None,
31 | }
32 | }
33 | }
34 |
35 | impl Into> for Message {
36 | fn into(self) -> Option {
37 | match self {
38 | Message::Alert(alert) => Some(alert),
39 | _ => None,
40 | }
41 | }
42 | }
43 |
44 | pub trait ReceiveDrain
45 | where M: Send + Clone {
46 | fn drain(&self) -> Vec;
47 | fn purge(&self);
48 | }
49 |
50 | struct Filter
51 | where M: Send {
52 | accept: Box bool>,
53 | sender: Sender,
54 | }
55 |
56 | pub struct PubSub
57 | where M: Send {
58 | filters: Vec>,
59 | }
60 |
61 | pub trait Outbox {
62 | fn post(&self, message: M);
63 | }
64 |
65 | pub trait Whiteboard
66 | where M: Send + Clone {
67 | fn subscribe(&mut self, accept: Box bool>) -> Inbox;
68 | }
69 |
70 | pub struct Inbox
71 | where M: Send + Clone {
72 | receiver: Receiver,
73 | }
74 |
75 | impl Outbox for PubSub
76 | where M: Send + Clone
77 | {
78 | fn post(&self, message: M) {
79 | for filter in &self.filters {
80 | if (*filter.accept)(&message) {
81 | let send_status = filter.sender.send(message.clone());
82 | if send_status.is_err() {
83 | debug!("Dropped message");
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | impl PubSub
91 | where M: Send + Clone
92 | {
93 | pub fn new() -> Self { PubSub { filters: Vec::new() } }
94 | }
95 |
96 | impl Whiteboard for PubSub
97 | where M: Send + Clone
98 | {
99 | fn subscribe(&mut self, accept: Box bool>) -> Inbox {
100 | let (sender, receiver) = mpsc::channel();
101 | self.filters.push(Filter { accept, sender });
102 | Inbox { receiver }
103 | }
104 | }
105 |
106 | impl ReceiveDrain for Inbox
107 | where M: Send + Clone
108 | {
109 | fn drain(&self) -> Vec {
110 | let mut acc = Vec::new();
111 | 'out: loop {
112 | match self.receiver.try_recv() {
113 | Ok(received) => {
114 | acc.push(received);
115 | }
116 | Err(_) => {
117 | break 'out;
118 | }
119 | }
120 | }
121 | acc
122 | }
123 |
124 | fn purge(&self) {
125 | 'out: loop {
126 | if self.receiver.try_recv().is_err() {
127 | break 'out;
128 | }
129 | }
130 | }
131 | }
132 |
133 | pub trait DrainInto: ReceiveDrain
134 | where M: Send + Clone + Into> {
135 | fn drain_into(&self) -> Vec { self.drain().into_iter().map(Into::into).filter_map(|i| i).collect::>() }
136 | }
137 |
--------------------------------------------------------------------------------
/src/backend/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod messagebus;
2 | pub mod obj;
3 | pub mod systems;
4 | pub mod world;
5 |
--------------------------------------------------------------------------------
/src/backend/obj.rs:
--------------------------------------------------------------------------------
1 | use app::constants::*;
2 | use core::color;
3 | use core::geometry::Transform;
4 | use core::geometry::*;
5 | use std::f32::consts::*;
6 |
7 | pub type Rgba = color::Rgba;
8 |
9 | pub type Id = usize;
10 | pub type SegmentIndex = u8;
11 | pub type BoneIndex = u8;
12 | pub type AttachmentIndex = u8;
13 | // pub type PhysicsHandle = Id;
14 |
15 | #[derive(Clone)]
16 | pub enum Shape {
17 | Ball { radius: f32 },
18 | Box { radius: f32, ratio: f32 },
19 | Star { radius: f32, n: u8, ratio1: f32, ratio2: f32 },
20 | Poly { radius: f32, n: i8 },
21 | Triangle { radius: f32, angle1: f32, angle2: f32 },
22 | }
23 |
24 | impl Shape {
25 | pub fn radius(&self) -> f32 {
26 | match *self {
27 | Shape::Ball { radius } => radius,
28 | Shape::Box { radius, .. } => radius,
29 | Shape::Star { radius, .. } => radius,
30 | Shape::Poly { radius, .. } => radius,
31 | Shape::Triangle { radius, .. } => radius,
32 | }
33 | }
34 |
35 | pub fn length(&self) -> usize {
36 | match *self {
37 | Shape::Ball { .. } => 12,
38 | Shape::Box { .. } => 8,
39 | Shape::Poly { n, .. } => n.abs() as usize * 2,
40 | Shape::Star { n, .. } => n as usize * 2,
41 | Shape::Triangle { .. } => 3,
42 | }
43 | }
44 |
45 | pub fn is_convex(&self) -> bool {
46 | match *self {
47 | Shape::Ball { .. } => true,
48 | Shape::Box { .. } => true,
49 | Shape::Poly { .. } => true,
50 | Shape::Star { .. } => false,
51 | Shape::Triangle { .. } => true,
52 | }
53 | }
54 |
55 | pub fn mid(&self) -> isize { self.length() as isize / 2 }
56 | }
57 |
58 | #[derive(Clone, Copy)]
59 | pub enum Winding {
60 | CW = 1,
61 | CCW = -1,
62 | }
63 |
64 | impl Winding {
65 | pub fn xunit(self) -> f32 { f32::from(self as i16) }
66 | }
67 |
68 | impl Shape {
69 | pub fn new_ball(radius: f32) -> Self { Shape::Ball { radius } }
70 |
71 | pub fn new_box(radius: f32, ratio: f32) -> Self { Shape::Box { radius, ratio } }
72 |
73 | pub fn new_star(n: u8, radius: f32, ratio1: f32, ratio2: f32) -> Self {
74 | assert!(n > 1);
75 | assert!(radius > 0.);
76 | assert!(ratio1 > 0.);
77 | assert!(ratio2 > 0.);
78 | assert!(ratio1 * ratio2 <= 1.);
79 |
80 | Shape::Star { radius, n, ratio1, ratio2 }
81 | }
82 |
83 | pub fn new_poly(n: i8, radius: f32) -> Self {
84 | assert!(n > 2 || n < -2);
85 |
86 | Shape::Poly { radius, n }
87 | }
88 |
89 | pub fn new_triangle(radius: f32, angle1: f32, angle2: f32) -> Self { Shape::Triangle { radius, angle1, angle2 } }
90 |
91 | pub fn vertices(&self, winding: Winding) -> Box<[Position]> {
92 | let xunit = winding.xunit();
93 | match *self {
94 | // first point is always unit y
95 | Shape::Ball { .. } => {
96 | let n = 12usize;
97 | (0..n)
98 | .map(|i| {
99 | let p = (i as f32) / (n as f32) * 2. * PI;
100 | let (sp, cp) = p.sin_cos();
101 | Position::new(xunit * sp, cp)
102 | })
103 | .collect()
104 | }
105 | Shape::Box { ratio, .. } => {
106 | let w2 = xunit * ratio;
107 | vec![
108 | Position::new(0., 1.),
109 | Position::new(w2, 1.),
110 | Position::new(w2, 0.),
111 | Position::new(w2, -1.),
112 | Position::new(0., -1.),
113 | Position::new(-w2, -1.),
114 | Position::new(-w2, 0.),
115 | Position::new(-w2, 1.),
116 | ]
117 | }
118 | Shape::Poly { n, .. } => {
119 | let phi = PI / f32::from(n.abs());
120 | let ratio1 = f32::cos(phi);
121 | let ratio = &[1., ratio1.powi(i32::from(n.signum()))];
122 | (0..(2 * n.abs()))
123 | .map(|i| {
124 | let p = f32::from(i) * phi;
125 | let r = ratio[i as usize % 2];
126 | let (sp, cp) = p.sin_cos();
127 | Position::new(xunit * r * sp, r * cp)
128 | })
129 | .collect()
130 | }
131 | Shape::Star { n, ratio1, ratio2, .. } => {
132 | let mut damp = 1.;
133 | let ratio = &[ratio1, ratio2];
134 | (0..(2 * n))
135 | .map(|i| {
136 | let p = f32::from(i) * (PI / f32::from(n));
137 | let r = f32::max(damp, 0.01); // zero is bad!
138 | damp *= ratio[i as usize % 2];
139 | let (sp, cp) = p.sin_cos();
140 | Position::new(xunit * r * sp, r * cp)
141 | })
142 | .collect()
143 | }
144 | Shape::Triangle { angle1, angle2, .. } => {
145 | let (sa1, ca1) = angle1.sin_cos();
146 | let (sa2, ca2) = angle2.sin_cos();
147 | vec![Position::new(0., 1.), Position::new(xunit * sa1, ca1), Position::new(xunit * sa2, ca2)]
148 | }
149 | }
150 | .into_boxed_slice()
151 | }
152 | }
153 |
154 | bitflags! {
155 | struct MeshFlags: u8 {
156 | const CW = 0x1;
157 | const CCW = 0x2;
158 | const CONVEX = 0x4;
159 | }
160 | }
161 |
162 | #[derive(Clone)]
163 | pub struct Mesh {
164 | flags: MeshFlags,
165 | pub shape: Shape,
166 | pub vertices: Box<[Position]>,
167 | }
168 |
169 | impl Mesh {
170 | pub fn from_shape(shape: Shape, winding: Winding) -> Self {
171 | let vertices = shape.vertices(winding);
172 | let winding_flags = match winding {
173 | Winding::CW => MeshFlags::CW,
174 | Winding::CCW => MeshFlags::CCW,
175 | };
176 |
177 | let shape_flags = if shape.is_convex() {
178 | MeshFlags::CONVEX
179 | } else {
180 | let classifier = PolygonType::classify(vertices.as_ref());
181 | if classifier.is_convex() {
182 | MeshFlags::CONVEX
183 | } else {
184 | MeshFlags::empty()
185 | }
186 | };
187 | Mesh { shape, flags: winding_flags | shape_flags, vertices }
188 | }
189 |
190 | pub fn vertex(&self, index: usize) -> Position { self.vertices[index % self.vertices.len()] }
191 |
192 | pub fn scaled_vertex(&self, index: usize) -> Position { self.vertex(index) * self.shape.radius() }
193 |
194 | #[inline]
195 | #[allow(dead_code)]
196 | pub fn is_convex(&self) -> bool { self.flags.contains(MeshFlags::CONVEX) }
197 |
198 | #[inline]
199 | pub fn winding(&self) -> Winding {
200 | if self.flags.contains(MeshFlags::CW) {
201 | Winding::CW
202 | } else {
203 | Winding::CCW
204 | }
205 | }
206 | }
207 |
208 | pub trait Identified {
209 | fn id(&self) -> Id;
210 | }
211 |
212 | pub trait Transformable {
213 | fn transform(&self) -> &Transform;
214 | fn transform_to(&mut self, t: Transform);
215 | }
216 |
217 | pub trait Motionable {
218 | fn motion(&self) -> &Motion;
219 | fn motion_to(&mut self, m: Motion);
220 | }
221 |
222 | #[derive(Clone)]
223 | pub struct Material {
224 | pub density: f32,
225 | pub restitution: f32,
226 | pub friction: f32,
227 | pub linear_damping: f32,
228 | pub angular_damping: f32,
229 | }
230 |
231 | #[derive(Clone)]
232 | pub struct Livery {
233 | pub albedo: Rgba,
234 | pub frequency: f32,
235 | pub phase: f32,
236 | pub amplitude: f32,
237 | pub seed: f32,
238 | }
239 |
240 | impl Default for Material {
241 | fn default() -> Self {
242 | Material {
243 | density: DENSITY_DEFAULT,
244 | restitution: RESTITUTION_DEFAULT,
245 | friction: FRICTION_DEFAULT,
246 | linear_damping: LINEAR_DAMPING_DEFAULT,
247 | angular_damping: ANGULAR_DAMPING,
248 | }
249 | }
250 | }
251 |
252 | impl Default for Livery {
253 | fn default() -> Self { Livery { albedo: [1., 1., 1., 1.], frequency: 0.5, phase: 0., amplitude: 0.5, seed: 0. } }
254 | }
255 |
256 | pub trait Solid {
257 | fn material(&self) -> &Material;
258 | fn livery(&self) -> &Livery;
259 | }
260 |
261 | pub trait Geometry {
262 | fn mesh(&self) -> &Mesh;
263 | }
264 |
265 | pub trait Drawable: Geometry {
266 | fn color(&self) -> Rgba;
267 | }
268 |
--------------------------------------------------------------------------------
/src/backend/systems/ai.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use app::constants::*;
3 | use backend::obj;
4 | use backend::obj::Identified;
5 | use backend::obj::Transformable;
6 | use backend::world;
7 | use backend::world::agent;
8 | use backend::world::agent::Personality;
9 | use backend::world::agent::TypedAgent;
10 | use backend::world::segment;
11 | use backend::world::segment::Intent;
12 | use cgmath::*;
13 | use core::geometry::Position;
14 | use itertools::Itertools;
15 | use std::collections::HashMap;
16 | use std::f32::consts;
17 |
18 | type IdPositionMap = HashMap;
19 |
20 | pub struct AiSystem {
21 | beacons: Box<[Position]>,
22 | targets: IdPositionMap,
23 | }
24 |
25 | impl System for AiSystem {
26 | fn clear(&mut self) {
27 | self.beacons = Box::new([]);
28 | self.targets.clear();
29 | }
30 |
31 | fn import(&mut self, world: &world::World) {
32 | self.beacons = world.feeders().iter().map(|e| e.transform().position).collect::>().into_boxed_slice();
33 | self.targets = world
34 | .agents(agent::AgentType::Resource)
35 | .iter()
36 | .filter(|&(_, ref v)| v.state.is_active())
37 | .map(|(_, v)| (v.id(), v.transform().position))
38 | .collect::>();
39 | }
40 |
41 | fn export(&self, world: &mut world::World, _outbox: &dyn Outbox) {
42 | Self::update_minions(&self.targets, &self.beacons, &mut world.agents_mut(agent::AgentType::Minion));
43 | }
44 | }
45 |
46 | impl Default for AiSystem {
47 | fn default() -> Self { AiSystem { beacons: Box::new([]), targets: HashMap::new() } }
48 | }
49 |
50 | impl AiSystem {
51 | fn update_minions(targets: &IdPositionMap, beacons: &[Position], minions: &mut agent::AgentMap) {
52 | fn nearest_beacon<'a>(beacons: &'a [Position], p: &'a Position) -> &'a Position {
53 | beacons.iter().fold1(|n, b| if (p - n).magnitude2() < (p - b).magnitude2() { n } else { b }).unwrap_or(p)
54 | }
55 |
56 | for (_, agent) in minions.iter_mut() {
57 | let brain = agent.brain().clone();
58 | let core = agent.first_segment(segment::Flags::CORE);
59 | let head = agent.first_segment(segment::Flags::SENSOR);
60 | if let Some(sensor) = head {
61 | let p0 = sensor.transform.position;
62 | let radar_range = sensor.growing_radius() * 10.;
63 | let current_target = *agent.state.target();
64 | let current_target_position = agent.state.target_position();
65 | // if our original target is dead then we need to find another one
66 | let new_target: Option<(obj::Id, Position)> = match current_target {
67 | None => targets
68 | .iter()
69 | .find(|&(_, &p)| (p - p0).magnitude() < radar_range)
70 | .map(|(&id, &position)| (id, position)),
71 | Some(id) => targets.get(&id).map(|&position| (id, position)),
72 | };
73 | // and failing that again, we target
74 | match new_target {
75 | None => agent.state.retarget(None, *nearest_beacon(beacons, ¤t_target_position)),
76 | Some((id, position)) => agent.state.retarget(Some(id), position),
77 | };
78 | // find where our target is in the world
79 | let target_position = agent.state.target_position();
80 | // and transform the world position into the head's frame
81 | let t0 = target_position - sensor.transform.position;
82 | let t = t0.normalize_to(t0.magnitude().min(radar_range));
83 | // direction in which the head is pointing, normalized
84 | let s = Matrix2::from_angle(Rad(sensor.transform.angle)) * (-Position::unit_y());
85 | // some proprioception, feeding back the angle betweent the neck and the first
86 | // torso
87 | let neck_angle = consts::PI + sensor.transform.angle
88 | - core.map(|t| t.transform.angle).unwrap_or(sensor.transform.angle);
89 | // we pass the relative position of the target decomposed in our frame of
90 | // reference to the neural network expecting four components we can use as
91 | // thresholds
92 | let r = agent.brain().response(&[neck_angle, t.dot(s), t.perp_dot(s), 0.]);
93 |
94 | let segments = &mut agent.segments_mut();
95 | let mut touch_accumulator = 0.0f32;
96 | for segment in segments.iter_mut() {
97 | let flags = &segment.flags;
98 | if flags.contains(segment::Flags::ACTUATOR) {
99 | let power = segment.state.charge() * segment.growing_radius().powi(2) * POWER_BOOST;
100 | let f = Matrix2::from_angle(Rad(segment.transform.angle)) * Position::unit_y() * power;
101 | let intent = if let Some(refs) = segment.state.last_touched {
102 | match refs.id().type_of() {
103 | agent::AgentType::Resource => Intent::Idle,
104 | _ => {
105 | let fear: f32 = brain.fear();
106 | touch_accumulator += COLLISION_BASE_COST / segment.state.maturity();
107 | Intent::RunAway(f * fear)
108 | }
109 | }
110 | } else if (flags.contains(segment::Flags::RUDDER | segment::Flags::LEFT)
111 | && r[0] > brain.hunger()) || (flags
112 | .contains(segment::Flags::RUDDER | segment::Flags::RIGHT)
113 | && r[1] > brain.hunger())
114 | {
115 | Intent::Move(-f)
116 | } else if flags.contains(segment::Flags::THRUSTER) && r[2] > brain.haste() {
117 | Intent::Move(f)
118 | } else if flags.contains(segment::Flags::BRAKE) && r[3] > brain.prudence() {
119 | Intent::Brake(-f)
120 | } else {
121 | Intent::Idle
122 | };
123 | match intent {
124 | Intent::Idle => segment.state.set_target_charge(brain.rest()),
125 | Intent::Move(_) => segment.state.set_target_charge(brain.thrust()),
126 | Intent::Brake(_) => segment.state.set_target_charge(brain.thrust()),
127 | Intent::RunAway(_) => segment.state.set_output_charge(brain.thrust()),
128 | _ => {}
129 | }
130 | segment.state.intent = intent;
131 | }
132 | }
133 | // touching costs energy, main body charges up
134 | if touch_accumulator > 0. {
135 | if let Some(ref mut segment) = segments.get_mut(0) {
136 | let thrust: f32 = brain.thrust();
137 | segment.state.set_output_charge(1.0f32.max(thrust * touch_accumulator));
138 | segment.state.set_target_charge(brain.rest());
139 | }
140 | }
141 | }
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/backend/systems/alife.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use app::constants::*;
3 | use backend::messagebus::Outbox;
4 | use backend::obj;
5 | use backend::obj::Identified;
6 | use backend::obj::Transformable;
7 | use backend::world;
8 | use backend::world::agent;
9 | use backend::world::alert;
10 | use backend::world::gen;
11 | use backend::world::particle;
12 | use backend::world::segment;
13 | use backend::world::AgentState;
14 | use core::clock::SimulationTimer;
15 | use core::geometry;
16 | use rand;
17 | use serialize::base64::{self, ToBase64};
18 | use std::collections::HashMap;
19 |
20 | type StateMap = HashMap;
21 | type GeneMap = HashMap;
22 |
23 | pub struct AlifeSystem {
24 | dt: Seconds,
25 | simulation_timer: SimulationTimer,
26 | source: Box<[world::Feeder]>,
27 | eaten: StateMap,
28 | touched: GeneMap,
29 | }
30 |
31 | impl System for AlifeSystem {
32 | fn clear(&mut self) {
33 | self.source = Box::new([]);
34 | self.eaten.clear();
35 | self.touched.clear();
36 | }
37 |
38 | fn import(&mut self, world: &world::World) {
39 | self.source = world.feeders().to_vec().into_boxed_slice();
40 | self.eaten = Self::find_eaten_resources(
41 | &world.agents(agent::AgentType::Minion),
42 | &world.agents(agent::AgentType::Resource),
43 | );
44 | self.touched =
45 | Self::find_touched_spores(&world.agents(agent::AgentType::Minion), &world.agents(agent::AgentType::Spore));
46 | }
47 |
48 | fn update(&mut self, _: &dyn AgentState, dt: Seconds) {
49 | self.dt = dt;
50 | self.simulation_timer.tick(dt);
51 | }
52 |
53 | fn export(&self, world: &mut world::World, outbox: &dyn Outbox) {
54 | Self::update_resources(
55 | self.dt,
56 | &self.simulation_timer,
57 | &mut world.agents_mut(agent::AgentType::Resource),
58 | &self.eaten,
59 | );
60 |
61 | let MinionEndState(spores, corpses) = Self::update_minions(
62 | outbox,
63 | self.dt,
64 | world.extent,
65 | &mut world.agents_mut(agent::AgentType::Minion),
66 | &self.eaten,
67 | );
68 |
69 | let SporeEndState(hatch, fertilised) = Self::update_spores(
70 | self.dt,
71 | &self.simulation_timer,
72 | &mut world.agents_mut(agent::AgentType::Spore),
73 | &self.touched,
74 | );
75 |
76 | for (transform, dna) in &*spores {
77 | outbox.post(alert::Alert::NewSpore.into());
78 | world.new_spore(outbox, transform.clone(), dna);
79 | }
80 |
81 | for (transform, dna) in &*hatch {
82 | outbox.post(alert::Alert::NewMinion.into());
83 | world.hatch_spore(outbox, transform.clone(), dna);
84 | }
85 |
86 | for (transforms, dna) in &*corpses {
87 | outbox.post(alert::Alert::DieMinion.into());
88 | for transform in &**transforms {
89 | world.decay_to_resource(outbox, transform.clone(), dna);
90 | }
91 | }
92 |
93 | for _ in 0..fertilised {
94 | outbox.post(alert::Alert::DieMinion.into());
95 | }
96 | }
97 | }
98 |
99 | impl Default for AlifeSystem {
100 | fn default() -> Self {
101 | AlifeSystem {
102 | dt: Seconds::new(1. / 60.),
103 | simulation_timer: SimulationTimer::new(),
104 | source: Box::new([]),
105 | eaten: StateMap::new(),
106 | touched: GeneMap::new(),
107 | }
108 | }
109 | }
110 |
111 | struct MinionEndState(Box<[(geometry::Transform, gen::Dna)]>, Box<[(Box<[geometry::Transform]>, gen::Dna)]>);
112 |
113 | struct SporeEndState(Box<[(geometry::Transform, gen::Dna)]>, usize);
114 |
115 | impl AlifeSystem {
116 | fn find_eaten_resources(minions: &agent::AgentMap, resources: &agent::AgentMap) -> StateMap {
117 | let mut eaten = HashMap::new();
118 | for agent in minions.values().filter(|&a| a.state.is_active()) {
119 | for segment in agent.segments.iter().filter(|&s| s.flags.contains(segment::Flags::MOUTH)) {
120 | if let Some(key) = segment.state.last_touched {
121 | if let Some(&agent::Agent { ref state, .. }) = resources.get(&key.id()) {
122 | eaten.insert(key.id(), (*state).clone());
123 | }
124 | }
125 | }
126 | }
127 | eaten
128 | }
129 |
130 | fn find_touched_spores(minions: &agent::AgentMap, spores: &agent::AgentMap) -> GeneMap {
131 | let mut touched = HashMap::new();
132 | for spore in spores.values().filter(|&a| a.state.is_active() && !a.state.is_fertilised()) {
133 | for segment in spore.segments.iter() {
134 | if let Some(key) = segment.state.last_touched {
135 | if let Some(ref agent) = minions.get(&key.id()) {
136 | if agent.gender() != spore.gender() {
137 | touched.insert(key.id(), agent.dna().clone());
138 | }
139 | }
140 | }
141 | }
142 | }
143 | touched
144 | }
145 |
146 | fn update_minions(
147 | outbox: &dyn Outbox,
148 | dt: Seconds,
149 | extent: geometry::Rect,
150 | minions: &mut agent::AgentMap,
151 | eaten: &StateMap,
152 | ) -> MinionEndState {
153 | let mut spawns = Vec::new();
154 | let mut corpses = Vec::new();
155 | for agent in minions.values_mut() {
156 | if agent.state.is_active() {
157 | agent.state.reset_growth();
158 | let segment = agent.segment(0).unwrap().clone();
159 | let id = agent.id();
160 | let maturity = segment.state.maturity();
161 | let livery_color = segment.livery.albedo;
162 | let transform = segment.transform().clone();
163 | if maturity < 1. {
164 | // just grow a bit
165 | let r = GROWTH_COST_RATIO;
166 | if agent.state.consume_ratio(1. - r, r) {
167 | let growth = 1. + r;
168 | agent.state.grow_by(growth);
169 | outbox.post(alert::Alert::GrowMinion.into());
170 | outbox.post(particle::Emitter::for_new_spore(transform, livery_color, id).into());
171 | let zero = agent.segment(0).unwrap().transform.position;
172 | for segment in agent.segments.iter_mut() {
173 | let maturity = segment.state.maturity();
174 | segment.state.set_maturity(maturity * growth);
175 | segment.transform.position = zero + (segment.transform.position - zero) * growth;
176 | }
177 | }
178 | } else if agent.state.consume_ratio(SPAWN_COST_THRESHOLD, SPAWN_COST_RATIO) {
179 | spawns.push((agent.last_segment().transform().clone(), agent.dna().clone()));
180 | }
181 |
182 | for segment in agent.segments.iter_mut() {
183 | let p = segment.transform().position;
184 | if p.x < extent.min.x || p.x > extent.max.x || p.y < extent.min.y || p.y > extent.max.y {
185 | agent.state.die();
186 | }
187 | if segment.flags.contains(segment::Flags::MOUTH) {
188 | if let Some(id) = segment.state.last_touched {
189 | if let Some(eaten_state) = eaten.get(&id.id()) {
190 | let energy = eaten_state.energy();
191 | agent.state.absorb(energy);
192 | }
193 | }
194 | }
195 | agent.state.consume(dt * segment.state.charge() * segment.growing_radius());
196 | segment.state.update(dt);
197 | }
198 |
199 | if agent.state.energy() < 1. {
200 | let transforms = agent.segments.iter().map(|segment| segment.transform.clone()).collect::>();
201 | corpses.push((transforms.into_boxed_slice(), agent.dna().clone()));
202 | agent.state.die();
203 | }
204 |
205 | if let Some(segment) = agent.first_segment(segment::Flags::TRACKER) {
206 | agent.state.track_position(segment.transform.position);
207 | }
208 | }
209 | }
210 | MinionEndState(spawns.into_boxed_slice(), corpses.into_boxed_slice())
211 | }
212 |
213 | fn update_resources(dt: Seconds, timer: &SimulationTimer, resources: &mut agent::AgentMap, eaten: &StateMap) {
214 | for resource in resources.values_mut() {
215 | if eaten.get(&resource.id()).is_some()
216 | || resource.state.energy() <= 0.
217 | || resource.state.lifecycle().is_expired(timer)
218 | {
219 | resource.state.die();
220 | } else if resource.state.is_active() {
221 | for segment in resource.segments.iter_mut() {
222 | segment.state.update(dt)
223 | }
224 | }
225 | }
226 | }
227 |
228 | fn crossover(dna: &gen::Dna, foreign_dna: &Option) -> gen::Dna {
229 | match *foreign_dna {
230 | Some(ref foreign) => gen::Genome::copy_from(&foreign).crossover(&mut rand::thread_rng(), dna).dna_cloned(),
231 | None => dna.clone(),
232 | }
233 | }
234 |
235 | fn update_spores(
236 | dt: Seconds,
237 | timer: &SimulationTimer,
238 | spores: &mut agent::AgentMap,
239 | touched: &GeneMap,
240 | ) -> SporeEndState {
241 | let mut spawns = Vec::new();
242 | let mut fertilise_count = 0usize;
243 | for (spore_id, spore) in spores.iter_mut() {
244 | if spore.state.lifecycle().is_expired(timer) {
245 | spore.state.die();
246 | spawns.push((spore.transform().clone(), Self::crossover(spore.dna(), spore.state.foreign_dna())))
247 | } else if spore.state.is_active() {
248 | for segment in spore.segments.iter_mut() {
249 | if let Some(key) = segment.state.last_touched {
250 | if let Some(touched_dna) = touched.get(&key.id()) {
251 | debug!(
252 | "fertilised: {} by {} as {}",
253 | spore_id,
254 | key.id(),
255 | touched_dna.to_base64(base64::STANDARD)
256 | );
257 | fertilise_count += 1;
258 | spore.state.fertilise(touched_dna);
259 | }
260 | }
261 | }
262 | for segment in spore.segments.iter_mut() {
263 | segment.state.update(dt)
264 | }
265 | }
266 | }
267 | SporeEndState(spawns.into_boxed_slice(), fertilise_count)
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/backend/systems/animation.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use backend::obj::Motionable;
3 | use backend::world::agent::AgentType;
4 | use backend::world::AgentState;
5 | use cgmath::InnerSpace;
6 | use core::clock::{seconds, Seconds, SimulationTimer, SpeedFactor, TimerStopwatch};
7 | use num_traits::clamp;
8 | use std::f32::consts;
9 |
10 | #[allow(unused)]
11 | pub struct AnimationSystem {
12 | speed: SpeedFactor,
13 | heartbeat_scale: SpeedFactor,
14 | background_animation_speed: SpeedFactor,
15 | dt: Seconds,
16 | animation_timer: SimulationTimer,
17 | simulation_timer: SimulationTimer,
18 | animation_clock: TimerStopwatch,
19 | simulation_clock: TimerStopwatch,
20 | }
21 |
22 | impl System for AnimationSystem {
23 | fn update(&mut self, _: &dyn AgentState, dt: Seconds) {
24 | self.dt = dt;
25 | self.simulation_timer.tick(dt);
26 | self.animation_timer.tick(dt.times(self.speed));
27 | }
28 |
29 | fn export(&self, world: &mut world::World, _outbox: &dyn Outbox) {
30 | let phase = f64::from(world.phase_mut()[1])
31 | + self.dt * self.speed * self.heartbeat_scale * self.background_animation_speed;
32 | world.phase_mut()[0] = 0.5;
33 | world.phase_mut()[1] = (phase % 1e+3) as f32;
34 | for (_, agent) in &mut world.agents_mut(AgentType::Minion).iter_mut() {
35 | if agent.state.is_active() {
36 | let energy = agent.state.energy();
37 | agent
38 | .state
39 | .heartbeat((self.dt * self.speed * self.heartbeat_scale) as f32 * clamp(energy, 50.0f32, 200.0f32))
40 | }
41 | }
42 | for (_, agent) in &mut world.agents_mut(AgentType::Player).iter_mut() {
43 | if agent.state.is_active() {
44 | let speed = agent.motion().velocity.magnitude();
45 | agent.state.reset_phase();
46 | // TODO: consts here for magic numbers
47 | agent.state.heartbeat(clamp(speed / 100.0, 0.0, consts::PI));
48 | }
49 | }
50 | }
51 | }
52 |
53 | impl Default for AnimationSystem {
54 | fn default() -> Self {
55 | let animation_timer = SimulationTimer::new();
56 | let simulation_timer = SimulationTimer::new();
57 | AnimationSystem {
58 | speed: 1.0,
59 | heartbeat_scale: 1.0 / 60.0,
60 | background_animation_speed: 0.25,
61 | dt: seconds(0.0),
62 | simulation_clock: TimerStopwatch::new(&simulation_timer),
63 | animation_clock: TimerStopwatch::new(&animation_timer),
64 | animation_timer,
65 | simulation_timer,
66 | }
67 | }
68 | }
69 |
70 | impl AnimationSystem {}
71 |
--------------------------------------------------------------------------------
/src/backend/systems/game.rs:
--------------------------------------------------------------------------------
1 | use super::*;
2 | use app::constants::*;
3 | use app::Event;
4 | use backend::messagebus::{Inbox, Message, PubSub, ReceiveDrain, Whiteboard};
5 | use backend::obj::Transformable;
6 | use backend::world;
7 | use backend::world::agent;
8 | use cgmath::InnerSpace;
9 | use core::clock::*;
10 | use core::geometry::Transform;
11 | use core::geometry::*;
12 | use core::math::{exponential_filter, ExponentialFilter};
13 | use rand;
14 | use rand::Rng;
15 | use std::f32::consts;
16 |
17 | #[derive(Default)]
18 | pub struct PlayerState {
19 | trigger_held: bool,
20 | bullet_speed: f32,
21 | bullet_ready: bool,
22 | firing_rate: SecondsValue,
23 | bullet_charge: SecondsValue,
24 | }
25 |
26 | pub struct GameSystem {
27 | timer: SimulationTimer,
28 | dt: Seconds,
29 | playerstate: PlayerState,
30 | feeders: Vec,
31 | inbox: Option,
32 | }
33 |
34 | struct Feeder {
35 | angle: Angle,
36 | position: Position,
37 | hourglass: Hourglass,
38 | light_intensity: ExponentialFilter,
39 | to_spawn: usize,
40 | spawned: usize,
41 | spin: Spin,
42 | emitted_spin: Spin,
43 | emitted_velocity: f32,
44 | }
45 |
46 | impl Feeder //where
47 | {
48 | fn new(position: Position, rate: Seconds, timer: &T) -> Self
49 | where T: Timer {
50 | Feeder {
51 | angle: 0.,
52 | position,
53 | light_intensity: exponential_filter(0., 0., EMITTER_INTENSITY_DECAY),
54 | hourglass: Hourglass::new(rate, timer),
55 | to_spawn: 0,
56 | spawned: 0,
57 | spin: consts::PI * 0.25,
58 | emitted_spin: consts::PI,
59 | emitted_velocity: 5.,
60 | }
61 | }
62 | }
63 |
64 | impl System for GameSystem {
65 | fn attach(&mut self, bus: &mut PubSub) {
66 | self.inbox = Some(bus.subscribe(Box::new(|ev| matches!(*ev, Message::Event(Event::PrimaryFire(_, _))))));
67 | }
68 |
69 | fn clear(&mut self) {
70 | self.playerstate = PlayerState::default();
71 | self.feeders = Vec::new();
72 | }
73 |
74 | fn import(&mut self, world: &world::World) {
75 | let messages = match self.inbox {
76 | Some(ref m) => m.drain(),
77 | None => Vec::new(),
78 | };
79 |
80 | for message in messages {
81 | if let Message::Event(Event::PrimaryFire(bullet_speed, rate)) = message {
82 | self.primary_fire(bullet_speed, rate);
83 | }
84 | }
85 |
86 | let source = world.feeders();
87 | // Add missing emitters - deletion not supported
88 | for s in &source[self.feeders.len()..] {
89 | // let s = &source[i];
90 | self.feeders.push(Feeder::new(s.transform().position, s.rate(), &self.timer));
91 | }
92 | for (i, d) in self.feeders.iter_mut().enumerate() {
93 | d.position = source[i].transform().position;
94 | }
95 | }
96 |
97 | fn update(&mut self, _: &dyn world::AgentState, dt: Seconds) {
98 | let rng = &mut rand::thread_rng();
99 | self.dt = dt;
100 |
101 | self.timer.tick(dt);
102 | for e in &mut self.feeders {
103 | e.spawned = e.to_spawn;
104 | }
105 | for e in &mut self.feeders {
106 | if e.hourglass.is_expired(&self.timer) {
107 | e.hourglass.flip(&self.timer);
108 | e.hourglass.delay(seconds(rng.next_f32() * EMITTER_SPREAD_JITTER));
109 | e.light_intensity.force_to(1.0);
110 | e.to_spawn += 1;
111 | }
112 | e.light_intensity.update(dt.get() as f32);
113 | let tangent = Position::new(-e.position.y, e.position.x).normalize();
114 | e.angle += dt * e.spin;
115 | e.position += tangent * (dt * rng.next_f32());
116 | }
117 | // Byzantine way of processing trigger presses without trigger releases
118 | // I should think of something less convoluted
119 | if !self.playerstate.trigger_held {
120 | self.playerstate.bullet_charge = BULLET_FULL_CHARGE;
121 | }
122 | self.playerstate.bullet_ready =
123 | self.playerstate.trigger_held && self.playerstate.bullet_charge >= BULLET_FULL_CHARGE;
124 | self.playerstate.bullet_charge = if self.playerstate.bullet_ready {
125 | 0.
126 | } else {
127 | BULLET_FULL_CHARGE
128 | .min(self.playerstate.bullet_charge + dt.get() * BULLET_FIRE_RATE * self.playerstate.firing_rate)
129 | };
130 | self.playerstate.trigger_held = false;
131 | }
132 |
133 | fn export(&self, world: &mut world::World, outbox: &dyn Outbox) {
134 | for e in &self.feeders {
135 | for _ in e.spawned..e.to_spawn {
136 | let r = e.angle;
137 |
138 | world.new_resource(
139 | Transform::new(e.position, r),
140 | Motion::new(Velocity::new(r.cos(), r.sin()) * e.emitted_velocity, e.emitted_spin),
141 | );
142 | }
143 | }
144 |
145 | for (src, dest) in self.feeders.iter().zip(world.feeders_mut().iter_mut()) {
146 | dest.transform_to(Transform::new(src.position, src.angle));
147 | dest.set_intensity(src.light_intensity.get());
148 | }
149 |
150 | if self.playerstate.bullet_ready {
151 | world.primary_fire(outbox, self.playerstate.bullet_speed);
152 | }
153 |
154 | world.get_player_agent_id().map(|player_id| {
155 | world.agent_mut(player_id).map(|agent| {
156 | agent.state.absorb(self.playerstate.bullet_charge as f32 * 100.0f32);
157 | if self.playerstate.bullet_ready {
158 | agent.reset_body_charge()
159 | }
160 | })
161 | });
162 |
163 | // if there are no minions, spawn some
164 | if world.agents(agent::AgentType::Minion).is_empty() {
165 | world.init_minions();
166 | }
167 |
168 | // if there are no players, spawn one
169 | if world.agents(agent::AgentType::Player).is_empty() {
170 | world.init_players();
171 | }
172 | for (_, agent) in world.agents_mut(agent::AgentType::Player).iter_mut() {
173 | for segment in agent.segments.iter_mut() {
174 | segment.state.update(self.dt);
175 | }
176 | }
177 | }
178 | }
179 |
180 | impl Default for GameSystem {
181 | fn default() -> Self {
182 | GameSystem {
183 | timer: SimulationTimer::new(),
184 | dt: seconds(0.),
185 | playerstate: PlayerState::default(),
186 | feeders: Vec::new(),
187 | inbox: None,
188 | }
189 | }
190 | }
191 |
192 | impl GameSystem {
193 | fn primary_fire(&mut self, bullet_speed: f32, firing_rate: SecondsValue) {
194 | self.playerstate.bullet_speed = bullet_speed;
195 | self.playerstate.firing_rate = firing_rate;
196 | self.playerstate.trigger_held = true;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/backend/systems/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod ai;
2 | pub mod alife;
3 | pub mod animation;
4 | pub mod game;
5 | pub mod particle;
6 | pub mod physics;
7 |
8 | pub use self::ai::AiSystem;
9 | pub use self::alife::AlifeSystem;
10 | pub use self::animation::AnimationSystem;
11 | pub use self::game::GameSystem;
12 | pub use self::particle::ParticleSystem;
13 | pub use self::physics::PhysicsSystem;
14 |
15 | use backend::messagebus::{Outbox, PubSub};
16 | use backend::world;
17 |
18 | use core::clock::Seconds;
19 |
20 | pub trait System {
21 | fn attach(&mut self, _: &mut PubSub) {}
22 | fn init(&mut self, _: &world::World) {}
23 | fn clear(&mut self) {}
24 | fn register(&mut self, _: &world::agent::Agent) {}
25 | fn unregister(&mut self, _: &world::agent::Agent) {}
26 | fn import(&mut self, _: &world::World) {}
27 | fn update(&mut self, _world_state: &dyn world::AgentState, _dt: Seconds) {}
28 | fn export(&self, _: &mut world::World, _: &dyn Outbox) {}
29 |
30 | fn step(&mut self, world: &world::World, dt: Seconds) {
31 | self.import(world);
32 | self.update(world, dt)
33 | }
34 |
35 | fn apply(&self, world: &mut world::World, outbox: &dyn Outbox) { self.export(world, outbox) }
36 | }
37 |
--------------------------------------------------------------------------------
/src/backend/world/alert.rs:
--------------------------------------------------------------------------------
1 | #[allow(unused)]
2 | #[derive(Copy, Clone, Debug)]
3 | pub enum Alert {
4 | BeginSimulation,
5 | RestartFromCheckpoint,
6 | NewMinion,
7 | NewSpore,
8 | NewResource,
9 | NewBullet(usize),
10 | DieMinion,
11 | DieResource,
12 | Fertilised,
13 | GrowMinion,
14 | }
15 |
--------------------------------------------------------------------------------
/src/backend/world/particle.rs:
--------------------------------------------------------------------------------
1 | use app::constants::*;
2 | use backend::obj;
3 | use core::clock::Seconds;
4 | use core::color::Fade;
5 | use core::color::Rgba;
6 | use core::geometry::{Motion, Position, Transform, Velocity};
7 |
8 | pub enum Fader {
9 | Color = 0,
10 | Scale = 1,
11 | Effect = 2,
12 | Frequency = 3,
13 | Count = 4,
14 | }
15 |
16 | #[allow(unused)]
17 | #[derive(Copy, Clone)]
18 | pub enum EmitterAttachment {
19 | None,
20 | Agent(obj::Id),
21 | Segment(obj::Id, u8),
22 | Vertex(obj::Id, u8, u8),
23 | }
24 |
25 | impl Default for EmitterAttachment {
26 | fn default() -> EmitterAttachment { EmitterAttachment::None }
27 | }
28 |
29 | #[derive(Clone)]
30 | pub enum EmitterStyle {
31 | Explosion { cluster_size: u8, color: Rgba },
32 | Ping { color: Rgba },
33 | Sparkle { cluster_size: u8, color: Rgba },
34 | }
35 |
36 | impl Default for EmitterStyle {
37 | fn default() -> EmitterStyle { EmitterStyle::Explosion { cluster_size: 10u8, color: COLOR_SUNSHINE } }
38 | }
39 |
40 | impl EmitterStyle {
41 | fn color_bang(color: Rgba, boost: f32) -> EmitterStyle {
42 | EmitterStyle::Explosion {
43 | cluster_size: 10u8,
44 | color: [color[0] * boost, color[1] * boost, color[2] * boost, color[3]],
45 | }
46 | }
47 |
48 | fn color_sparkle(color: Rgba, boost: f32) -> EmitterStyle {
49 | EmitterStyle::Sparkle {
50 | cluster_size: 10u8,
51 | color: [color[0] * boost, color[1] * boost, color[2] * boost, color[3]],
52 | }
53 | }
54 |
55 | fn color_ping(color: Rgba, boost: f32) -> EmitterStyle {
56 | EmitterStyle::Ping { color: [color[0] * boost, color[1] * boost, color[2] * boost, color[3]] }
57 | }
58 | }
59 |
60 | #[allow(unused)]
61 | pub struct Particle {
62 | transform: Transform,
63 | direction: Velocity,
64 | tag: isize,
65 | trail: Box<[Position]>,
66 | faders: [f32; 4],
67 | color: (Rgba, Rgba),
68 | effect: (Rgba, Rgba),
69 | age: Seconds,
70 | }
71 |
72 | #[derive(Default, Clone)]
73 | pub struct Emitter {
74 | pub id: Option,
75 | pub transform: Transform,
76 | pub motion: Motion,
77 | pub attached_to: EmitterAttachment,
78 | pub style: EmitterStyle,
79 | }
80 |
81 | impl Emitter {
82 | pub fn for_new_spore(transform: Transform, color: Rgba, id: obj::Id) -> Emitter {
83 | Emitter {
84 | transform,
85 | attached_to: EmitterAttachment::Agent(id),
86 | style: EmitterStyle::color_ping(color, 100.),
87 | ..Emitter::default()
88 | }
89 | }
90 | pub fn for_new_minion(transform: Transform, color: Rgba) -> Emitter {
91 | Emitter { transform, style: EmitterStyle::color_sparkle(color, 100.), ..Emitter::default() }
92 | }
93 | pub fn for_dead_minion(transform: Transform, color: Rgba) -> Emitter {
94 | Emitter { transform, style: EmitterStyle::color_bang(color, 100.), ..Emitter::default() }
95 | }
96 | }
97 |
98 | impl Particle {
99 | #[allow(clippy::too_many_arguments)]
100 | pub fn new(
101 | transform: Transform,
102 | direction: Velocity,
103 | tag: isize,
104 | trail: Box<[Position]>,
105 | faders: [f32; 4],
106 | color: (Rgba, Rgba),
107 | effect: (Rgba, Rgba),
108 | age: Seconds,
109 | ) -> Particle {
110 | Particle { transform, direction, tag, trail, faders, color, effect, age }
111 | }
112 |
113 | pub fn transform(&self) -> Transform { self.transform.clone() }
114 |
115 | pub fn trail(&self) -> &[Position] { &self.trail }
116 |
117 | pub fn scale(&self) -> f32 { self.faders[Fader::Scale as usize] }
118 |
119 | pub fn color(&self) -> Rgba { self.color.0.fade(self.color.1, self.faders[Fader::Color as usize]) }
120 |
121 | pub fn effect(&self) -> Rgba {
122 | let effect = self.effect.0.fade(self.effect.1, self.faders[Fader::Effect as usize]);
123 | let frequency = self.faders[Fader::Frequency as usize];
124 | [effect[0], self.age * effect[1], frequency * effect[2], effect[3]]
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/backend/world/persist.rs:
--------------------------------------------------------------------------------
1 | use backend::world;
2 | use backend::world::agent;
3 | use backend::world::gen;
4 | use core::clock;
5 | use core::geometry;
6 | use num_traits::FromPrimitive;
7 | use serde_json;
8 | use serialize::base64::{self, FromBase64, ToBase64};
9 | use std::fs;
10 | use std::io;
11 | use std::path;
12 |
13 | #[derive(Serialize, Deserialize, Debug)]
14 | pub struct Segment {
15 | charge: f32,
16 | target_charge: f32,
17 | }
18 |
19 | #[derive(Serialize, Deserialize, Debug)]
20 | struct Agent {
21 | id: usize,
22 | x: f32,
23 | y: f32,
24 | angle: f32,
25 | vx: f32,
26 | vy: f32,
27 | spin: f32,
28 | dna: String,
29 | age_seconds: f64,
30 | age_frames: usize,
31 | flags: u32,
32 | maturity: f32,
33 | phase: f32,
34 | energy: f32,
35 | segments: Vec,
36 | }
37 |
38 | #[derive(Serialize, Deserialize, Debug)]
39 | pub struct Swarm {
40 | seq: usize,
41 | agent_type: usize,
42 | agents: Vec,
43 | }
44 |
45 | #[derive(Serialize, Deserialize, Debug)]
46 | pub struct World {
47 | left: f32,
48 | bottom: f32,
49 | right: f32,
50 | top: f32,
51 | swarms: Vec,
52 | regenerations: usize,
53 | minion_gene_pool: Vec,
54 | minion_gene_pool_index: usize,
55 | resource_gene_pool: Vec,
56 | resource_gene_pool_index: usize,
57 | }
58 |
59 | pub struct Serializer;
60 |
61 | impl Serializer {
62 | pub fn save_snapshot(world: &world::World) -> World {
63 | fn serialize_swarm(src: &world::swarm::Swarm) -> Swarm {
64 | Swarm {
65 | seq: src.seq() as usize,
66 | agent_type: src.agent_type() as usize,
67 | agents: src.agents().iter().map(|(_k, v)| serialize_agent(v)).collect(),
68 | }
69 | }
70 |
71 | fn serialize_agent(src: &world::agent::Agent) -> Agent {
72 | let body = &src.segments[0];
73 |
74 | Agent {
75 | id: src.id(),
76 | x: body.transform.position.x,
77 | y: body.transform.position.y,
78 | angle: body.transform.angle,
79 | vx: body.motion.velocity.x,
80 | vy: body.motion.velocity.y,
81 | spin: body.motion.spin,
82 | dna: src.dna().to_base64(base64::STANDARD),
83 | age_seconds: body.state.age_seconds().into(),
84 | age_frames: body.state.age_frames(),
85 | maturity: body.state.maturity(),
86 | flags: src.state.flags().bits(),
87 | phase: src.state.phase(),
88 | energy: src.state.energy(),
89 | segments: src.segments().iter().map(|s| serialize_segment(s)).collect(),
90 | }
91 | }
92 |
93 | fn serialize_segment(src: &world::segment::Segment) -> Segment {
94 | Segment { charge: src.state.charge(), target_charge: src.state.target_charge() }
95 | }
96 |
97 | let swarms = world.swarms().iter().map(|(_k, v)| serialize_swarm(v)).collect();
98 | let minion_gene_pool: Vec<_> =
99 | world.minion_gene_pool.gene_pool_iter().map(|dna| dna.to_base64(base64::STANDARD)).collect();
100 | let resource_gene_pool: Vec<_> =
101 | world.resource_gene_pool.gene_pool_iter().map(|dna| dna.to_base64(base64::STANDARD)).collect();
102 | World {
103 | left: world.extent.min.x,
104 | bottom: world.extent.min.y,
105 | right: world.extent.max.x,
106 | top: world.extent.max.y,
107 | swarms,
108 | regenerations: world.regenerations,
109 | minion_gene_pool,
110 | minion_gene_pool_index: world.minion_gene_pool.gene_pool_index(),
111 | resource_gene_pool,
112 | resource_gene_pool_index: world.resource_gene_pool.gene_pool_index(),
113 | }
114 | }
115 |
116 | pub fn restore_snapshot(src: &World, world: &mut world::World) {
117 | let timer = world.clock.clone();
118 | world.extent.min.x = src.left;
119 | world.extent.min.y = src.bottom;
120 | world.extent.max.x = src.right;
121 | world.extent.max.y = src.top;
122 | world.regenerations = src.regenerations;
123 |
124 | world.minion_gene_pool.populate_from_base64(&src.minion_gene_pool, src.minion_gene_pool_index);
125 | world.resource_gene_pool.populate_from_base64(&src.resource_gene_pool, src.resource_gene_pool_index);
126 |
127 | let mut registered = Vec::new();
128 | for src_swarm in &src.swarms {
129 | if let Some(agent_type) = agent::AgentType::from_usize(src_swarm.agent_type) {
130 | let swarm = world.swarm_mut(&agent_type);
131 | swarm.reset(src_swarm.seq);
132 | for src_agent in &src_swarm.agents {
133 | if let Ok(dna) = src_agent.dna.from_base64() {
134 | let id = swarm.rebuild(
135 | src_agent.id,
136 | &mut gen::Genome::new(dna),
137 | agent::InitialState {
138 | transform: geometry::Transform::from_components(
139 | src_agent.x,
140 | src_agent.y,
141 | src_agent.angle,
142 | ),
143 | motion: geometry::Motion::from_components(src_agent.vx, src_agent.vy, src_agent.spin),
144 | age_seconds: clock::seconds(src_agent.age_seconds),
145 | age_frames: src_agent.age_frames,
146 | maturity: Some(src_agent.maturity),
147 | ..Default::default()
148 | },
149 | &timer,
150 | );
151 | if let Some(agent) = swarm.get_mut(id) {
152 | agent.state.restore(src_agent.flags, src_agent.phase, src_agent.energy);
153 |
154 | for (src_segment, dest_segment) in
155 | src_agent.segments.iter().zip(agent.segments_mut().iter_mut())
156 | {
157 | dest_segment.state.restore(src_segment.charge, src_segment.target_charge);
158 | }
159 | registered.push(id);
160 | }
161 | }
162 | }
163 | }
164 | }
165 | world.registered_player_id = world
166 | .swarms()
167 | .get(&agent::AgentType::Player)
168 | .and_then(|swarm| swarm.agents().iter().next().map(|(k, _s)| *k));
169 | for id in registered {
170 | world.register(id);
171 | }
172 | }
173 |
174 | #[allow(unused)]
175 | pub fn from_string(source: &str, dest: &mut world::World) -> Result<(), serde_json::Error> {
176 | let result: Result = serde_json::from_str(source);
177 | match result {
178 | Ok(src) => {
179 | Self::restore_snapshot(&src, dest);
180 | Ok(())
181 | }
182 | Err(e) => Err(e),
183 | }
184 | }
185 |
186 | pub fn save(file_path: &path::Path, world: &world::World) -> io::Result<()> {
187 | let out_file = fs::File::create(file_path)?;
188 | let s_world = Self::save_snapshot(world);
189 | serde_json::to_writer_pretty(out_file, &s_world)?;
190 | Ok(())
191 | }
192 |
193 | pub fn load(file_path: &path::Path, world: &mut world::World) -> io::Result<()> {
194 | let in_file = fs::File::open(file_path)?;
195 | let src = serde_json::from_reader(in_file)?;
196 | Self::restore_snapshot(&src, world);
197 | Ok(())
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/backend/world/segment.rs:
--------------------------------------------------------------------------------
1 | use backend::obj;
2 | use backend::obj::*;
3 | use backend::world::agent;
4 | use core::clock::Seconds;
5 | use core::geometry::Transform;
6 | use core::geometry::*;
7 | use core::math;
8 | use core::math::ExponentialFilter;
9 | use num::Zero;
10 |
11 | #[derive(Clone)]
12 | pub enum PilotRotation {
13 | None,
14 | Orientation(Position),
15 | LookAt(Position),
16 | Turn(Angle),
17 | FromVelocity,
18 | }
19 |
20 | #[derive(Clone)]
21 | pub enum Intent {
22 | Idle,
23 | Move(Position),
24 | Brake(Position),
25 | RunAway(Position),
26 | PilotTo(Option, PilotRotation),
27 | }
28 |
29 | #[derive(Clone)]
30 | pub struct State {
31 | age_seconds: Seconds,
32 | age_frames: usize,
33 | maturity: f32,
34 | charge: ExponentialFilter,
35 | pub intent: Intent,
36 | pub last_touched: Option,
37 | }
38 |
39 | impl Default for State {
40 | fn default() -> Self {
41 | State {
42 | age_seconds: Seconds::zero(),
43 | age_frames: 0,
44 | maturity: 1.0,
45 | charge: math::exponential_filter(0., 1., 2.),
46 | intent: Intent::Idle,
47 | last_touched: None,
48 | }
49 | }
50 | }
51 |
52 | impl State {
53 | pub fn age_seconds(&self) -> Seconds { self.age_seconds }
54 | pub fn age_frames(&self) -> usize { self.age_frames }
55 |
56 | pub fn charge(&self) -> f32 { self.charge.get() }
57 | pub fn reset_charge(&mut self, current_charge: f32, target_charge: f32) {
58 | self.charge.reset_to(target_charge, current_charge);
59 | }
60 | pub fn set_output_charge(&mut self, charge: f32) { self.charge.force_to(charge); }
61 | pub fn target_charge(&self) -> f32 { self.charge.last_input() }
62 | pub fn set_target_charge(&mut self, target_charge: f32) { self.charge.input(target_charge); }
63 |
64 | //pub fn recharge(&self) -> f32 { self.recharge }
65 |
66 | pub fn maturity(&self) -> f32 { self.maturity }
67 |
68 | pub fn set_maturity(&mut self, maturity: f32) { self.maturity = maturity }
69 |
70 | pub fn restore(&mut self, current_charge: f32, target_charge: f32) {
71 | self.charge.reset_to(target_charge, current_charge);
72 | }
73 |
74 | pub fn update(&mut self, dt: Seconds) {
75 | self.age_seconds += dt;
76 | self.age_frames += 1;
77 | self.charge.update(dt.into());
78 | }
79 |
80 | pub fn with_charge(current_charge: f32, target_charge: f32, charge_decay_time: Seconds) -> Self {
81 | State {
82 | charge: math::exponential_filter(target_charge, current_charge, charge_decay_time.get() as f32),
83 | ..Self::default()
84 | }
85 | }
86 | }
87 |
88 | #[derive(Copy, Clone)]
89 | pub struct Attachment {
90 | pub index: SegmentIndex,
91 | pub attachment_point: AttachmentIndex,
92 | }
93 |
94 | bitflags! {
95 | pub struct Flags: u32 {
96 | const SENSOR = 0x00001u32;
97 | const ACTUATOR = 0x00002u32;
98 | const JOINT = 0x00004u32;
99 | const MOUTH = 0x00008u32;
100 | const HEAD = 0x00010u32;
101 | const LEG = 0x00020u32;
102 | const ARM = 0x00040u32;
103 | const CORE = 0x00100u32;
104 | const STORAGE = 0x00200u32;
105 | const TAIL = 0x00400u32;
106 | const TRACKER = 0x00800u32;
107 | const LEFT = 0x01000u32;
108 | const RIGHT = 0x02000u32;
109 | const MIDDLE = 0x04000u32;
110 | const THRUSTER = 0x10000u32;
111 | const RUDDER = 0x20000u32;
112 | const BRAKE = 0x40000u32;
113 | }
114 | }
115 |
116 | #[derive(Clone)]
117 | pub struct Segment {
118 | pub transform: Transform,
119 | pub rest_angle: Angle,
120 | pub motion: Motion,
121 | pub index: SegmentIndex,
122 | pub mesh: Mesh,
123 | pub material: Material,
124 | pub livery: Livery,
125 | pub attached_to: Option,
126 | pub state: State,
127 | pub flags: Flags,
128 | }
129 |
130 | impl Segment {
131 | pub fn new_attachment(&self, attachment_point: AttachmentIndex) -> Option {
132 | let max = self.mesh.vertices.len() as AttachmentIndex;
133 | Some(Attachment {
134 | index: self.index,
135 | attachment_point: if attachment_point < max { attachment_point } else { max - 1 },
136 | })
137 | }
138 |
139 | pub fn growing_radius(&self) -> f32 { self.state.maturity * self.mesh.shape.radius() }
140 |
141 | pub fn growing_scaled_vertex(&self, index: usize) -> Position {
142 | self.state.maturity * self.mesh.scaled_vertex(index)
143 | }
144 | }
145 |
146 | impl obj::Drawable for Segment {
147 | fn color(&self) -> Rgba {
148 | let rgba = self.livery.albedo;
149 | let c = 5. * ((self.state.charge.get() * 0.99) + 0.01);
150 | [rgba[0] * c, rgba[1] * c, rgba[2] * c, rgba[3] * self.material.density]
151 | }
152 | }
153 |
154 | impl Transformable for Segment {
155 | fn transform(&self) -> &Transform { &self.transform }
156 |
157 | fn transform_to(&mut self, t: Transform) { self.transform = t; }
158 | }
159 |
160 | impl Motionable for Segment {
161 | fn motion(&self) -> &Motion { &self.motion }
162 |
163 | fn motion_to(&mut self, m: Motion) { self.motion = m; }
164 | }
165 |
166 | impl obj::Geometry for Segment {
167 | fn mesh(&self) -> &Mesh { &self.mesh }
168 | }
169 |
170 | impl obj::Solid for Segment {
171 | fn material(&self) -> &Material { &self.material }
172 |
173 | fn livery(&self) -> &Livery { &self.livery }
174 | }
175 |
--------------------------------------------------------------------------------
/src/backend/world/swarm.rs:
--------------------------------------------------------------------------------
1 | use backend::obj::*;
2 | use backend::world::agent;
3 | use backend::world::agent::Agent;
4 | use backend::world::agent::AgentType;
5 | use backend::world::agent::TypedAgent;
6 | use backend::world::gen::*;
7 | use backend::world::phen;
8 | use core::clock::Timer;
9 | use std::collections::HashMap;
10 | use std::collections::HashSet;
11 |
12 | pub struct Swarm {
13 | seq: Id,
14 | agent_type: AgentType,
15 | phenotype: Box,
16 | agents: agent::AgentMap,
17 | }
18 |
19 | impl Swarm {
20 | pub fn new(agent_type: AgentType, phenotype: Box) -> Swarm {
21 | Swarm { seq: 0, agent_type, phenotype, agents: HashMap::new() }
22 | }
23 |
24 | #[allow(dead_code)]
25 | pub fn type_of(&self) -> AgentType { self.agent_type }
26 |
27 | pub fn get(&self, id: Id) -> Option<&Agent> { self.agents.get(&id) }
28 |
29 | pub fn get_mut(&mut self, id: Id) -> Option<&mut agent::Agent> { self.agents.get_mut(&id) }
30 |
31 | pub fn agent_type(&self) -> AgentType { self.agent_type }
32 |
33 | pub fn seq(&self) -> Id { self.seq }
34 |
35 | pub fn clear(&mut self) { self.agents.clear() }
36 |
37 | pub fn reset(&mut self, seq: Id) {
38 | self.agents.clear();
39 | self.seq = seq;
40 | }
41 |
42 | pub fn next_id(&mut self) -> Id {
43 | self.seq += 1;
44 | self.seq << 8 | (self.agent_type as usize)
45 | }
46 |
47 | pub fn free_resources(&mut self, freed: &mut Vec) {
48 | let mut dead = HashSet::new();
49 | for id in self.agents.iter().filter(|&(_, agent)| !agent.state.is_alive()).map(|(&id, _)| id) {
50 | dead.insert(id);
51 | }
52 | for id in &dead {
53 | if let Some(agent) = self.agents.remove(&id) {
54 | freed.push(agent);
55 | }
56 | }
57 | }
58 |
59 | fn insert(&mut self, agent: Agent) -> Id {
60 | let id = agent.id();
61 | self.agents.insert(id, agent);
62 | id
63 | }
64 |
65 | #[allow(dead_code)]
66 | pub fn is_empty(&self) -> bool { self.agents.is_empty() }
67 |
68 | pub fn spawn(&mut self, genome: &mut Genome, initial_state: agent::InitialState, timer: &dyn Timer) -> Id {
69 | let id = self.next_id();
70 | match id.type_of() {
71 | AgentType::Minion | AgentType::Spore => debug!("spawn: {} as {}", genome, id.type_of()),
72 | _ => {}
73 | }
74 | self.rebuild(id, genome, initial_state, timer)
75 | // let entity = self.phenotype.develop(genome, id, initial_state,
76 | // timer); self.insert(entity)
77 | }
78 |
79 | pub fn rebuild(
80 | &mut self,
81 | id: Id,
82 | genome: &mut Genome,
83 | initial_state: agent::InitialState,
84 | timer: &dyn Timer,
85 | ) -> Id {
86 | let entity = self.phenotype.develop(genome, id, initial_state, timer);
87 | self.insert(entity)
88 | }
89 |
90 | pub fn agents(&self) -> &HashMap { &self.agents }
91 |
92 | pub fn agents_mut(&mut self) -> &mut HashMap { &mut self.agents }
93 | }
94 |
95 | pub type SwarmMap = HashMap;
96 |
--------------------------------------------------------------------------------
/src/core/clock.rs:
--------------------------------------------------------------------------------
1 | use num;
2 | use num::NumCast;
3 | use num::Zero;
4 | use std::convert;
5 | use std::fmt;
6 | use std::fmt::Display;
7 | use std::ops::*;
8 | use std::time;
9 |
10 | pub type SecondsValue = f64;
11 | pub type SpeedFactor = SecondsValue;
12 |
13 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
14 | pub struct Seconds(SecondsValue);
15 |
16 | const ZERO_SECONDS: Seconds = Seconds(0.0);
17 |
18 | impl Display for Seconds {
19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20 | if self.0 > 0.5 {
21 | write!(f, "{:.3}s", self.0)
22 | } else {
23 | write!(f, "{:.1}ms", self.0 * 1000.0)
24 | }
25 | }
26 | }
27 |
28 | pub fn seconds(value: T) -> Seconds
29 | where T: Into {
30 | Seconds::new(value.into())
31 | }
32 |
33 | impl Seconds {
34 | pub fn new(value: SecondsValue) -> Seconds { Seconds(value) }
35 | pub fn get(self) -> SecondsValue { self.0 }
36 | pub fn times(self, other: F) -> Seconds
37 | where F: num::Float {
38 | seconds(::from(other).unwrap() * self.0)
39 | }
40 | }
41 |
42 | impl Zero for Seconds {
43 | #[inline]
44 | fn zero() -> Seconds { ZERO_SECONDS }
45 | fn is_zero(&self) -> bool { *self == ZERO_SECONDS }
46 | }
47 |
48 | impl Default for Seconds {
49 | fn default() -> Seconds { Seconds::zero() }
50 | }
51 |
52 | impl Add for Seconds {
53 | type Output = Seconds;
54 | fn add(self, other: Seconds) -> Seconds { Seconds(self.0 + other.0) }
55 | }
56 |
57 | impl AddAssign for Seconds {
58 | fn add_assign(&mut self, other: Seconds) { self.0 += other.0; }
59 | }
60 |
61 | impl SubAssign for Seconds {
62 | fn sub_assign(&mut self, other: Seconds) { self.0 -= other.0; }
63 | }
64 |
65 | impl Mul for Seconds
66 | where F: num::Float
67 | {
68 | type Output = F;
69 | fn mul(self, other: F) -> F { other * ::from(self.0).unwrap() }
70 | }
71 |
72 | impl Div for Seconds {
73 | type Output = Seconds;
74 | fn div(self, other: usize) -> Seconds { Seconds(self.0 / other as SecondsValue) }
75 | }
76 |
77 | impl Sub for Seconds {
78 | type Output = Seconds;
79 | fn sub(self, other: Seconds) -> Seconds { Seconds(self.0 - other.0) }
80 | }
81 |
82 | impl Into for Seconds {
83 | fn into(self) -> SecondsValue { self.0 }
84 | }
85 |
86 | impl Into for Seconds {
87 | fn into(self) -> f32 { self.0 as f32 }
88 | }
89 |
90 | /// Timer
91 | pub trait Timer {
92 | fn seconds(&self) -> Seconds;
93 | }
94 |
95 | /// SystemTimer
96 | #[derive(Clone)]
97 | pub struct SystemTimer {
98 | t0: time::SystemTime,
99 | }
100 |
101 | impl SystemTimer {
102 | pub fn new() -> Self { SystemTimer { t0: time::SystemTime::now() } }
103 | }
104 |
105 | impl Timer for SystemTimer {
106 | fn seconds(&self) -> Seconds {
107 | match self.t0.elapsed() {
108 | Ok(dt) => Seconds(
109 | (dt.as_secs() as SecondsValue) + >::from(dt.subsec_nanos()) * 1e-9,
110 | ),
111 | Err(_) => Seconds::zero(),
112 | }
113 | }
114 | }
115 |
116 | /// SimulationTimer
117 | #[derive(Clone)]
118 | pub struct SimulationTimer {
119 | seconds: Seconds,
120 | }
121 |
122 | impl From for SimulationTimer {
123 | fn from(seconds: Seconds) -> Self { SimulationTimer { seconds } }
124 | }
125 |
126 | impl Timer for SimulationTimer {
127 | fn seconds(&self) -> Seconds { self.seconds }
128 | }
129 |
130 | impl SimulationTimer {
131 | pub fn new() -> Self { SimulationTimer { seconds: Seconds::zero() } }
132 | pub fn tick(&mut self, dt: Seconds) { self.seconds += dt }
133 | //pub fn from(source: T) -> Self where T: Timer { SimulationTimer {
134 | // seconds: source.seconds() } }
135 | }
136 |
137 | /// Stopwatch
138 | pub trait Stopwatch {
139 | fn reset(&mut self, timer: &T)
140 | where T: Timer;
141 |
142 | fn elapsed(&self, timer: &T) -> Seconds
143 | where T: Timer;
144 |
145 | fn restart(&mut self, timer: &T) -> Seconds
146 | where T: Timer {
147 | let elapsed = self.elapsed(timer);
148 | self.reset(timer);
149 | elapsed
150 | }
151 | }
152 |
153 | #[derive(Clone)]
154 | pub struct TimerStopwatch {
155 | t0: Seconds,
156 | }
157 |
158 | impl TimerStopwatch {
159 | pub fn new(timer: &dyn Timer) -> Self {
160 | let t0 = timer.seconds();
161 | TimerStopwatch { t0 }
162 | }
163 | }
164 |
165 | impl Stopwatch for TimerStopwatch {
166 | fn reset(&mut self, timer: &T)
167 | where T: Timer {
168 | self.t0 = timer.seconds();
169 | }
170 |
171 | fn elapsed(&self, timer: &T) -> Seconds
172 | where T: Timer {
173 | timer.seconds() - self.t0
174 | }
175 | }
176 |
177 | /// Hourglass
178 | #[derive(Clone)]
179 | pub struct Hourglass {
180 | stopwatch: TimerStopwatch,
181 | capacity: Seconds,
182 | timeout: Seconds,
183 | }
184 |
185 | impl fmt::Debug for Hourglass {
186 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.timeout, self.capacity) }
187 | }
188 |
189 | #[allow(unused)]
190 | impl Hourglass {
191 | pub fn new(seconds: Seconds, timer: &dyn Timer) -> Self {
192 | Hourglass { stopwatch: TimerStopwatch::new(timer), capacity: seconds, timeout: seconds }
193 | }
194 |
195 | pub fn renew(&mut self, timer: &T)
196 | where T: Timer {
197 | self.timeout = self.capacity;
198 | self.stopwatch.reset(timer)
199 | }
200 |
201 | pub fn flip(&mut self, timer: &T) -> Seconds
202 | where T: Timer {
203 | let left = self.left(timer);
204 | self.timeout = self.capacity - left;
205 | self.stopwatch.reset(timer);
206 | left
207 | }
208 |
209 | pub fn delay(&mut self, delay_seconds: Seconds) { self.timeout += delay_seconds; }
210 |
211 | #[allow(unused)]
212 | pub fn elapsed(&self, timer: &T) -> Seconds
213 | where T: Timer {
214 | self.stopwatch.elapsed(timer)
215 | }
216 |
217 | pub fn left(&self, timer: &T) -> Seconds
218 | where T: Timer {
219 | let dt = self.timeout - self.stopwatch.elapsed(timer);
220 | Seconds(SecondsValue::max(0., dt.into()))
221 | }
222 |
223 | pub fn is_expired(&self, timer: &T) -> bool
224 | where T: Timer {
225 | self.left(timer).get() <= Seconds::zero().0
226 | }
227 |
228 | pub fn flip_if_expired(&mut self, timer: &T) -> bool
229 | where T: Timer {
230 | let expired = self.is_expired(timer);
231 | if expired {
232 | self.flip(timer);
233 | };
234 | expired
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/core/color.rs:
--------------------------------------------------------------------------------
1 | use num;
2 |
3 | pub type Rgb = [T; 3];
4 | pub type Rgba = [T; 4];
5 |
6 | pub trait ToRgb {
7 | fn to_rgb(&self) -> Rgb;
8 |
9 | fn to_rgba(&self) -> Rgba {
10 | let rgb = self.to_rgb();
11 | [rgb[0], rgb[1], rgb[2], T::one()]
12 | }
13 | }
14 |
15 | pub trait FromRgb: Sized {
16 | fn from_rgb(c: &Rgb) -> Self;
17 |
18 | fn from_rgba(c: &Rgba) -> Self { Self::from_rgb(&[c[0], c[1], c[2]]) }
19 | }
20 |
21 | pub trait Fade
22 | where T: num::Float {
23 | fn fade(&self, other: F, alpha: T) -> F;
24 | }
25 |
26 | impl Fade, T> for Rgba
27 | where T: num::Float
28 | {
29 | fn fade(&self, other: Rgba, alpha: T) -> Rgba {
30 | let alpha1 = T::one() - alpha;
31 | [
32 | self[0] * alpha1 + other[0] * alpha,
33 | self[1] * alpha1 + other[1] * alpha,
34 | self[2] * alpha1 + other[2] * alpha,
35 | self[3] * alpha1 + other[3] * alpha,
36 | ]
37 | }
38 | }
39 |
40 | #[derive(Debug, Copy, Clone)]
41 | pub struct Hsl {
42 | h: T,
43 | s: T,
44 | l: T,
45 | }
46 |
47 | #[derive(Debug, Copy, Clone)]
48 | pub struct YPbPr {
49 | // luma/chroma. y in [0,1], cb, cr in [-0.5, 0.5]
50 | y: T,
51 | pb: T,
52 | pr: T,
53 | }
54 |
55 | impl FromRgb for YPbPr {
56 | fn from_rgb(c: &Rgb) -> Self {
57 | let (r, g, b) = (c[0], c[1], c[2]);
58 | YPbPr {
59 | y: 0.299 * r + 0.587 * g + 0.114 * b,
60 | pb: -0.168_736 * r - 0.331_264 * g + 0.500 * b,
61 | pr: 0.500 * r - 0.418_688 * g - 0.081_312 * b,
62 | }
63 | }
64 | }
65 |
66 | impl YPbPr
67 | where T: num::Float
68 | {
69 | pub fn new(y: T, pb: T, pr: T) -> Self { YPbPr { y, pb, pr } }
70 | }
71 |
72 | impl ToRgb for YPbPr {
73 | fn to_rgb(&self) -> Rgb {
74 | let r = self.y + 1.402 * self.pr;
75 | let g = self.y - 0.344_136 * self.pb - 0.714_136 * self.pr;
76 | let b = self.y + 1.772 * self.pb;
77 | [r.max(0.).min(1.), g.max(0.).min(1.), b.max(0.).min(1.)]
78 | }
79 | }
80 |
81 | impl Hsl
82 | where T: num::Float
83 | {
84 | pub fn new(h: T, s: T, l: T) -> Self { Hsl { h, s, l } }
85 | }
86 |
87 | impl FromRgb for Hsl {
88 | /// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
89 | ///
90 | /// Converts an RGB color value to HSL. Conversion formula
91 | /// adapted from http://en.wikipedia.org/wiki/HSL_color_space.
92 | /// Assumes r, g, and b are contained in the set [0, 255] and
93 | /// returns h, s, and l in the set [0, 1].
94 | ///
95 | /// @param Number r The red color value
96 | /// @param Number g The green color value
97 | /// @param Number b The blue color value
98 | /// @return Array The HSL representation
99 | #[allow(clippy::float_cmp)]
100 | #[allow(clippy::many_single_char_names)]
101 | fn from_rgb(c: &Rgb) -> Self {
102 | let (r, g, b) = (c[0], c[1], c[2]);
103 | let max = f32::max(r, f32::max(g, b));
104 | let min = f32::min(r, f32::min(g, b));
105 | let m = (max + min) / 2.;
106 |
107 | if max == min {
108 | Hsl { h: 0., s: 0., l: m }
109 | } else {
110 | let d = max - min;
111 | Hsl {
112 | h: if max == r {
113 | (g - b) / d + if g < b { 6. } else { 0. }
114 | } else if max == g {
115 | (b - r) / d + 2.
116 | } else {
117 | (r - g) / d + 4.
118 | } / 6.,
119 | s: if b > 0.5 { d / (2. - max - min) } else { d / (max + min) },
120 | l: m,
121 | }
122 | }
123 | }
124 | }
125 |
126 | impl ToRgb for Hsl {
127 | /// Converts an HSL color value to RGB. Conversion formula
128 | /// adapted from http://en.wikipedia.org/wiki/HSL_color_space.
129 | /// Assumes h, s, and l are contained in the set [0, 1] and
130 | /// returns r, g, and b in the set [0, 1].
131 | ///
132 | /// @param Number h The hue
133 | /// @param Number s The saturation
134 | /// @param Number l The lightness
135 | /// @return Array The RGB representation
136 | fn to_rgb(&self) -> Rgb {
137 | fn hue2rgb(p: f32, q: f32, t0: f32) -> f32 {
138 | let t = if t0 < 0. {
139 | t0 + 1.
140 | } else if t0 > 1. {
141 | t0 - 1.
142 | } else {
143 | t0
144 | };
145 | if t < 1. / 6. {
146 | p + (q - p) * 6. * t
147 | } else if t < 1. / 2. {
148 | q
149 | } else if t < 2. / 3. {
150 | p + (q - p) * (2. / 3. - t) * 6.
151 | } else {
152 | p
153 | }
154 | }
155 |
156 | match *self {
157 | Hsl { h, l, .. } if h == 0. => [l, l, l],
158 | Hsl { h, s, l } => {
159 | let q = if l < 0.5 { l * (1. + s) } else { l + s - l * s };
160 | let p = 2. * l - q;
161 | let red = hue2rgb(p, q, h + 1. / 3.);
162 | let green = hue2rgb(p, q, h);
163 | let blue = hue2rgb(p, q, h - 1. / 3.);
164 |
165 | [red, green, blue]
166 | }
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/core/geometry.rs:
--------------------------------------------------------------------------------
1 | use cgmath;
2 | use cgmath::Vector2;
3 | use cgmath::*;
4 | use core::util::Initial;
5 |
6 | pub type Position = Vector2;
7 | // pub type Translation = Vector2;
8 | pub type Velocity = Vector2;
9 | pub type Acceleration = Vector2;
10 | pub type Angle = f32;
11 | // pub type Rotation = f32;
12 | pub type Spin = f32;
13 |
14 | pub type M44 = cgmath::Matrix4;
15 |
16 | #[derive(Clone, Default)]
17 | pub struct Size {
18 | pub width: f32,
19 | pub height: f32,
20 | }
21 |
22 | #[derive(Clone)]
23 | pub struct Transform {
24 | pub position: Position,
25 | pub angle: Angle,
26 | }
27 |
28 | #[derive(Clone)]
29 | pub struct Motion {
30 | pub velocity: Velocity,
31 | pub spin: Spin,
32 | }
33 |
34 | #[derive(Copy, Clone)]
35 | pub struct Rect {
36 | pub min: Position,
37 | pub max: Position,
38 | }
39 |
40 | impl Rect {
41 | pub fn new(left: f32, bottom: f32, right: f32, top: f32) -> Self {
42 | Rect { min: Position::new(left, bottom), max: Position::new(right, top) }
43 | }
44 |
45 | pub fn bottom_left(&self) -> Position { self.min }
46 | pub fn top_right(&self) -> Position { self.max }
47 |
48 | pub fn bottom_right(&self) -> Position { Position::new(self.max.x, self.min.y) }
49 |
50 | pub fn top_left(&self) -> Position { Position::new(self.min.x, self.max.y) }
51 | }
52 |
53 | impl Default for Rect {
54 | fn default() -> Self { Rect::new(0., 0., 0., 0.) }
55 | }
56 |
57 | impl Initial for Position {
58 | fn initial() -> Self { Position::zero() }
59 | }
60 |
61 | impl Default for Transform {
62 | fn default() -> Transform { Transform { position: Position::zero(), angle: 0. } }
63 | }
64 |
65 | impl Default for Motion {
66 | fn default() -> Motion { Motion { velocity: Velocity::zero(), spin: 0. } }
67 | }
68 |
69 | #[allow(unused)]
70 | impl Transform {
71 | pub fn new(position: Position, angle: f32) -> Self { Transform { position, angle } }
72 |
73 | pub fn from_components(x: f32, y: f32, angle: f32) -> Self { Self::new(Position::new(x, y), angle) }
74 |
75 | pub fn from_position(position: Position) -> Self { Transform { position, ..Transform::default() } }
76 |
77 | pub fn from_angle(angle: Angle) -> Self { Transform { angle, ..Transform::default() } }
78 |
79 | pub fn apply_rotation(&self, position: Position) -> Position {
80 | if self.angle != 0. {
81 | let ca = self.angle.cos();
82 | let sa = self.angle.sin();
83 |
84 | Position::new(ca * position.x - sa * position.y, sa * position.x + ca * position.y)
85 | } else {
86 | position
87 | }
88 | }
89 |
90 | pub fn apply_translation(&self, position: Position) -> Position { self.position + position }
91 |
92 | pub fn apply(&self, position: Position) -> Position {
93 | if self.angle != 0. {
94 | let ca = self.angle.cos();
95 | let sa = self.angle.sin();
96 |
97 | self.position + Position::new(ca * position.x - sa * position.y, sa * position.x + ca * position.y)
98 | } else {
99 | self.position + position
100 | }
101 | }
102 |
103 | fn to_matrix(&self) -> Matrix3 {
104 | let ca = self.angle.cos();
105 | let sa = self.angle.sin();
106 | let tx = self.position.x;
107 | let ty = self.position.y;
108 | Matrix3::new(ca, sa, 0., -sa, ca, 0., tx, ty, 1.)
109 | }
110 | }
111 |
112 | impl Motion {
113 | pub fn new(velocity: Position, spin: f32) -> Self { Motion { velocity, spin } }
114 |
115 | pub fn from_components(vx: f32, vy: f32, spin: f32) -> Self { Self::new(Velocity::new(vx, vy), spin) }
116 | }
117 |
118 | pub fn origin() -> Position { Position::new(0., 0.) }
119 |
120 | #[derive(Clone, PartialEq)]
121 | enum VertexType {
122 | Plus,
123 | Minus,
124 | Flat,
125 | }
126 |
127 | pub struct PolygonType {
128 | count: [usize; 3],
129 | }
130 |
131 | impl PolygonType {
132 | fn classify_vertex(v0: Position, v1: Position, v2: Position) -> VertexType {
133 | let x: f32 = (v1 - v0).perp_dot(v2 - v0);
134 | if relative_eq!(x, 0.0f32) {
135 | VertexType::Flat
136 | } else if x > 0. {
137 | VertexType::Plus
138 | } else {
139 | VertexType::Minus
140 | }
141 | }
142 |
143 | pub fn classify(v: &[Position]) -> Self {
144 | let mut count = [0usize; 3];
145 |
146 | let n = v.len();
147 | for i in 0..n {
148 | let vertex_type = Self::classify_vertex(v[(i + n - 1) % n], v[i], v[(i + 1) % n]);
149 | count[vertex_type as usize] += 1;
150 | }
151 |
152 | PolygonType { count }
153 | }
154 |
155 | pub fn is_convex(&self) -> bool {
156 | self.count[VertexType::Plus as usize] == 0 || self.count[VertexType::Minus as usize] == 0
157 | }
158 |
159 | #[allow(dead_code)]
160 | pub fn is_concave(&self) -> bool {
161 | self.count[VertexType::Plus as usize] > 0 && self.count[VertexType::Minus as usize] > 0
162 | }
163 |
164 | #[allow(dead_code)]
165 | pub fn has_flat_vertices(&self) -> bool { self.count[VertexType::Flat as usize] > 0 }
166 | }
167 |
--------------------------------------------------------------------------------
/src/core/math.rs:
--------------------------------------------------------------------------------
1 | use cgmath;
2 | use cgmath::InnerSpace;
3 | use num;
4 | use num::NumCast;
5 | use num::Zero;
6 | use num_traits::FloatConst;
7 | use std::marker::PhantomData;
8 | use std::ops::*;
9 |
10 | pub trait Smooth {
11 | fn smooth(&mut self, value: S) -> S { value }
12 | }
13 |
14 | pub trait IntervalSmooth
15 | where S: Add + Mul {
16 | fn smooth(&mut self, value: S, dt: T) -> S { value * dt }
17 | fn reset(&mut self, _value: S) {}
18 | fn last(&self) -> S;
19 | }
20 |
21 | #[allow(unused)]
22 | pub fn normalize_rad(angle: S) -> S
23 | where S: num::Float + FloatConst {
24 | let pi: S = S::PI();
25 | (angle + ::from(3.).unwrap() * pi) % (::from(2.).unwrap() * pi) - pi
26 | }
27 |
28 | pub struct MovingAverage {
29 | ptr: usize,
30 | count: usize,
31 | acc: S,
32 | last: S,
33 | values: Vec,
34 | }
35 |
36 | #[derive(Copy, Clone)]
37 | pub struct Exponential {
38 | tau: T,
39 | last: S,
40 | }
41 |
42 | impl MovingAverage {
43 | pub fn new(window_size: usize) -> Self {
44 | MovingAverage { ptr: 0, count: 0, last: S::zero(), acc: S::zero(), values: vec![S::zero(); window_size] }
45 | }
46 | }
47 |
48 | impl Smooth for MovingAverage
49 | where S: Zero + Sub + Copy + AddAssign + SubAssign + Div
50 | {
51 | fn smooth(&mut self, value: S) -> S {
52 | let len = self.values.len();
53 | if self.count < len {
54 | self.count += 1;
55 | } else {
56 | self.acc -= self.values[self.ptr];
57 | }
58 | self.acc += value;
59 | self.values[self.ptr] = value;
60 | self.ptr = ((self.ptr + 1) % len) as usize;
61 | self.last = self.acc / self.count;
62 | self.last
63 | }
64 | }
65 |
66 | pub trait Mix
67 | where V: num::Float {
68 | fn mix(self, a: V, b: V) -> V;
69 | }
70 |
71 | impl Mix for T
72 | where
73 | T: num::Float,
74 | V: num::Float + Mul,
75 | {
76 | fn mix(self, a: V, b: V) -> V {
77 | let alpha = T::min(T::one(), T::max(T::zero(), self));
78 | a * alpha + b * (T::one() - alpha)
79 | }
80 | }
81 |
82 | impl Exponential
83 | where
84 | S: Add + Mul + Copy,
85 | T: cgmath::BaseFloat,
86 | {
87 | pub fn new(value: S, tau: T) -> Self { Exponential { last: value, tau } }
88 | }
89 |
90 | impl IntervalSmooth for Exponential
91 | where
92 | S: Add + Mul + Copy,
93 | T: cgmath::BaseFloat,
94 | {
95 | fn smooth(&mut self, value: S, dt: T) -> S {
96 | let alpha1 = T::exp(-dt / self.tau);
97 | self.last = value * (T::one() - alpha1) + self.last * alpha1;
98 | self.last
99 | }
100 |
101 | fn reset(&mut self, value: S) { self.last = value; }
102 | fn last(&self) -> S { self.last }
103 | }
104 |
105 | #[derive(Clone)]
106 | pub struct LPF
107 | where
108 | S: Add + Mul + Copy,
109 | T: cgmath::BaseFloat,
110 | M: IntervalSmooth, {
111 | input: S,
112 | smooth: M,
113 | _interval: PhantomData,
114 | }
115 |
116 | impl LPF
117 | where
118 | S: Add + Mul + Sub + Copy,
119 | T: cgmath::BaseFloat,
120 | M: IntervalSmooth,
121 | {
122 | pub fn new(input: S, smooth: M) -> Self { LPF { input, smooth, _interval: PhantomData } }
123 |
124 | pub fn input(&mut self, target: S) { self.input = target; }
125 | pub fn last_input(&self) -> S { self.input }
126 | pub fn force_to(&mut self, output: S) { self.smooth.reset(output) }
127 | pub fn reset_to(&mut self, input: S, output: S) {
128 | self.input = input;
129 | self.smooth.reset(output);
130 | }
131 | pub fn update(&mut self, dt: T) -> S { self.smooth.smooth(self.input, dt) }
132 | pub fn get(&self) -> S { self.smooth.last() }
133 | }
134 |
135 | pub type ExponentialFilter = LPF>;
136 |
137 | pub fn exponential_filter(initial_input: T, initial_output: T, decay_time: T) -> ExponentialFilter
138 | where T: cgmath::BaseFloat {
139 | LPF::new(initial_input, Exponential::new(initial_output, decay_time))
140 | }
141 |
142 | pub enum Direction {
143 | Up,
144 | Down,
145 | Left,
146 | Right,
147 | }
148 |
149 | pub trait Directional {
150 | fn push(&mut self, d: Direction, weight: T);
151 | fn position(&self) -> cgmath::Vector2;
152 | fn unit(d: Direction) -> cgmath::Vector2 {
153 | match d {
154 | Direction::Up => cgmath::Vector2::unit_y(),
155 | Direction::Down => -cgmath::Vector2::unit_y(),
156 | Direction::Right => cgmath::Vector2::unit_x(),
157 | Direction::Left => -cgmath::Vector2::unit_x(),
158 | }
159 | }
160 | }
161 |
162 | pub trait Relative {
163 | fn zero(&mut self);
164 | fn set_relative(&mut self, p: cgmath::Vector2);
165 | }
166 |
167 | #[derive(Clone)]
168 | pub struct Inertial {
169 | impulse: T,
170 | inertia: T,
171 | limit: T,
172 | target: Option>,
173 | zero: cgmath::Vector2,
174 | position: cgmath::Vector2,
175 | velocity: cgmath::Vector2,
176 | }
177 |
178 | impl Default for Inertial
179 | where T: cgmath::BaseFloat + cgmath::Zero + cgmath::One
180 | {
181 | fn default() -> Self {
182 | Inertial {
183 | impulse: T::one(),
184 | inertia: T::one(),
185 | limit: T::one(),
186 | target: None,
187 | zero: cgmath::Zero::zero(),
188 | position: cgmath::Zero::zero(),
189 | velocity: cgmath::Zero::zero(),
190 | }
191 | }
192 | }
193 |
194 | impl Directional for Inertial
195 | where T: cgmath::BaseFloat
196 | {
197 | fn push(&mut self, d: Direction, weight: T) {
198 | let v = Self::unit(d) * weight;
199 | self.velocity += v * self.impulse;
200 | if self.velocity.magnitude() > self.limit {
201 | self.velocity.normalize_to(self.limit);
202 | }
203 | }
204 | fn position(&self) -> cgmath::Vector2 { self.position }
205 | }
206 |
207 | impl Relative for Inertial
208 | where T: cgmath::BaseFloat
209 | {
210 | fn zero(&mut self) { self.zero = self.position; }
211 | fn set_relative(&mut self, p: cgmath::Vector2