Skip to content

Instantly share code, notes, and snippets.

@DGriffin91
Last active March 5, 2024 08:11
Show Gist options
  • Save DGriffin91/e63e5f7a90b633250c2cf4bf8fd61ef8 to your computer and use it in GitHub Desktop.
Save DGriffin91/e63e5f7a90b633250c2cf4bf8fd61ef8 to your computer and use it in GitHub Desktop.
Bevy Combine Meshes with Transforms
// License: Apache-2.0 / MIT
// Combine multiple meshes in Bevy with Transforms
// Adapted from https://github.com/bevyengine/bevy/blob/main/examples/3d/3d_shapes.rs
use std::f32::consts::PI;
use bevy::{
math::Vec4Swizzles,
prelude::*,
render::{
mesh::{Indices, VertexAttributeValues},
render_resource::{Extent3d, PrimitiveTopology, TextureDimension, TextureFormat},
texture::ImageSettings,
},
};
fn main() {
App::new()
.insert_resource(ImageSettings::default_nearest())
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.run();
}
const X_EXTENT: f32 = 14.;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut images: ResMut<Assets<Image>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let debug_material = materials.add(StandardMaterial {
base_color_texture: Some(images.add(uv_debug_texture())),
..default()
});
let shapes = [
shape::Cube::default().into(),
shape::Box::default().into(),
shape::Capsule::default().into(),
shape::Torus::default().into(),
shape::Icosphere::default().into(),
shape::UVSphere::default().into(),
];
let transforms = (0..shapes.len())
.map(|i| {
Transform::from_xyz(
-X_EXTENT / 2. + i as f32 / (shapes.len() - 1) as f32 * X_EXTENT,
2.0,
0.0,
)
.with_rotation(Quat::from_rotation_x(-PI / 4.))
})
.collect::<Vec<Transform>>();
let combined_mesh = combine_meshes(&shapes, &transforms, true, false, true, false);
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(combined_mesh),
material: debug_material,
..default()
});
commands.spawn_bundle(PointLightBundle {
point_light: PointLight {
intensity: 9000.0,
range: 100.,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(8.0, 16.0, 8.0),
..default()
});
// ground plane
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(shape::Plane { size: 50. }.into()),
material: materials.add(Color::SILVER.into()),
..default()
});
commands.spawn_bundle(Camera3dBundle {
transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
..default()
});
}
/// Creates a colorful test pattern
fn uv_debug_texture() -> Image {
const TEXTURE_SIZE: usize = 8;
let mut palette: [u8; 32] = [
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
];
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
for y in 0..TEXTURE_SIZE {
let offset = TEXTURE_SIZE * y * 4;
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
palette.rotate_right(4);
}
Image::new_fill(
Extent3d {
width: TEXTURE_SIZE as u32,
height: TEXTURE_SIZE as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&texture_data,
TextureFormat::Rgba8UnormSrgb,
)
}
fn combine_meshes(
meshes: &[Mesh],
transforms: &[Transform],
use_normals: bool,
use_tangents: bool,
use_uvs: bool,
use_colors: bool,
) -> Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
let mut positions: Vec<[f32; 3]> = Vec::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut tangets: Vec<[f32; 4]> = Vec::new();
let mut uvs: Vec<[f32; 2]> = Vec::new();
let mut colors: Vec<[f32; 4]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut indices_offset = 0;
if meshes.len() != transforms.len() {
panic!(
"meshes.len({}) != transforms.len({})",
meshes.len(),
transforms.len()
);
}
for (mesh, trans) in meshes.iter().zip(transforms) {
if let Indices::U32(mesh_indices) = &mesh.indices().unwrap() {
let mat = trans.compute_matrix();
let positions_len;
if let Some(VertexAttributeValues::Float32x3(vert_positions)) =
&mesh.attribute(Mesh::ATTRIBUTE_POSITION)
{
positions_len = vert_positions.len();
for p in vert_positions {
positions.push(mat.transform_point3(Vec3::from(*p)).into());
}
} else {
panic!("no positions")
}
if use_uvs {
if let Some(VertexAttributeValues::Float32x2(vert_uv)) =
&mesh.attribute(Mesh::ATTRIBUTE_UV_0)
{
for uv in vert_uv {
uvs.push(*uv);
}
} else {
panic!("no uvs")
}
}
if use_normals {
// Comment below taken from mesh_normal_local_to_world() in mesh_functions.wgsl regarding
// transform normals from local to world coordinates:
// NOTE: The mikktspace method of normal mapping requires that the world normal is
// re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents
// and normal maps so that the exact inverse process is applied when shading. Blender, Unity,
// Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code
// unless you really know what you are doing.
// http://www.mikktspace.com/
let inverse_transpose_model = mat.inverse().transpose();
let inverse_transpose_model = Mat3 {
x_axis: inverse_transpose_model.x_axis.xyz(),
y_axis: inverse_transpose_model.y_axis.xyz(),
z_axis: inverse_transpose_model.z_axis.xyz(),
};
if let Some(VertexAttributeValues::Float32x3(vert_normals)) =
&mesh.attribute(Mesh::ATTRIBUTE_NORMAL)
{
for n in vert_normals {
normals.push(
inverse_transpose_model
.mul_vec3(Vec3::from(*n))
.normalize_or_zero()
.into(),
);
}
} else {
panic!("no normals")
}
}
if use_tangents {
if let Some(VertexAttributeValues::Float32x4(vert_tangets)) =
&mesh.attribute(Mesh::ATTRIBUTE_TANGENT)
{
for t in vert_tangets {
tangets.push(*t);
}
} else {
panic!("no tangets")
}
}
if use_colors {
if let Some(VertexAttributeValues::Float32x4(vert_colors)) =
&mesh.attribute(Mesh::ATTRIBUTE_COLOR)
{
for c in vert_colors {
colors.push(*c);
}
} else {
panic!("no colors")
}
}
for i in mesh_indices {
indices.push(*i + indices_offset);
}
indices_offset += positions_len as u32;
}
}
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
if use_normals {
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
}
if use_tangents {
mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangets);
}
if use_uvs {
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
}
if use_colors {
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
}
mesh.set_indices(Some(Indices::U32(indices)));
mesh
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment