use bevy::mesh::MeshVertexAttribute;
use bevy::render::render_resource::VertexFormat;
pub const ATTRIBUTE_MATERIAL_INDICES: MeshVertexAttribute =
MeshVertexAttribute::new("MaterialIndices", 988540910, VertexFormat::Uint32);
pub const ATTRIBUTE_MATERIAL_WEIGHTS: MeshVertexAttribute =
MeshVertexAttribute::new("MaterialWeights", 988540911, VertexFormat::Uint32);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct MaterialVertex {
pub indices: [u8; 4],
pub weights: [u8; 4],
}
impl MaterialVertex {
pub fn new(indices: [u8; 4], weights: [u8; 4]) -> Self {
Self { indices, weights }
}
pub fn single(index: u8) -> Self {
Self {
indices: [index, 0, 0, 0],
weights: [255, 0, 0, 0],
}
}
pub fn blend2(index0: u8, index1: u8, weight0: f32) -> Self {
let w0 = (weight0.clamp(0.0, 1.0) * 255.0) as u8;
let w1 = 255 - w0;
Self {
indices: [index0, index1, 0, 0],
weights: [w0, w1, 0, 0],
}
}
pub fn blend3(index0: u8, index1: u8, index2: u8, weights: [f32; 3]) -> Self {
let total = weights[0] + weights[1] + weights[2];
let normalized = if total > 0.0 {
[weights[0] / total, weights[1] / total, weights[2] / total]
} else {
[1.0, 0.0, 0.0]
};
let w0 = (normalized[0] * 255.0) as u8;
let w1 = (normalized[1] * 255.0) as u8;
let w2 = 255u8.saturating_sub(w0).saturating_sub(w1);
Self {
indices: [index0, index1, index2, 0],
weights: [w0, w1, w2, 0],
}
}
pub fn blend4(indices: [u8; 4], weights: [f32; 4]) -> Self {
let total: f32 = weights.iter().sum();
let normalized: [f32; 4] = if total > 0.0 {
[
weights[0] / total,
weights[1] / total,
weights[2] / total,
weights[3] / total,
]
} else {
[1.0, 0.0, 0.0, 0.0]
};
let w0 = (normalized[0] * 255.0) as u8;
let w1 = (normalized[1] * 255.0) as u8;
let w2 = (normalized[2] * 255.0) as u8;
let w3 = 255u8
.saturating_sub(w0)
.saturating_sub(w1)
.saturating_sub(w2);
Self {
indices,
weights: [w0, w1, w2, w3],
}
}
#[inline]
pub fn packed_indices(&self) -> u32 {
pack_u8x4(self.indices)
}
#[inline]
pub fn packed_weights(&self) -> u32 {
pack_u8x4(self.weights)
}
#[inline]
pub fn weight_sum(&self) -> u16 {
self.weights.iter().map(|&w| w as u16).sum()
}
#[inline]
pub fn active_material_count(&self) -> usize {
self.weights.iter().filter(|&&w| w > 0).count()
}
}
pub fn encode_material_data(vertices: &[MaterialVertex]) -> (Vec<u32>, Vec<u32>) {
let indices: Vec<u32> = vertices.iter().map(|v| v.packed_indices()).collect();
let weights: Vec<u32> = vertices.iter().map(|v| v.packed_weights()).collect();
(indices, weights)
}
#[inline]
pub fn pack_u8x4(values: [u8; 4]) -> u32 {
(values[0] as u32)
| ((values[1] as u32) << 8)
| ((values[2] as u32) << 16)
| ((values[3] as u32) << 24)
}
#[inline]
pub fn unpack_u8x4(packed: u32) -> [u8; 4] {
[
(packed & 0xFF) as u8,
((packed >> 8) & 0xFF) as u8,
((packed >> 16) & 0xFF) as u8,
((packed >> 24) & 0xFF) as u8,
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_material() {
let v = MaterialVertex::single(42);
assert_eq!(v.indices[0], 42);
assert_eq!(v.weights[0], 255);
assert_eq!(v.indices[1], 0);
assert_eq!(v.weights[1], 0);
assert_eq!(v.weight_sum(), 255);
assert_eq!(v.active_material_count(), 1);
}
#[test]
fn test_single_material_max_index() {
let v = MaterialVertex::single(255);
assert_eq!(v.indices[0], 255);
assert_eq!(v.weights[0], 255);
}
#[test]
fn test_blend2_even() {
let v = MaterialVertex::blend2(10, 20, 0.5);
assert_eq!(v.indices[0], 10);
assert_eq!(v.indices[1], 20);
assert_eq!(v.weight_sum(), 255);
assert_eq!(v.active_material_count(), 2);
assert_eq!(v.weights[0], 127);
assert_eq!(v.weights[1], 128); }
#[test]
fn test_blend2_extreme_values() {
let v = MaterialVertex::blend2(0, 1, 0.0);
assert_eq!(v.weights[0], 0);
assert_eq!(v.weights[1], 255);
let v = MaterialVertex::blend2(0, 1, 1.0);
assert_eq!(v.weights[0], 255);
assert_eq!(v.weights[1], 0);
}
#[test]
fn test_blend2_clamping() {
let v = MaterialVertex::blend2(0, 1, -0.5);
assert_eq!(v.weights[0], 0);
assert_eq!(v.weights[1], 255);
let v = MaterialVertex::blend2(0, 1, 1.5);
assert_eq!(v.weights[0], 255);
assert_eq!(v.weights[1], 0);
}
#[test]
fn test_blend3() {
let v = MaterialVertex::blend3(0, 1, 2, [0.5, 0.3, 0.2]);
assert_eq!(v.indices, [0, 1, 2, 0]);
assert_eq!(v.weight_sum(), 255);
assert_eq!(v.active_material_count(), 3);
}
#[test]
fn test_blend3_zero_weights() {
let v = MaterialVertex::blend3(5, 6, 7, [0.0, 0.0, 0.0]);
assert_eq!(v.weights[0], 255);
assert_eq!(v.weights[1], 0);
assert_eq!(v.weights[2], 0);
}
#[test]
fn test_blend4() {
let v = MaterialVertex::blend4([0, 1, 2, 3], [0.25, 0.25, 0.25, 0.25]);
assert_eq!(v.indices, [0, 1, 2, 3]);
assert_eq!(v.weight_sum(), 255);
assert_eq!(v.active_material_count(), 4);
}
#[test]
fn test_blend4_unequal_weights() {
let v = MaterialVertex::blend4([0, 1, 2, 3], [0.4, 0.3, 0.2, 0.1]);
assert_eq!(v.weight_sum(), 255);
assert!(v.weights[0] > v.weights[1]);
assert!(v.weights[1] > v.weights[2]);
assert!(v.weights[2] > v.weights[3]);
}
#[test]
fn test_blend4_zero_weights() {
let v = MaterialVertex::blend4([0, 1, 2, 3], [0.0, 0.0, 0.0, 0.0]);
assert_eq!(v.weights[0], 255);
assert_eq!(v.weight_sum(), 255);
}
#[test]
fn test_new_constructor() {
let v = MaterialVertex::new([10, 20, 30, 40], [100, 50, 75, 30]);
assert_eq!(v.indices, [10, 20, 30, 40]);
assert_eq!(v.weights, [100, 50, 75, 30]);
}
#[test]
fn test_pack_unpack_roundtrip() {
let values = [1, 2, 3, 4];
let packed = pack_u8x4(values);
let unpacked = unpack_u8x4(packed);
assert_eq!(values, unpacked);
}
#[test]
fn test_pack_unpack_zeros() {
let values = [0, 0, 0, 0];
let packed = pack_u8x4(values);
assert_eq!(packed, 0);
assert_eq!(unpack_u8x4(packed), values);
}
#[test]
fn test_pack_unpack_max_values() {
let values = [255, 255, 255, 255];
let packed = pack_u8x4(values);
assert_eq!(packed, 0xFFFFFFFF);
assert_eq!(unpack_u8x4(packed), values);
}
#[test]
fn test_pack_individual_bytes() {
assert_eq!(pack_u8x4([0xFF, 0, 0, 0]), 0x000000FF);
assert_eq!(pack_u8x4([0, 0xFF, 0, 0]), 0x0000FF00);
assert_eq!(pack_u8x4([0, 0, 0xFF, 0]), 0x00FF0000);
assert_eq!(pack_u8x4([0, 0, 0, 0xFF]), 0xFF000000);
}
#[test]
fn test_material_vertex_packing() {
let v = MaterialVertex::new([10, 20, 30, 40], [100, 50, 75, 30]);
let packed_indices = v.packed_indices();
let packed_weights = v.packed_weights();
assert_eq!(unpack_u8x4(packed_indices), [10, 20, 30, 40]);
assert_eq!(unpack_u8x4(packed_weights), [100, 50, 75, 30]);
}
#[test]
fn test_encode_material_data_empty() {
let (indices, weights) = encode_material_data(&[]);
assert!(indices.is_empty());
assert!(weights.is_empty());
}
#[test]
fn test_encode_material_data_single() {
let vertices = vec![MaterialVertex::single(5)];
let (indices, weights) = encode_material_data(&vertices);
assert_eq!(indices.len(), 1);
assert_eq!(weights.len(), 1);
assert_eq!(unpack_u8x4(indices[0]), [5, 0, 0, 0]);
assert_eq!(unpack_u8x4(weights[0]), [255, 0, 0, 0]);
}
#[test]
fn test_encode_material_data_multiple() {
let vertices = vec![
MaterialVertex::single(0),
MaterialVertex::blend2(1, 2, 0.5),
MaterialVertex::single(3),
];
let (indices, weights) = encode_material_data(&vertices);
assert_eq!(indices.len(), 3);
assert_eq!(weights.len(), 3);
assert_eq!(unpack_u8x4(indices[0])[0], 0);
assert_eq!(unpack_u8x4(weights[0])[0], 255);
assert_eq!(unpack_u8x4(indices[1])[0], 1);
assert_eq!(unpack_u8x4(indices[1])[1], 2);
assert_eq!(unpack_u8x4(indices[2])[0], 3);
}
#[test]
fn test_default_material_vertex() {
let v = MaterialVertex::default();
assert_eq!(v.indices, [0, 0, 0, 0]);
assert_eq!(v.weights, [0, 0, 0, 0]);
}
#[test]
fn test_material_vertex_equality() {
let v1 = MaterialVertex::single(5);
let v2 = MaterialVertex::single(5);
let v3 = MaterialVertex::single(6);
assert_eq!(v1, v2);
assert_ne!(v1, v3);
}
#[test]
fn test_material_vertex_clone() {
let v1 = MaterialVertex::blend2(1, 2, 0.7);
let v2 = v1.clone();
assert_eq!(v1, v2);
}
#[test]
fn test_all_blend_functions_sum_to_255() {
assert_eq!(MaterialVertex::single(0).weight_sum(), 255);
assert_eq!(MaterialVertex::blend2(0, 1, 0.3).weight_sum(), 255);
assert_eq!(MaterialVertex::blend2(0, 1, 0.7).weight_sum(), 255);
assert_eq!(MaterialVertex::blend3(0, 1, 2, [0.1, 0.2, 0.7]).weight_sum(), 255);
assert_eq!(MaterialVertex::blend4([0, 1, 2, 3], [0.1, 0.2, 0.3, 0.4]).weight_sum(), 255);
}
}