Skip to content

Commit f111584

Browse files
committed
examples: share n-body rendering support
1 parent 6e86a61 commit f111584

3 files changed

Lines changed: 62 additions & 213 deletions

File tree

examples/solarsystemnobody.rs

Lines changed: 20 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod support;
2+
13
use colored::Color;
24
use crossterm::{
35
cursor,
@@ -12,92 +14,12 @@ use std::collections::VecDeque;
1214
use std::io::{self, Write};
1315
use std::thread;
1416
use std::time::{Duration, Instant};
17+
use support::solar::{line_z, plot_z, PickingZBuffer as ZBuffer};
18+
use support::three_d::{
19+
make_sphere_points, project_with_projection, rotate_x, rotate_y, Projection, Vec3,
20+
};
1521
use txtplot::ChartContext;
1622

17-
// ============================================================================
18-
// 3D MATH
19-
// ============================================================================
20-
#[derive(Clone, Copy, Debug)]
21-
struct Vec3 {
22-
x: f64,
23-
y: f64,
24-
z: f64,
25-
}
26-
impl Vec3 {
27-
fn new(x: f64, y: f64, z: f64) -> Self {
28-
Self { x, y, z }
29-
}
30-
fn add(self, o: Vec3) -> Self {
31-
Self::new(self.x + o.x, self.y + o.y, self.z + o.z)
32-
}
33-
fn sub(self, o: Vec3) -> Self {
34-
Self::new(self.x - o.x, self.y - o.y, self.z - o.z)
35-
}
36-
fn mul(self, s: f64) -> Self {
37-
Self::new(self.x * s, self.y * s, self.z * s)
38-
}
39-
fn dot(self, o: Vec3) -> f64 {
40-
self.x * o.x + self.y * o.y + self.z * o.z
41-
}
42-
fn norm(self) -> f64 {
43-
self.dot(self).sqrt()
44-
}
45-
fn normalize(self) -> Self {
46-
let l = self.norm();
47-
if l > 0.0 {
48-
Self::new(self.x / l, self.y / l, self.z / l)
49-
} else {
50-
self
51-
}
52-
}
53-
}
54-
55-
fn rotate_x(v: Vec3, a: f64) -> Vec3 {
56-
let (s, c) = a.sin_cos();
57-
Vec3::new(v.x, v.y * c - v.z * s, v.y * s + v.z * c)
58-
}
59-
fn rotate_y(v: Vec3, a: f64) -> Vec3 {
60-
let (s, c) = a.sin_cos();
61-
Vec3::new(v.x * c - v.z * s, v.y, v.x * s + v.z * c)
62-
}
63-
// ============================================================================
64-
// GRAPHICS ENGINE (Z-BUFFER)
65-
// ============================================================================
66-
struct ZBuffer {
67-
w: usize,
68-
h: usize,
69-
z: Vec<f64>,
70-
id: Vec<Option<usize>>,
71-
}
72-
impl ZBuffer {
73-
fn new(w: usize, h: usize) -> Self {
74-
Self {
75-
w,
76-
h,
77-
z: vec![f64::INFINITY; w * h],
78-
id: vec![None; w * h],
79-
}
80-
}
81-
fn clear(&mut self) {
82-
self.z.fill(f64::INFINITY);
83-
self.id.fill(None);
84-
}
85-
#[inline]
86-
fn idx(&self, x: usize, y: usize) -> usize {
87-
y * self.w + x
88-
}
89-
fn test_and_set(&mut self, x: usize, y: usize, depth: f64, body_id: Option<usize>) -> bool {
90-
let i = self.idx(x, y);
91-
if depth < self.z[i] {
92-
self.z[i] = depth;
93-
self.id[i] = body_id;
94-
true
95-
} else {
96-
false
97-
}
98-
}
99-
}
100-
10123
// ============================================================================
10224
// GRAVITY: NEWTONIAN N-BODY PHYSICS
10325
// ============================================================================
@@ -169,95 +91,6 @@ fn create_body(
16991
}
17092
}
17193

172-
// ============================================================================
173-
// DRAWING FUNCTIONS
174-
// ============================================================================
175-
fn make_sphere_points(lat_steps: usize, lon_steps: usize) -> Vec<Vec3> {
176-
let mut pts = Vec::with_capacity(lat_steps * lon_steps);
177-
for i in 0..lat_steps {
178-
let v = i as f64 / (lat_steps - 1).max(1) as f64;
179-
let theta = v * std::f64::consts::PI;
180-
let (st, ct) = theta.sin_cos();
181-
for j in 0..lon_steps {
182-
let u = j as f64 / lon_steps as f64;
183-
let phi = u * std::f64::consts::TAU;
184-
pts.push(Vec3::new(st * phi.cos(), ct, st * phi.sin()));
185-
}
186-
}
187-
pts
188-
}
189-
190-
fn project_to_screen(v_cam: Vec3, w: f64, h: f64, scale: f64) -> Option<(isize, isize, f64)> {
191-
if v_cam.z <= 1.5 {
192-
return None;
193-
} // Camera culling
194-
let px = (v_cam.x / v_cam.z) * 2.0;
195-
let py = v_cam.y / v_cam.z;
196-
Some((
197-
(w / 2.0 + px * scale).round() as isize,
198-
(h / 2.0 + py * scale).round() as isize,
199-
v_cam.z,
200-
))
201-
}
202-
203-
fn plot_z(
204-
chart: &mut ChartContext,
205-
zb: &mut ZBuffer,
206-
x: isize,
207-
y: isize,
208-
z: f64,
209-
col: Color,
210-
id: Option<usize>,
211-
) {
212-
if x < 0 || y < 0 {
213-
return;
214-
}
215-
let (ux, uy) = (x as usize, y as usize);
216-
if ux < zb.w && uy < zb.h && zb.test_and_set(ux, uy, z, id) {
217-
chart.canvas.set_pixel_screen(ux, uy, Some(col));
218-
}
219-
}
220-
221-
fn line_z(
222-
chart: &mut ChartContext,
223-
zb: &mut ZBuffer,
224-
p1: (isize, isize, f64),
225-
p2: (isize, isize, f64),
226-
col: Color,
227-
) {
228-
let min_x = p1.0.min(p2.0);
229-
let max_x = p1.0.max(p2.0);
230-
let min_y = p1.1.min(p2.1);
231-
let max_y = p1.1.max(p2.1);
232-
233-
if max_x < 0 || min_x >= zb.w as isize || max_y < 0 || min_y >= zb.h as isize {
234-
return;
235-
}
236-
237-
let dx = (p2.0 - p1.0).abs();
238-
let dy = (p2.1 - p1.1).abs();
239-
let steps = dx.max(dy).max(1) as i32;
240-
if steps > 1500 {
241-
return;
242-
}
243-
244-
for s in 0..=steps {
245-
let t = s as f64 / steps as f64;
246-
let xf = p1.0 as f64 + (p2.0 as f64 - p1.0 as f64) * t;
247-
let yf = p1.1 as f64 + (p2.1 as f64 - p1.1 as f64) * t;
248-
let zf = p1.2 + (p2.2 - p1.2) * t;
249-
plot_z(
250-
chart,
251-
zb,
252-
xf.round() as isize,
253-
yf.round() as isize,
254-
zf,
255-
col,
256-
None,
257-
);
258-
}
259-
}
260-
26194
// ============================================================================
26295
// MAIN LOOP
26396
// ============================================================================
@@ -392,8 +225,8 @@ fn main() -> io::Result<()> {
392225

393226
// --- INPUT ---
394227
while event::poll(Duration::from_millis(0))? {
395-
if let Event::Key(KeyEvent { code, .. }) = event::read()? {
396-
match code {
228+
match event::read()? {
229+
Event::Key(KeyEvent { code, .. }) => match code {
397230
KeyCode::Char('q') | KeyCode::Esc => {
398231
execute!(
399232
stdout,
@@ -476,47 +309,15 @@ fn main() -> io::Result<()> {
476309
}
477310
}
478311
_ => {}
479-
}
480-
} else if let Event::Mouse(me) = event::read()? {
481-
let get_clicked_id = |c: u16, r: u16, zb: &ZBuffer| -> Option<usize> {
482-
let mx = (c.saturating_sub(2) as isize) * 2;
483-
let my = (r.saturating_sub(2) as isize) * 4;
484-
let mut cl_id = None;
485-
let mut min_dist = 40.0;
486-
let mut close_z = f64::INFINITY;
487-
for py in (my - 40).max(0) as usize..=(my + 40).min(zb.h as isize - 1) as usize
488-
{
489-
for px in
490-
(mx - 40).max(0) as usize..=(mx + 40).min(zb.w as isize - 1) as usize
491-
{
492-
if let Some(id) = zb.id[zb.idx(px, py)] {
493-
let dist = ((px as f64 - mx as f64).powi(2)
494-
+ (py as f64 - my as f64).powi(2))
495-
.sqrt();
496-
if dist < min_dist {
497-
min_dist = dist;
498-
close_z = zb.z[zb.idx(px, py)];
499-
cl_id = Some(id);
500-
} else if (dist - min_dist).abs() < 1.0
501-
&& zb.z[zb.idx(px, py)] < close_z
502-
{
503-
close_z = zb.z[zb.idx(px, py)];
504-
cl_id = Some(id);
505-
}
506-
}
507-
}
508-
}
509-
cl_id
510-
};
511-
512-
match me.kind {
312+
},
313+
Event::Mouse(me) => match me.kind {
513314
MouseEventKind::Down(MouseButton::Left) => {
514315
is_dragging = true;
515316
last_mouse_pos = Some((me.column, me.row));
516-
selected_body = get_clicked_id(me.column, me.row, zb);
317+
selected_body = zb.pick_body(me.column, me.row);
517318
}
518319
MouseEventKind::Down(MouseButton::Right) => {
519-
follow_body = get_clicked_id(me.column, me.row, zb);
320+
follow_body = zb.pick_body(me.column, me.row);
520321
if follow_body.is_some() {
521322
selected_body = follow_body;
522323
}
@@ -576,7 +377,8 @@ fn main() -> io::Result<()> {
576377
}
577378
}
578379
_ => {}
579-
}
380+
},
381+
_ => {}
580382
}
581383
}
582384

@@ -657,7 +459,12 @@ fn main() -> io::Result<()> {
657459
let mut v_cam = v_world.sub(camera_target_offset).sub(cam_pos);
658460
v_cam = rotate_y(v_cam, -cam_yaw);
659461
v_cam = rotate_x(v_cam, -cam_pitch);
660-
project_to_screen(v_cam, cw, ch, scale)
462+
project_with_projection(
463+
v_cam,
464+
cw,
465+
ch,
466+
Projection::new(1.5, 0.5, 0.5, scale * 2.0, scale),
467+
)
661468
};
662469

663470
for (i, body) in bodies.iter().enumerate() {

examples/support/solar.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,41 @@ pub fn plot_z(
104104
chart.canvas.set_pixel_screen(ux, uy, Some(color));
105105
}
106106
}
107+
108+
pub fn line_z(
109+
chart: &mut ChartContext,
110+
zbuf: &mut PickingZBuffer,
111+
start: (isize, isize, f64),
112+
end: (isize, isize, f64),
113+
color: Color,
114+
) {
115+
let min_x = start.0.min(end.0);
116+
let max_x = start.0.max(end.0);
117+
let min_y = start.1.min(end.1);
118+
let max_y = start.1.max(end.1);
119+
120+
if max_x < 0 || min_x >= zbuf.width as isize || max_y < 0 || min_y >= zbuf.height as isize {
121+
return;
122+
}
123+
124+
let steps = (end.0 - start.0).abs().max((end.1 - start.1).abs()).max(1) as usize;
125+
if steps > 1500 {
126+
return;
127+
}
128+
129+
for step in 0..=steps {
130+
let t = step as f64 / steps as f64;
131+
let x = start.0 as f64 + (end.0 - start.0) as f64 * t;
132+
let y = start.1 as f64 + (end.1 - start.1) as f64 * t;
133+
let z = start.2 + (end.2 - start.2) * t;
134+
plot_z(
135+
chart,
136+
zbuf,
137+
x.round() as isize,
138+
y.round() as isize,
139+
z,
140+
color,
141+
None,
142+
);
143+
}
144+
}

examples/support/three_d.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ impl Vec3 {
2323
Self::new(self.x - other.x, self.y - other.y, self.z - other.z)
2424
}
2525

26+
pub fn mul(self, scalar: f64) -> Self {
27+
Self::new(self.x * scalar, self.y * scalar, self.z * scalar)
28+
}
29+
2630
pub fn dot(self, other: Vec3) -> f64 {
2731
self.x * other.x + self.y * other.y + self.z * other.z
2832
}

0 commit comments

Comments
 (0)