Created
March 6, 2023 14:52
-
-
Save mxgrey/9f60f7e7319204f3a22a17606ad4bf55 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![feature(array_windows)] | |
use std::f32::consts::PI; | |
use bevy::{ | |
prelude::*, | |
render::mesh::{PrimitiveTopology, Indices}, | |
window::close_on_esc, | |
}; | |
use bevy_mod_outline::*; | |
#[bevy_main] | |
fn main() { | |
App::new() | |
.insert_resource(Msaa { samples: 4 }) | |
.insert_resource(ClearColor(Color::BLACK)) | |
.add_plugins(DefaultPlugins) | |
.add_plugin(OutlinePlugin) | |
.add_startup_system(setup) | |
.add_system(oscillate) | |
.add_system(close_on_esc) | |
.run(); | |
} | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut materials: ResMut<Assets<StandardMaterial>>, | |
) { | |
let mut flat_mesh = Mesh::new(PrimitiveTopology::TriangleList); | |
let positions: Vec<_> = [ | |
[-1.0, 0.0, 0.0], | |
[-1.0, 1.0, 0.0], | |
[1.0, 1.0, 0.0], | |
[2.5, 0.0, 0.0], | |
].into_iter().collect(); | |
let normals: Vec<_> = [[0.0, 0.0, 1.0]].into_iter().cycle().take(4).collect(); | |
let indices: Vec<_> = [0, 2, 1, 0, 3, 2].into_iter().collect(); | |
let (positions, normals, indices) = outline_sharp_edges(positions, normals, indices); | |
dbg!(&indices); | |
flat_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); | |
flat_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); | |
flat_mesh.set_indices(Some(Indices::U32(indices))); | |
commands | |
.spawn(PbrBundle { | |
mesh: meshes.add(flat_mesh), | |
material: materials.add(Color::CYAN.into()), | |
..default() | |
}) | |
.insert(OutlineBundle { | |
outline: OutlineVolume { | |
visible: true, | |
width: 5.0, | |
colour: Color::RED, | |
}, | |
..default() | |
}) | |
.insert(SetOutlineDepth::Flat { model_origin: Vec3::ZERO }); | |
commands | |
.spawn(PointLightBundle { | |
point_light: PointLight { | |
intensity: 1500.0, | |
..default() | |
}, | |
transform: Transform::from_xyz(0.0, 0.0, 3.0), | |
..default() | |
}); | |
commands | |
.spawn(SpatialBundle::default()) | |
.insert(Oscillate { | |
lower: -PI * 30.0 / 180.0, | |
upper: PI * 30.0 / 180.0, | |
rate: 3.0 / (2.0 * PI), | |
// lower: -1.1 * PI, | |
// upper: 1.1 * PI, | |
// rate: 1.0 / (2.0 * PI), | |
axis: Vec3::X, | |
}) | |
.with_children(|p| { | |
p.spawn(Camera3dBundle { | |
// transform: Transform::from_xyz(0.0, -1.0, 10.0).looking_at(Vec3::ZERO, Vec3::Z), | |
transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), | |
// transform: Transform::from_xyz(15.0, -1.0, 10.0).looking_at(Vec3::new(15.0, -1.0, 0.0), Vec3::Y), | |
..default() | |
}); | |
}); | |
} | |
fn outline_sharp_edges( | |
mut initial_positions: Vec<[f32; 3]>, | |
initial_normals: Vec<[f32; 3]>, | |
initial_indices: Vec<u32>, | |
) -> (Vec<[f32; 3]>, Vec<[f32; 3]>, Vec<u32>) { | |
let initial_index = initial_positions.len() as u32; | |
let mut positions = initial_positions.clone(); | |
let mut normals = initial_normals; | |
let mut indices = initial_indices; | |
// Close the loop by repeating the first and last values at the end and start, respectively | |
if let Some(pf) = initial_positions.last() { | |
let pf = *pf; | |
if let Some(pi) = initial_positions.first() { | |
let pi = *pi; | |
initial_positions.push(pi); | |
} | |
initial_positions.insert(0, pf); | |
} | |
for (i, [p0, p1, p2]) in initial_positions.array_windows::<3>().enumerate() { | |
let i = i as u32; | |
let p = *p1; | |
let p0 = Vec3::new(p0[0], p0[1], 0.0); | |
let p1 = Vec3::new(p1[0], p1[1], 0.0); | |
let p2 = Vec3::new(p2[0], p2[1], 0.0); | |
let v0 = match (p1 - p0).try_normalize() { | |
Some(v) => v, | |
None => continue, | |
}; | |
let v1 = match (p2 - p1).try_normalize() { | |
Some(v) => v, | |
None => continue, | |
}; | |
// n: normal | |
let n = Vec3::Z; | |
let u = n.cross(v0).normalize(); | |
let w = n.cross(v1).normalize(); | |
// b: bisector | |
let b = match (u + w).try_normalize() { | |
Some(b) => b, | |
None => { | |
// This means that u and w are pointing in opposite directions, | |
// so the next vertex is in a perfect 180 back towards the | |
// previous vertex. We can simply use v0 as the bisecting | |
// vector. | |
v0 | |
} | |
}; | |
positions.extend([p, p, p, p, p, p, p, p]); | |
normals.extend([u, -u, w, -w, b, -b, n, -n].map(Into::<[f32; 3]>::into)); | |
let u0 = 0; | |
let u1 = 1; | |
let w0 = 2; | |
let w1 = 3; | |
let b0 = 4; | |
let b1 = 5; | |
let n0 = 6; | |
let n1 = 7; | |
let i_delta = 8; | |
// Current base index | |
let c = initial_index + i_delta*i; | |
// Next base index | |
let f = if i == initial_index-1 { | |
// We have reached the last element of the original vertex set | |
initial_index | |
} else { | |
initial_index + i_delta*(i+1) | |
}; | |
dbg!(c, f); | |
if w.cross(b).dot(n) < 0.0 { | |
// left turn | |
indices.extend([ | |
c+u1, c+b1, c+n0, c+b1, c+w1, c+n0, | |
c+u1, c+n1, c+b1, c+b1, c+n1, c+w1, | |
]); | |
} else { | |
// right turn | |
indices.extend([ | |
c+u0, c+n0, c+b0, c+b0, c+n0, c+w0, | |
c+u0, c+b0, c+n1, c+b0, c+w0, c+n1, | |
]); | |
} | |
indices.extend([ | |
c+w0, c+n0, f+n0, c+w0, f+n0, f+u0, | |
c+w1, f+n0, c+n0, c+w1, f+u1, f+n0, | |
c+w0, f+u0, f+n1, c+w0, f+n1, c+n1, | |
c+w1, f+n1, f+u1, c+w1, c+n1, f+n1, | |
]); | |
} | |
(positions, normals, indices) | |
} | |
#[derive(Component)] | |
struct Oscillate { | |
lower: f32, | |
upper: f32, | |
rate: f32, | |
axis: Vec3, | |
} | |
fn oscillate( | |
mut oscillating: Query<(&mut Transform, &Oscillate)>, | |
time: Res<Time>, | |
) { | |
for (mut tf, osc) in &mut oscillating { | |
let angle = (osc.upper - osc.lower) * (osc.rate * time.elapsed_seconds()).cos() + osc.lower; | |
tf.rotation = Quat::from_axis_angle(osc.axis, angle); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment