Implement native asset pipeline and cache (#64)
Some checks failed
Native code generation / deterministic (push) Failing after 2m18s
Imaging and meshing gate / native (push) Failing after 1m30s
JPEG 2000 feature / linux (push) Successful in 2m40s
Native Rust workspace compile / compile (push) Failing after 57s
Skia feature / linux (push) Successful in 31m8s

This commit is contained in:
2026-08-10 07:51:17 +00:00
parent d5c318d280
commit 52f62d8c39
23 changed files with 4511 additions and 1620 deletions

View File

@@ -0,0 +1,719 @@
//! GLTF material asset and simulator material-override encoding.
#![allow(
clippy::assigning_clones,
clippy::cast_possible_truncation,
clippy::collapsible_if,
clippy::float_cmp,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::must_use_candidate,
clippy::needless_pass_by_value,
clippy::should_implement_trait
)]
use crate::{Error, assets::GltfAlphaMode};
use libremetaverse_structured_data::{OSD, OSDMap};
use libremetaverse_types::{AssetType, Color4, UUID, Vector2, Vector3};
use serde_json::{Value, json};
use std::collections::HashMap;
const FLOAT_EPSILON: f32 = 1.192_092_9e-7;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GltfTextureTransform {
pub offset: Vector2,
pub rotation: f32,
pub scale: Vector2,
}
impl GltfTextureTransform {
pub fn default() -> Self {
Self {
offset: Vector2::zero(),
rotation: 0.0,
scale: Vector2 { x: 1.0, y: 1.0 },
}
}
pub fn equals_with_gltf_texture_transform(&self, other: Self) -> bool {
*self == other
}
pub fn equals_with_object(&self, _obj: Option<libremetaverse_types::compat::Object>) -> bool {
false
}
pub fn get_hash_code(&self) -> i32 {
(self.offset.x.to_bits()
^ self.offset.y.to_bits()
^ self.rotation.to_bits()
^ self.scale.x.to_bits()
^ self.scale.y.to_bits())
.cast_signed()
}
pub fn is_default(&self) -> bool {
self.offset.x.abs() <= FLOAT_EPSILON
&& self.offset.y.abs() <= FLOAT_EPSILON
&& self.rotation.abs() <= FLOAT_EPSILON
&& (self.scale.x - 1.0).abs() <= FLOAT_EPSILON
&& (self.scale.y - 1.0).abs() <= FLOAT_EPSILON
}
}
#[derive(Clone, Debug)]
pub struct AssetMaterial {
asset_id: UUID,
asset_data: Vec<u8>,
name: String,
texture_ids: Vec<UUID>,
texture_transforms: Vec<GltfTextureTransform>,
base_color_factor: Color4,
emissive_factor: Vector3,
metallic_factor: f32,
roughness_factor: f32,
alpha_cutoff: f32,
alpha_mode: GltfAlphaMode,
double_sided: bool,
override_alpha_mode: bool,
override_double_sided: bool,
}
impl AssetMaterial {
pub const TEXTURE_BASE_COLOR: i32 = 0;
pub const TEXTURE_NORMAL: i32 = 1;
pub const TEXTURE_METALLIC_ROUGHNESS: i32 = 2;
pub const TEXTURE_EMISSIVE: i32 = 3;
pub const TEXTURE_COUNT: i32 = 4;
pub fn default() -> Self {
Self {
asset_id: UUID::zero(),
asset_data: Vec::new(),
name: String::new(),
texture_ids: vec![UUID::zero(); Self::TEXTURE_COUNT as usize],
texture_transforms: vec![GltfTextureTransform::default(); Self::TEXTURE_COUNT as usize],
base_color_factor: Color4 {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
},
emissive_factor: Vector3::zero(),
metallic_factor: 1.0,
roughness_factor: 1.0,
alpha_cutoff: 0.5,
alpha_mode: GltfAlphaMode::Opaque,
double_sided: false,
override_alpha_mode: false,
override_double_sided: false,
}
}
pub fn gltf_override_null_uuid() -> UUID {
UUID::new_with_string("ffffffff-ffff-ffff-ffff-ffffffffffff".into())
.expect("constant UUID is valid")
}
pub fn new_with_constructor() -> Result<Self, Error> {
Ok(Self::default())
}
pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
if asset_data.len() > crate::asset_models::MAX_ASSET_BYTES {
return Err(Error::Argument);
}
let mut material = Self {
asset_id,
asset_data,
..Self::default()
};
if !material.decode()? {
return Err(Error::Argument);
}
Ok(material)
}
fn slot(slot: i32) -> Result<usize, Error> {
usize::try_from(slot)
.ok()
.filter(|slot| *slot < Self::TEXTURE_COUNT as usize)
.ok_or(Error::Argument)
}
pub fn set_texture_id(
&mut self,
slot: i32,
id: UUID,
for_override: Option<bool>,
) -> Result<(), Error> {
let slot = Self::slot(slot)?;
self.texture_ids[slot] = if for_override.unwrap_or(false) && id == UUID::zero() {
Self::gltf_override_null_uuid()
} else {
id
};
Ok(())
}
pub fn set_texture_offset(&mut self, slot: i32, offset: Vector2) -> Result<(), Error> {
self.texture_transforms[Self::slot(slot)?].offset = offset;
Ok(())
}
pub fn set_texture_scale(&mut self, slot: i32, scale: Vector2) -> Result<(), Error> {
self.texture_transforms[Self::slot(slot)?].scale = scale;
Ok(())
}
pub fn set_texture_rotation(&mut self, slot: i32, rotation: f32) -> Result<(), Error> {
self.texture_transforms[Self::slot(slot)?].rotation = rotation;
Ok(())
}
pub fn set_base_color_factor_with_color4_boolean(
&mut self,
mut color: Color4,
for_override: Option<bool>,
) -> Result<(), Error> {
if for_override.unwrap_or(false) && color == Self::default().base_color_factor {
color.a -= FLOAT_EPSILON;
}
self.base_color_factor = color;
Ok(())
}
pub fn set_emissive_factor_with_vector3_boolean(
&mut self,
mut color: Vector3,
for_override: Option<bool>,
) -> Result<(), Error> {
if for_override.unwrap_or(false) && color == Vector3::zero() {
color.x += FLOAT_EPSILON;
}
self.emissive_factor = color;
Ok(())
}
pub fn set_metallic_factor_with_single_boolean(
&mut self,
metallic: f32,
for_override: Option<bool>,
) -> Result<(), Error> {
if !metallic.is_finite() {
return Err(Error::Argument);
}
self.metallic_factor = if for_override.unwrap_or(false) {
metallic.min(1.0 - FLOAT_EPSILON)
} else {
metallic
};
Ok(())
}
pub fn set_roughness_factor_with_single_boolean(
&mut self,
roughness: f32,
for_override: Option<bool>,
) -> Result<(), Error> {
if !roughness.is_finite() {
return Err(Error::Argument);
}
self.roughness_factor = if for_override.unwrap_or(false) {
roughness.min(1.0 - FLOAT_EPSILON)
} else {
roughness
};
Ok(())
}
pub fn set_alpha_cutoff_with_single_boolean(
&mut self,
cutoff: f32,
for_override: Option<bool>,
) -> Result<(), Error> {
if !cutoff.is_finite() {
return Err(Error::Argument);
}
self.alpha_cutoff = if for_override.unwrap_or(false) && cutoff == 0.5 {
cutoff - FLOAT_EPSILON
} else {
cutoff
};
Ok(())
}
pub fn set_alpha_mode_with_gltf_alpha_mode_boolean(
&mut self,
mode: GltfAlphaMode,
for_override: Option<bool>,
) -> Result<(), Error> {
self.alpha_mode = mode;
self.override_alpha_mode = for_override.unwrap_or(false);
Ok(())
}
pub fn set_double_sided_with_boolean_boolean(
&mut self,
double_sided: bool,
for_override: Option<bool>,
) -> Result<(), Error> {
self.double_sided = double_sided;
self.override_double_sided = for_override.unwrap_or(false);
Ok(())
}
pub fn set_base_material(&mut self) -> Result<(), Error> {
let transforms = self.texture_transforms.clone();
let asset_id = self.asset_id;
*self = Self::default();
self.asset_id = asset_id;
self.texture_transforms = transforms;
Ok(())
}
pub fn apply_override(&mut self, value: Self) -> Result<(), Error> {
let sentinel = Self::gltf_override_null_uuid();
for slot in 0..Self::TEXTURE_COUNT as usize {
if value.texture_ids[slot] == sentinel {
self.texture_ids[slot] = UUID::zero();
} else if value.texture_ids[slot] != UUID::zero() {
self.texture_ids[slot] = value.texture_ids[slot];
}
if !value.texture_transforms[slot].is_default() {
self.texture_transforms[slot] = value.texture_transforms[slot];
}
}
let defaults = Self::default();
if value.base_color_factor != defaults.base_color_factor {
self.base_color_factor = value.base_color_factor;
}
if value.emissive_factor != defaults.emissive_factor {
self.emissive_factor = value.emissive_factor;
}
if value.metallic_factor != defaults.metallic_factor {
self.metallic_factor = value.metallic_factor;
}
if value.roughness_factor != defaults.roughness_factor {
self.roughness_factor = value.roughness_factor;
}
if value.alpha_cutoff != defaults.alpha_cutoff {
self.alpha_cutoff = value.alpha_cutoff;
}
if value.override_alpha_mode {
self.alpha_mode = value.alpha_mode;
}
if value.override_double_sided {
self.double_sided = value.double_sided;
}
Ok(())
}
fn alpha_name(mode: GltfAlphaMode) -> &'static str {
match mode {
GltfAlphaMode::Opaque => "OPAQUE",
GltfAlphaMode::Blend => "BLEND",
GltfAlphaMode::Mask => "MASK",
}
}
fn parse_alpha(value: Option<&str>) -> Result<GltfAlphaMode, Error> {
match value.unwrap_or("OPAQUE") {
"OPAQUE" => Ok(GltfAlphaMode::Opaque),
"BLEND" => Ok(GltfAlphaMode::Blend),
"MASK" => Ok(GltfAlphaMode::Mask),
_ => Err(Error::Argument),
}
}
fn texture_json(
&self,
slot: usize,
images: &mut Vec<Value>,
textures: &mut Vec<Value>,
) -> Option<Value> {
let id = self.texture_ids[slot];
if id == UUID::zero() || id == Self::gltf_override_null_uuid() {
return None;
}
let index = images.len();
images.push(json!({"uri": id.to_string()}));
textures.push(json!({"source": index}));
let mut info = json!({"index": index});
let transform = self.texture_transforms[slot];
if !transform.is_default() {
info["extensions"] = json!({"KHR_texture_transform": {"offset": [transform.offset.x, transform.offset.y], "scale": [transform.scale.x, transform.scale.y], "rotation": transform.rotation}});
}
Some(info)
}
pub fn to_json(&self) -> Result<String, Error> {
let mut images = Vec::new();
let mut textures = Vec::new();
let base = self.texture_json(
Self::TEXTURE_BASE_COLOR as usize,
&mut images,
&mut textures,
);
let normal = self.texture_json(Self::TEXTURE_NORMAL as usize, &mut images, &mut textures);
let metallic = self.texture_json(
Self::TEXTURE_METALLIC_ROUGHNESS as usize,
&mut images,
&mut textures,
);
let emissive =
self.texture_json(Self::TEXTURE_EMISSIVE as usize, &mut images, &mut textures);
let mut pbr = json!({"baseColorFactor": [self.base_color_factor.r,self.base_color_factor.g,self.base_color_factor.b,self.base_color_factor.a], "metallicFactor": self.metallic_factor, "roughnessFactor": self.roughness_factor});
if let Some(value) = base {
pbr["baseColorTexture"] = value;
}
if let Some(value) = metallic {
pbr["metallicRoughnessTexture"] = value;
}
let mut material = json!({"name": self.name, "pbrMetallicRoughness": pbr, "emissiveFactor": [self.emissive_factor.x,self.emissive_factor.y,self.emissive_factor.z], "alphaMode": Self::alpha_name(self.alpha_mode), "alphaCutoff": self.alpha_cutoff, "doubleSided": self.double_sided});
if let Some(value) = normal {
material["normalTexture"] = value;
}
if let Some(value) = emissive {
material["emissiveTexture"] = value;
}
let mut root = json!({"asset":{"version":"2.0"},"materials":[material]});
if !images.is_empty() {
root["images"] = Value::Array(images);
root["textures"] = Value::Array(textures);
root["extensionsUsed"] = json!(["KHR_texture_transform"]);
}
serde_json::to_string(&root).map_err(|_| Error::InvalidOperation)
}
pub fn encode(&mut self) -> Result<(), Error> {
self.asset_data = self.to_json()?.into_bytes();
Ok(())
}
pub fn decode(&mut self) -> Result<bool, Error> {
let root: Value = serde_json::from_slice(&self.asset_data).map_err(|_| Error::Argument)?;
let mat = root
.get("materials")
.and_then(Value::as_array)
.and_then(|v| v.first())
.and_then(Value::as_object)
.ok_or(Error::Argument)?;
self.name = mat
.get("name")
.and_then(Value::as_str)
.unwrap_or_default()
.to_owned();
let pbr = mat.get("pbrMetallicRoughness").and_then(Value::as_object);
if let Some(values) = pbr
.and_then(|v| v.get("baseColorFactor"))
.and_then(Value::as_array)
{
if values.len() == 4 {
self.base_color_factor = Color4 {
r: number(&values[0])?,
g: number(&values[1])?,
b: number(&values[2])?,
a: number(&values[3])?,
};
}
}
self.metallic_factor = pbr
.and_then(|v| v.get("metallicFactor"))
.map(number)
.transpose()?
.unwrap_or(1.0);
self.roughness_factor = pbr
.and_then(|v| v.get("roughnessFactor"))
.map(number)
.transpose()?
.unwrap_or(1.0);
if let Some(values) = mat.get("emissiveFactor").and_then(Value::as_array) {
if values.len() == 3 {
self.emissive_factor = Vector3 {
x: number(&values[0])?,
y: number(&values[1])?,
z: number(&values[2])?,
};
}
}
self.alpha_mode = Self::parse_alpha(mat.get("alphaMode").and_then(Value::as_str))?;
self.alpha_cutoff = mat
.get("alphaCutoff")
.map(number)
.transpose()?
.unwrap_or(0.5);
self.double_sided = mat
.get("doubleSided")
.and_then(Value::as_bool)
.unwrap_or(false);
let textures = root.get("textures").and_then(Value::as_array);
let images = root.get("images").and_then(Value::as_array);
for (slot, info) in [
(0, pbr.and_then(|v| v.get("baseColorTexture"))),
(1, mat.get("normalTexture")),
(2, pbr.and_then(|v| v.get("metallicRoughnessTexture"))),
(3, mat.get("emissiveTexture")),
] {
if let Some(info) = info {
self.decode_texture(slot, info, textures, images)?;
}
}
Ok(true)
}
fn decode_texture(
&mut self,
slot: usize,
info: &Value,
textures: Option<&Vec<Value>>,
images: Option<&Vec<Value>>,
) -> Result<(), Error> {
let index = info
.get("index")
.and_then(Value::as_u64)
.and_then(|v| usize::try_from(v).ok())
.ok_or(Error::Argument)?;
let source = textures
.and_then(|v| v.get(index))
.and_then(|v| v.get("source"))
.and_then(Value::as_u64)
.and_then(|v| usize::try_from(v).ok())
.ok_or(Error::Argument)?;
let uri = images
.and_then(|v| v.get(source))
.and_then(|v| v.get("uri"))
.and_then(Value::as_str)
.ok_or(Error::Argument)?;
self.texture_ids[slot] = UUID::new_with_string(uri.to_owned())?;
if let Some(transform) = info.pointer("/extensions/KHR_texture_transform") {
if let Some(v) = transform.get("offset").and_then(Value::as_array) {
if v.len() == 2 {
self.texture_transforms[slot].offset = Vector2 {
x: number(&v[0])?,
y: number(&v[1])?,
};
}
}
if let Some(v) = transform.get("scale").and_then(Value::as_array) {
if v.len() == 2 {
self.texture_transforms[slot].scale = Vector2 {
x: number(&v[0])?,
y: number(&v[1])?,
};
}
}
if let Some(v) = transform.get("rotation") {
self.texture_transforms[slot].rotation = number(v)?;
}
}
Ok(())
}
pub fn to_override_osd(&self) -> Result<OSDMap, Error> {
let defaults = Self::default();
let mut map = HashMap::new();
let mut tex = Vec::new();
let mut last = None;
for (index, id) in self.texture_ids.iter().enumerate() {
if *id != UUID::zero() {
last = Some(index);
}
}
if let Some(last) = last {
for id in &self.texture_ids[..=last] {
tex.push(OSD::UUID(*id));
}
map.insert("tex".into(), OSD::Array(tex));
}
if self.base_color_factor != defaults.base_color_factor {
map.insert("bc".into(), color_osd(self.base_color_factor));
}
if self.emissive_factor != defaults.emissive_factor {
map.insert("ec".into(), vector3_osd(self.emissive_factor));
}
if self.metallic_factor != defaults.metallic_factor {
map.insert("mf".into(), OSD::Real(f64::from(self.metallic_factor)));
}
if self.roughness_factor != defaults.roughness_factor {
map.insert("rf".into(), OSD::Real(f64::from(self.roughness_factor)));
}
if self.override_alpha_mode {
map.insert("am".into(), OSD::Integer(self.alpha_mode as i32));
}
if self.alpha_cutoff != defaults.alpha_cutoff {
map.insert("ac".into(), OSD::Real(f64::from(self.alpha_cutoff)));
}
if self.override_double_sided {
map.insert("ds".into(), OSD::Boolean(self.double_sided));
}
let transforms: Vec<_> = self
.texture_transforms
.iter()
.map(|t| {
OSD::Map(HashMap::from([
("o".into(), vector2_osd(t.offset)),
("s".into(), vector2_osd(t.scale)),
("r".into(), OSD::Real(f64::from(t.rotation))),
]))
})
.collect();
if self.texture_transforms.iter().any(|v| !v.is_default()) {
map.insert("ti".into(), OSD::Array(transforms));
}
OSDMap::new_with_dictionary(map)
}
pub fn from_override_osd(data: OSDMap) -> Result<Self, Error> {
let map = data.snapshot();
let mut value = Self::default();
if let Some(OSD::Array(ids)) = map.get("tex") {
for (slot, id) in ids.iter().take(Self::TEXTURE_COUNT as usize).enumerate() {
value.texture_ids[slot] = id.as_uuid()?;
}
}
if let Some(v) = map.get("bc") {
value.base_color_factor = v.as_color4()?;
if value.base_color_factor == Self::default().base_color_factor {
value.base_color_factor.a -= FLOAT_EPSILON;
}
}
if let Some(v) = map.get("ec") {
value.emissive_factor = v.as_vector3()?;
if value.emissive_factor == Vector3::zero() {
value.emissive_factor.x += FLOAT_EPSILON;
}
}
if let Some(v) = map.get("mf") {
value.metallic_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON);
}
if let Some(v) = map.get("rf") {
value.roughness_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON);
}
if let Some(v) = map.get("am") {
value.alpha_mode = match v.as_integer()? {
0 => GltfAlphaMode::Opaque,
1 => GltfAlphaMode::Blend,
2 => GltfAlphaMode::Mask,
_ => return Err(Error::Argument),
};
value.override_alpha_mode = true;
}
if let Some(v) = map.get("ac") {
value.alpha_cutoff = v.as_real()? as f32;
if value.alpha_cutoff == 0.5 {
value.alpha_cutoff -= FLOAT_EPSILON;
}
}
if let Some(v) = map.get("ds") {
value.double_sided = v.as_boolean()?;
value.override_double_sided = true;
}
if let Some(OSD::Array(values)) = map.get("ti") {
for (slot, transform) in values.iter().take(Self::TEXTURE_COUNT as usize).enumerate() {
if let OSD::Map(t) = transform {
if let Some(v) = t.get("o") {
value.texture_transforms[slot].offset = v.as_vector2()?;
}
if let Some(v) = t.get("s") {
value.texture_transforms[slot].scale = v.as_vector2()?;
}
if let Some(v) = t.get("r") {
value.texture_transforms[slot].rotation = v.as_real()? as f32;
}
}
}
}
Ok(value)
}
pub const fn asset_type(&self) -> AssetType {
AssetType::Material
}
pub fn alpha_cutoff(&self) -> f32 {
self.alpha_cutoff
}
pub fn set_alpha_cutoff(&mut self, value: f32) {
self.alpha_cutoff = value;
}
pub fn alpha_mode(&self) -> GltfAlphaMode {
self.alpha_mode
}
pub fn set_alpha_mode(&mut self, value: GltfAlphaMode) {
self.alpha_mode = value;
}
pub fn base_color_factor(&self) -> Color4 {
self.base_color_factor
}
pub fn set_base_color_factor(&mut self, value: Color4) {
self.base_color_factor = value;
}
pub fn double_sided(&self) -> bool {
self.double_sided
}
pub fn set_double_sided(&mut self, value: bool) {
self.double_sided = value;
}
pub fn emissive_factor(&self) -> Vector3 {
self.emissive_factor
}
pub fn set_emissive_factor(&mut self, value: Vector3) {
self.emissive_factor = value;
}
pub fn metallic_factor(&self) -> f32 {
self.metallic_factor
}
pub fn set_metallic_factor(&mut self, value: f32) {
self.metallic_factor = value;
}
pub fn roughness_factor(&self) -> f32 {
self.roughness_factor
}
pub fn set_roughness_factor(&mut self, value: f32) {
self.roughness_factor = value;
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn set_name(&mut self, value: String) {
self.name = value;
}
pub fn override_alpha_mode(&self) -> bool {
self.override_alpha_mode
}
pub fn set_override_alpha_mode(&mut self, value: bool) {
self.override_alpha_mode = value;
}
pub fn override_double_sided(&self) -> bool {
self.override_double_sided
}
pub fn set_override_double_sided(&mut self, value: bool) {
self.override_double_sided = value;
}
pub fn texture_ids(&self) -> Vec<UUID> {
self.texture_ids.clone()
}
pub fn set_texture_ids(&mut self, value: Vec<UUID>) {
self.texture_ids = value;
}
pub fn texture_transforms(&self) -> Vec<GltfTextureTransform> {
self.texture_transforms.clone()
}
pub fn set_texture_transforms(&mut self, value: Vec<GltfTextureTransform>) {
self.texture_transforms = value;
}
}
fn number(value: &Value) -> Result<f32, Error> {
value
.as_f64()
.map(|v| v as f32)
.filter(|v| v.is_finite())
.ok_or(Error::Argument)
}
fn vector2_osd(value: Vector2) -> OSD {
OSD::Array(vec![
OSD::Real(f64::from(value.x)),
OSD::Real(f64::from(value.y)),
])
}
fn vector3_osd(value: Vector3) -> OSD {
OSD::Array(vec![
OSD::Real(f64::from(value.x)),
OSD::Real(f64::from(value.y)),
OSD::Real(f64::from(value.z)),
])
}
fn color_osd(value: Color4) -> OSD {
OSD::Array(vec![
OSD::Real(f64::from(value.r)),
OSD::Real(f64::from(value.g)),
OSD::Real(f64::from(value.b)),
OSD::Real(f64::from(value.a)),
])
}