Add `Mesh` transformation
Objective
It can sometimes be useful to transform actual Mesh data without needing to change the Transform of an entity. For example, one might want to spawn a circle mesh facing up instead of facing Z, or to spawn a mesh slightly offset without needing child entities.
Solution
Add transform_by and transformed_by methods to Mesh. They take a Transform and apply the translation, rotation, and scale to vertex positions, and the rotation to normals and tangents.
In the load_gltf example, with this system:
fn transform(time: Res<Time>, mut q: Query<&mut Handle<Mesh>>, mut meshes: ResMut<Assets<Mesh>>) {
let sin = 0.0025 * time.elapsed_seconds().sin();
for mesh_handle in &mut q {
if let Some(mesh) = meshes.get_mut(mesh_handle.clone_weak()) {
let transform =
Transform::from_rotation(Quat::from_rotation_y(0.75 * time.delta_seconds()))
.with_scale(Vec3::splat(1.0 + sin));
mesh.transform_by(transform);
}
}
}
it looks like this:
https://github.com/bevyengine/bevy/assets/57632562/60432456-6d28-4d06-9c94-2f4148f5acd5
I like this, but maybe it would be better to implement this as Add<Transform> and AddAssign<Transform>?
Not sure, maybe? But it'd probably be Mul instead of Add. Transformations are typically represented with multiplication, e.g. transform * point is equivalent to transform.transform_point(point); you can't add or subtract transforms.
I could at least add a transformed_by method so that you could create and transform the mesh on a single line instead of having to define a mutable mesh and separately call transform_by on that
I ended up adding transformed_by and implementing Mul<Mesh> for Transform. You can now do this:
let transform = Transform::from_xyz(0.0, 2.0, 0.0);
// These are equivalent
let cuboid = Mesh::from(shape::Box::default()).transformed_by(transform);
let cuboid = transform * Mesh::from(shape::Box::default());
let mut cuboid = Mesh::from(shape::Box::default());
cuboid.transform_by(transform);
Feel free to suggest further improvements/changes to the API
Why did you implement Mul<Mesh> for Transform and not Mul<Transform> for Mesh? You could also implement MulAssign<Transform> for Mesh and be able to perform syntax like this:
let mut cuboid = Mesh::from(shape::Box::default());
cuboid *= transform;
// or
let cuboid = Mesh::from(shape::Box::default()) * transform;
For consistency. You can't do point * transform or point *= transform, so I don't see why you'd be able to do mesh * transform. Whether or not you should be able to do point * transform is a separate discussion though
For me, the current transform * point or transform * mesh order makes sense, since it reads like "transform point" or "transform mesh". The other way around it'd read something like "to this point, apply transform"
Here to mention that I ran into exactly this use-case (wanted a xz plane circle).
I do think the Mul<> operator not being symmetric is a bit weird, and I will most likely used the transformed_by variant.
alternatively to avoid a division, (normal.xyz * scale.yzx * scale.zxy).normalized() (component-wise multiplication)
If any component of scale is zero it will cause a division by zero and result in infinite-length normals, maybe the division-skipping version that uses only multiplications is a better option
If any component of scale is zero it will cause a division by zero and result in infinite-length normals, maybe the division-skipping version that uses only multiplications is a better option
Won't the multiplication version similarly fail when we attempt to normalize the product? Or is .normalized implicitly "normalize or zero" behavior?
I can use normalize_or_zero for it
No, the multiplication-only version will work fine, i'm talking about only one component being zero. ~~normalize_or_zero is unneeded.~~ edit: if the normal is perpendicular to the axis being flattened along it will be zero. The vector as a whole will usually not be zero as it will still have some non-zero component. It doesnt quite make sense to scale a mesh on two or more axes by zero, that should be guarded for at the top level imo. It would just get collapsed to a line segment and not render anything because all triangles would be zero-area. Zero scales only make sense for flattening meshes along a single axis.
Thanks for the patience and the fixes