Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
13 kB · 357 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358use polymodel_renderer_protocol::scene::SceneTexmapProjectionMode;
use crate::RendererError;
#[derive(Debug, Clone, PartialEq, Eq)]pub struct GlossMapPixels { pub width: u32, pub height: u32, pub bit_depth: png::BitDepth, pub alpha: Vec<u32>, pub packed_rgb: Vec<[u8; 3]>,}
pub fn decode_glossmap_png(bytes: &[u8]) -> Result<GlossMapPixels, RendererError> { let (info, output) = decode_png(bytes, "GLOSSMAP")?; let bytes = &output[..info.buffer_size()]; let (channels, has_alpha) = match info.color_type { png::ColorType::Rgba => (4usize, true), png::ColorType::GrayscaleAlpha => (2usize, true), png::ColorType::Rgb => (3usize, false), png::ColorType::Grayscale => (1usize, false), png::ColorType::Indexed => { return Err(RendererError::TextureDecode( "GLOSSMAP indexed output was not expanded".into(), )); } }; let (sample_bytes, max) = match info.bit_depth { png::BitDepth::Eight => (1usize, u32::from(u8::MAX)), png::BitDepth::Sixteen => (2usize, u32::from(u16::MAX)), depth => { return Err(RendererError::TextureDecode(format!( "unsupported decoded GLOSSMAP bit depth: {depth:?}" ))); } }; let pixel_stride = channels .checked_mul(sample_bytes) .ok_or(RendererError::IndexOverflow { context: "GLOSSMAP pixel stride", })?; if pixel_stride == 0 || bytes.len() % pixel_stride != 0 { return Err(RendererError::InvalidRange { context: "GLOSSMAP decoded pixels", index: bytes.len(), len: pixel_stride, }); } let mut alpha = Vec::with_capacity(bytes.len() / pixel_stride); for pixel in bytes.chunks_exact(pixel_stride) { let sample = |channel: usize| { let offset = channel * sample_bytes; if sample_bytes == 1 { u32::from(pixel[offset]) } else { u32::from(u16::from_be_bytes([pixel[offset], pixel[offset + 1]])) } }; alpha.push(if has_alpha { sample(channels - 1) } else { max }); } let packed_rgb = packed_roughness_from_alpha_samples(&alpha, max)?; Ok(GlossMapPixels { width: info.width, height: info.height, bit_depth: info.bit_depth, alpha, packed_rgb, })}
fn decode_png(bytes: &[u8], label: &str) -> Result<(png::OutputInfo, Vec<u8>), RendererError> { let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes)); decoder.set_transformations(png::Transformations::EXPAND); let mut reader = decoder .read_info() .map_err(|error| RendererError::TextureDecode(format!("decode {label} header: {error}")))?; let output_size = reader .output_buffer_size() .ok_or(RendererError::IndexOverflow { context: "decoded texture size", })?; let mut output = vec![0; output_size]; let info = reader .next_frame(&mut output) .map_err(|error| RendererError::TextureDecode(format!("decode {label} pixels: {error}")))?; Ok((info, output))}
pub fn decode_albedo_png(bytes: &[u8]) -> Result<(u32, u32, Vec<[u8; 4]>, bool), RendererError> { let (info, output) = decode_png(bytes, "albedo")?; if info.bit_depth != png::BitDepth::Eight { return Err(RendererError::TextureDecode(format!( "unsupported albedo bit depth: {:?}", info.bit_depth ))); } let source = &output[..info.buffer_size()]; let (channels, has_alpha) = match info.color_type { png::ColorType::Rgba => (4, true), png::ColorType::Rgb => (3, false), png::ColorType::GrayscaleAlpha => (2, true), png::ColorType::Grayscale => (1, false), png::ColorType::Indexed => { return Err(RendererError::TextureDecode( "albedo indexed output was not expanded".into(), )); } }; let mut rgba = Vec::with_capacity( (info.width as usize) .saturating_mul(info.height as usize) .saturating_mul(4), ); let mut translucent = false; for pixel in source.chunks_exact(channels) { let (r, g, b, a) = match channels { 4 => (pixel[0], pixel[1], pixel[2], pixel[3]), 3 => (pixel[0], pixel[1], pixel[2], 255), 2 => (pixel[0], pixel[0], pixel[0], pixel[1]), 1 => (pixel[0], pixel[0], pixel[0], 255), _ => unreachable!(), }; translucent |= a < 255; rgba.push([r, g, b, a]); } if source.len() != rgba.len() * channels { return Err(RendererError::InvalidRange { context: "albedo decoded pixels", index: source.len(), len: rgba.len() * channels, }); } let translucent = translucent || has_alpha && rgba.iter().any(|p| p[3] < 255); Ok((info.width, info.height, rgba, translucent))}
pub fn packed_roughness_from_alpha(alpha: u32, max: u32) -> Result<[u8; 3], RendererError> { if max == 0 || (max != u8::MAX as u32 && max != u16::MAX as u32) || alpha > max { return Err(RendererError::InvalidRange { context: "gloss alpha", index: alpha as usize, len: max as usize, }); } let roughness = max.checked_sub(alpha).ok_or(RendererError::IndexOverflow { context: "gloss alpha", })?; let numerator = u64::from(roughness) .checked_mul(255) .and_then(|value| value.checked_add(u64::from(max / 2))) .ok_or(RendererError::IndexOverflow { context: "roughness quantization", })?; let channel = numerator / u64::from(max); let channel = u8::try_from(channel).map_err(|_| RendererError::IndexOverflow { context: "roughness channel", })?; Ok([0, channel, 0])}
pub fn packed_roughness_from_alpha_samples( alpha: &[u32], max: u32,) -> Result<Vec<[u8; 3]>, RendererError> { alpha .iter() .copied() .map(|sample| packed_roughness_from_alpha(sample, max)) .collect()}
pub fn texmap_uvs( mode: SceneTexmapProjectionMode, parameters: &[f64], points: &[[f64; 3]],) -> Result<Vec<[f32; 2]>, RendererError> { let expected = match mode { SceneTexmapProjectionMode::Planar => 9, SceneTexmapProjectionMode::Cylindrical => 10, SceneTexmapProjectionMode::Spherical => 11, }; if parameters.len() != expected || parameters.iter().any(|value| !value.is_finite()) { return Err(RendererError::ParseAdaptation( "TEXMAP parameters must have the expected finite arity", )); } let point = |offset: usize| { [ parameters[offset], parameters[offset + 1], parameters[offset + 2], ] }; let p1 = point(0); let p2 = point(3); let p3 = point(6); let sub = |a: [f64; 3], b: [f64; 3]| [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; let dot = |a: [f64; 3], b: [f64; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; let cross = |a: [f64; 3], b: [f64; 3]| { [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], ] }; let length = |v: [f64; 3]| dot(v, v).sqrt(); let normalize = |v: [f64; 3]| { let len = length(v); (len.is_finite() && len > f64::EPSILON).then(|| [v[0] / len, v[1] / len, v[2] / len]) }; let clamp01 = |value: f64| value.clamp(0.0, 1.0); let result = match mode { SceneTexmapProjectionMode::Planar => { let u = sub(p2, p1); let v = sub(p3, p1); let uu = dot(u, u); let uv = dot(u, v); let vv = dot(v, v); let determinant = uu * vv - uv * uv; if !uu.is_finite() || !uv.is_finite() || !vv.is_finite() || !determinant.is_finite() || determinant <= f64::EPSILON { return Err(RendererError::ParseAdaptation( "degenerate PLANAR TEXMAP basis", )); } points .iter() .map(|point| { let delta = sub(*point, p1); let du = dot(delta, u); let dv = dot(delta, v); let u_coordinate = (du * vv - dv * uv) / determinant; let v_coordinate = (dv * uu - du * uv) / determinant; [clamp01(u_coordinate), clamp01(v_coordinate)] }) .collect::<Vec<_>>() } SceneTexmapProjectionMode::Cylindrical => { let axis = sub(p2, p1); let axis_len = length(axis); let axis_n = normalize(axis).ok_or(RendererError::ParseAdaptation( "degenerate CYLINDRICAL TEXMAP axis", ))?; let radial = sub(p3, p1); let radial = sub( radial, [ axis_n[0] * dot(radial, axis_n), axis_n[1] * dot(radial, axis_n), axis_n[2] * dot(radial, axis_n), ], ); let radial_n = normalize(radial).ok_or(RendererError::ParseAdaptation( "degenerate CYLINDRICAL TEXMAP radius", ))?; let angle_extent = parameters[9].to_radians(); if !angle_extent.is_finite() || angle_extent.abs() <= f64::EPSILON { return Err(RendererError::ParseAdaptation( "degenerate CYLINDRICAL TEXMAP angle", )); } points .iter() .map(|point| { let delta = sub(*point, p1); let axial = dot(delta, axis_n); let projected = sub( delta, [axis_n[0] * axial, axis_n[1] * axial, axis_n[2] * axial], ); let tangent = cross(axis_n, radial_n); let angle = dot(projected, tangent).atan2(dot(projected, radial_n)); [angle / angle_extent + 0.5, clamp01(axial / axis_len)] }) .collect::<Vec<_>>() } SceneTexmapProjectionMode::Spherical => { let normal = normalize(sub(p2, p1)).ok_or(RendererError::ParseAdaptation( "degenerate SPHERICAL TEXMAP direction", ))?; let mut horizontal = sub(p3, p1); horizontal = sub( horizontal, [ normal[0] * dot(horizontal, normal), normal[1] * dot(horizontal, normal), normal[2] * dot(horizontal, normal), ], ); let horizontal = normalize(horizontal).ok_or(RendererError::ParseAdaptation( "degenerate SPHERICAL TEXMAP basis", ))?; let vertical = normalize(cross(normal, horizontal)).ok_or( RendererError::ParseAdaptation("degenerate SPHERICAL TEXMAP vertical"), )?; let a = parameters[9].to_radians(); let b = parameters[10].to_radians(); if !a.is_finite() || !b.is_finite() || a.abs() <= f64::EPSILON || b.abs() <= f64::EPSILON { return Err(RendererError::ParseAdaptation( "degenerate SPHERICAL TEXMAP angle", )); } points .iter() .map(|point| { let direction = normalize(sub(*point, p1)).unwrap_or(normal); let longitude = dot(direction, horizontal).atan2(dot(direction, normal)); let latitude = dot(direction, vertical).clamp(-1.0, 1.0).asin(); [longitude / a + 0.5, clamp01(latitude / b + 0.5)] }) .collect::<Vec<_>>() } }; if result.iter().flatten().any(|value| !value.is_finite()) { return Err(RendererError::NonFiniteTransform); } let mut result = result; if matches!( mode, SceneTexmapProjectionMode::Cylindrical | SceneTexmapProjectionMode::Spherical ) { let mut reference = result.first().map(|uv| uv[0]).unwrap_or(0.0); for uv in result.iter_mut() { while uv[0] - reference > 0.5 { uv[0] -= 1.0; } while reference - uv[0] > 0.5 { uv[0] += 1.0; } reference = uv[0]; } } result .into_iter() .map(|[u, v]| { let u = if matches!(mode, SceneTexmapProjectionMode::Planar) { clamp01(u) } else { u }; let v = clamp01(v); let u = u as f32; let v = v as f32; (u.is_finite() && v.is_finite()) .then_some([u, v]) .ok_or(RendererError::NonFiniteTransform) }) .collect()}