mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
Adding the first version of the glTF loader.
This commit is contained in:
@@ -28,7 +28,7 @@ use fyrox_core::uuid::uuid;
|
||||
|
||||
pub use pose::{AnimationPose, NodePose};
|
||||
pub use signal::{AnimationEvent, AnimationSignal};
|
||||
use value::{TrackValue, ValueBinding};
|
||||
use value::{nlerp, TrackValue, ValueBinding};
|
||||
|
||||
pub mod container;
|
||||
pub mod machine;
|
||||
@@ -243,7 +243,7 @@ impl RootMotion {
|
||||
/// Blend this motion with some other using `weight` as a proportion.
|
||||
pub fn blend_with(&mut self, other: &RootMotion, weight: f32) {
|
||||
self.delta_position = self.delta_position.lerp(&other.delta_position, weight);
|
||||
self.delta_rotation = self.delta_rotation.nlerp(&other.delta_rotation, weight);
|
||||
self.delta_rotation = nlerp(self.delta_rotation, &other.delta_rotation, weight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! for more info.
|
||||
|
||||
use crate::core::{
|
||||
algebra::{UnitQuaternion, Vector2, Vector3, Vector4},
|
||||
algebra::{Unit, UnitQuaternion, Vector2, Vector3, Vector4},
|
||||
math::lerpf,
|
||||
num_traits::AsPrimitive,
|
||||
reflect::prelude::*,
|
||||
@@ -148,7 +148,7 @@ impl TrackValue {
|
||||
(Self::Vector2(a), Self::Vector2(b)) => *a = a.lerp(b, weight),
|
||||
(Self::Vector3(a), Self::Vector3(b)) => *a = a.lerp(b, weight),
|
||||
(Self::Vector4(a), Self::Vector4(b)) => *a = a.lerp(b, weight),
|
||||
(Self::UnitQuaternion(a), Self::UnitQuaternion(b)) => *a = a.nlerp(b, weight),
|
||||
(Self::UnitQuaternion(a), Self::UnitQuaternion(b)) => *a = nlerp(*a, &b, weight),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -357,3 +357,12 @@ impl BoundValueCollection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpolates from `a` to `b` using nlerp, including an additional check to ensure
|
||||
/// that the a.dot(b) is positive to prevent the interpolation from going around the long way.
|
||||
pub fn nlerp(mut a: UnitQuaternion<f32>, b: &UnitQuaternion<f32>, w: f32) -> UnitQuaternion<f32> {
|
||||
if a.dot(b) < 0.0 {
|
||||
a = Unit::new_unchecked(-a.as_ref());
|
||||
}
|
||||
a.nlerp(b, w)
|
||||
}
|
||||
|
||||
@@ -6,5 +6,10 @@ edition = "2021"
|
||||
[lib]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[features]
|
||||
gltf = ["fyrox-impl/gltf"]
|
||||
mesh_analysis = ["fyrox-impl/mesh_analysis"]
|
||||
gltf_blend_shapes = ["fyrox-impl/gltf_blend_shapes"]
|
||||
|
||||
[dependencies]
|
||||
fyrox-impl = { path = "../fyrox-impl", version = "0.33.1" }
|
||||
@@ -46,6 +46,8 @@ base64 = "0.21.0"
|
||||
uvgen = "0.1.0"
|
||||
lightmap = "0.1.1"
|
||||
libloading = "0.8.1"
|
||||
serde_json = { version="1.0", optional=true }
|
||||
gltf = { version = "1.4.0", optional = true, default-features = false, features = ["names", "utils"] }
|
||||
|
||||
# This dependency isn't actually used by the engine, but it is needed to prevent cargo from rebuilding
|
||||
# the engine lib on different packages.
|
||||
@@ -53,6 +55,8 @@ hashbrown = { version = "0.14.3", features = ["raw"] }
|
||||
|
||||
[features]
|
||||
enable_profiler = ["fyrox-core/enable_profiler"]
|
||||
gltf_blend_shapes = ["gltf", "gltf/extras", "dep:serde_json"]
|
||||
mesh_analysis = []
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
glutin = "0.31"
|
||||
|
||||
@@ -1148,6 +1148,15 @@ pub(crate) fn initialize_resource_manager_loaders(
|
||||
|
||||
let mut state = resource_manager.state();
|
||||
|
||||
#[cfg(feature = "gltf")]
|
||||
{
|
||||
let gltf_loader = super::resource::gltf::GltfLoader {
|
||||
resource_manager: resource_manager.clone(),
|
||||
default_import_options: Default::default(),
|
||||
};
|
||||
state.loaders.set(gltf_loader);
|
||||
}
|
||||
|
||||
for shader in ShaderResource::standard_shaders() {
|
||||
state
|
||||
.built_in_resources
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
use crate::core::algebra::{Quaternion, Unit, UnitQuaternion, Vector3};
|
||||
use crate::core::log::Log;
|
||||
use crate::core::math::curve::{Curve, CurveKey, CurveKeyKind};
|
||||
use crate::core::pool::Handle;
|
||||
use crate::generic_animation::container::{TrackDataContainer, TrackValueKind};
|
||||
use crate::generic_animation::track::Track;
|
||||
use crate::generic_animation::value::{ValueBinding, ValueType};
|
||||
use crate::scene::animation::Animation;
|
||||
use crate::scene::node::Node;
|
||||
use gltf::animation::util::ReadOutputs;
|
||||
use gltf::animation::Channel;
|
||||
use gltf::animation::{Interpolation, Property};
|
||||
use gltf::Buffer;
|
||||
|
||||
type Result<T> = std::result::Result<T, ()>;
|
||||
|
||||
use super::iter::*;
|
||||
|
||||
/// Extract a list of Animations from the give glTF document, if that document contains any.
|
||||
/// The resulting list of animations is not guaranteed to be the same length as the list of animations
|
||||
/// in the document. If any error is encountered in translating an animation from glTF to Fyrox,
|
||||
/// that animation will be excluded from the resulting list and an error message will be logged.
|
||||
///
|
||||
/// * `doc`: The document in which to find the animations.
|
||||
///
|
||||
/// * `node_handles`: A slice containing a [Handle] for every node defined in the document, in that order, so that
|
||||
/// a handle can be looked up using the index of a node within the document. Animations in glTF specify their target
|
||||
/// nodes by their index within the node list of the document, and these indices need to be translated into handles.
|
||||
///
|
||||
/// * `buffers`: A slice containing a list of byte-vectors, one for each buffer in the glTF document.
|
||||
/// Animations in glTF make reference to data stored in the document's list of buffers by index.
|
||||
/// This slcie allows an index into the document's list of buffers to be translated into actual bytes of data.
|
||||
pub fn import_animations(
|
||||
doc: &gltf::Document,
|
||||
node_handles: &[Handle<Node>],
|
||||
buffers: &[Vec<u8>],
|
||||
) -> Vec<Animation> {
|
||||
let mut result: Vec<Animation> = Vec::with_capacity(doc.animations().len());
|
||||
for animation in doc.animations() {
|
||||
if let Ok(animation) = import_animation(&animation, node_handles, buffers) {
|
||||
result.push(animation);
|
||||
} else {
|
||||
Log::err(format!(
|
||||
"Failed to import animation: {}",
|
||||
animation.name().unwrap_or("[Unnamed]")
|
||||
));
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn import_animation(
|
||||
animation: &gltf::Animation,
|
||||
node_handles: &[Handle<Node>],
|
||||
buffers: &[Vec<u8>],
|
||||
) -> Result<Animation> {
|
||||
let mut result = Animation::default();
|
||||
let name = animation
|
||||
.name()
|
||||
.map(str::to_owned)
|
||||
.unwrap_or(default_animation_name(animation));
|
||||
//Log::info(format!("ANIMATION: {}", name));
|
||||
result.set_name(name);
|
||||
import_channels(animation, &mut result, node_handles, buffers)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn default_animation_name(animation: &gltf::Animation) -> String {
|
||||
format!("Animation {}", animation.index())
|
||||
}
|
||||
|
||||
fn import_channels(
|
||||
source_animation: &gltf::Animation,
|
||||
build_animation: &mut Animation,
|
||||
node_handles: &[Handle<Node>],
|
||||
buffers: &[Vec<u8>],
|
||||
) -> Result<()> {
|
||||
let mut end_time: f32 = 0.0;
|
||||
for channel in source_animation.channels() {
|
||||
let channel_end = find_end_time_for_channel(&channel, |buf: Buffer| {
|
||||
buffers.get(buf.index()).map(Vec::as_slice)
|
||||
})?;
|
||||
if end_time < channel_end {
|
||||
end_time = channel_end;
|
||||
}
|
||||
let target_index = channel.target().node().index();
|
||||
let target: Handle<Node> = node_handles.get(target_index).ok_or(())?.clone();
|
||||
if let Property::MorphTargetWeights = channel.target().property() {
|
||||
import_weight_channel(&channel, target, build_animation, |buf: Buffer| {
|
||||
buffers.get(buf.index()).map(Vec::as_slice)
|
||||
})?;
|
||||
} else {
|
||||
import_transform_channel(&channel, target, build_animation, |buf: Buffer| {
|
||||
buffers.get(buf.index()).map(Vec::as_slice)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
build_animation.set_time_slice(0.0..end_time);
|
||||
build_animation.set_enabled(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_transform_channel<'a, 's, F>(
|
||||
channel: &'a Channel,
|
||||
target: Handle<Node>,
|
||||
build_animation: &mut Animation,
|
||||
get_buffer_data: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let prop = match channel.target().property() {
|
||||
Property::Translation => ValueBinding::Position,
|
||||
Property::Rotation => ValueBinding::Rotation,
|
||||
Property::Scale => ValueBinding::Scale,
|
||||
Property::MorphTargetWeights => return Err(()),
|
||||
};
|
||||
let sampler = import_sampler(&channel, get_buffer_data)?;
|
||||
let mut track = Track::new(sampler, prop);
|
||||
track.set_target(target);
|
||||
|
||||
//print_track(&track);
|
||||
|
||||
build_animation.add_track(track);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn print_track(track: &Track<Handle<Node>>) {
|
||||
let data = track.data_container();
|
||||
println!("Track: {:?}", data.value_kind());
|
||||
for (i, curve) in data.curves_ref().iter().enumerate() {
|
||||
println!("Curve: {}", i);
|
||||
for key in curve.keys() {
|
||||
println!("{:4.2}: {}: {:?}", key.location(), key.value, key.kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn import_weight_channel<'a, 's, F>(
|
||||
channel: &'a Channel,
|
||||
target: Handle<Node>,
|
||||
build_animation: &mut Animation,
|
||||
get_buffer_data: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let target_count = count_morph_targets(&channel.target().node());
|
||||
for i in 0..target_count {
|
||||
let prop = ValueBinding::Property {
|
||||
name: format!("blend_shapes[{}].weight", i),
|
||||
value_type: ValueType::F32,
|
||||
};
|
||||
let sampler = match channel.sampler().interpolation() {
|
||||
Interpolation::Linear => import_simple_morph_sampler(
|
||||
channel,
|
||||
i,
|
||||
target_count,
|
||||
CurveKeyKind::Linear,
|
||||
get_buffer_data.clone(),
|
||||
)?,
|
||||
Interpolation::Step => import_simple_morph_sampler(
|
||||
channel,
|
||||
i,
|
||||
target_count,
|
||||
CurveKeyKind::Constant,
|
||||
get_buffer_data.clone(),
|
||||
)?,
|
||||
Interpolation::CubicSpline => {
|
||||
import_cubic_morph_sampler(channel, i, target_count, get_buffer_data.clone())?
|
||||
}
|
||||
};
|
||||
let mut track = Track::new(sampler, prop);
|
||||
track.set_target(target);
|
||||
build_animation.add_track(track);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn count_morph_targets(target: &gltf::Node) -> usize {
|
||||
if let Some(mesh) = target.mesh() {
|
||||
if let Some(prim) = mesh.primitives().next() {
|
||||
prim.morph_targets().count()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn array_to_euler(quaternion: [f32; 4]) -> Vector3<f32> {
|
||||
quaternion_to_euler(Unit::new_normalize(Quaternion::from(quaternion)))
|
||||
}
|
||||
|
||||
// X - pitch, Y - yaw, Z - roll
|
||||
// Or maybe X - roll, Y - yaw, Z - pitch
|
||||
// Or maybe X - roll, Y - pitch, Z - yaw
|
||||
fn quaternion_to_euler(q: UnitQuaternion<f32>) -> Vector3<f32> {
|
||||
let (roll, pitch, yaw) = q.euler_angles();
|
||||
//let (x, y, z) = (pitch, yaw, roll);
|
||||
let (x, y, z) = (roll, pitch, yaw);
|
||||
Vector3::new(x, y, z)
|
||||
}
|
||||
|
||||
fn quaternion_to_euler_tangents(
|
||||
in_tangent: [f32; 4],
|
||||
value: [f32; 4],
|
||||
out_tangent: [f32; 4],
|
||||
) -> (Vector3<f32>, Vector3<f32>) {
|
||||
let a: UnitQuaternion<f32> = Unit::new_normalize(quaternion_subtract(value, in_tangent).into());
|
||||
let v: UnitQuaternion<f32> = Unit::new_normalize(value.into());
|
||||
let b: UnitQuaternion<f32> = Unit::new_normalize(quaternion_add(value, out_tangent).into());
|
||||
(
|
||||
quaternion_to_euler(a.rotation_to(&v).into()),
|
||||
quaternion_to_euler(v.rotation_to(&b).into()),
|
||||
)
|
||||
}
|
||||
|
||||
fn quaternion_add(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
|
||||
let mut r = [0f32; 4];
|
||||
for i in 0..4 {
|
||||
r[i] = a[i] + b[i];
|
||||
}
|
||||
r
|
||||
}
|
||||
fn quaternion_subtract(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
|
||||
let mut r = [0f32; 4];
|
||||
for i in 0..4 {
|
||||
r[i] = a[i] - b[i];
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fn find_end_time_for_channel<'a, 's, F>(channel: &'a Channel, get_buffer_data: F) -> Result<f32>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel.reader(get_buffer_data).read_inputs().ok_or(())?;
|
||||
Ok(inputs.last().unwrap_or(0.0f32))
|
||||
}
|
||||
|
||||
fn import_sampler<'a, 's, F>(channel: &'a Channel, get_buffer_data: F) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
if let Property::Rotation = channel.target().property() {
|
||||
match channel.sampler().interpolation() {
|
||||
Interpolation::Linear => {
|
||||
import_simple_rotation(channel, CurveKeyKind::Linear, get_buffer_data)
|
||||
}
|
||||
Interpolation::Step => {
|
||||
import_simple_rotation(channel, CurveKeyKind::Constant, get_buffer_data)
|
||||
}
|
||||
Interpolation::CubicSpline => import_cubic_rotation(channel, get_buffer_data),
|
||||
}
|
||||
} else {
|
||||
match channel.sampler().interpolation() {
|
||||
Interpolation::Linear => {
|
||||
import_simple_sampler(channel, CurveKeyKind::Linear, get_buffer_data)
|
||||
}
|
||||
Interpolation::Step => {
|
||||
import_simple_sampler(channel, CurveKeyKind::Constant, get_buffer_data)
|
||||
}
|
||||
Interpolation::CubicSpline => import_cubic_sampler(channel, get_buffer_data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn import_simple_sampler<'a, 's, F>(
|
||||
channel: &'a gltf::animation::Channel,
|
||||
kind: CurveKeyKind,
|
||||
get_buffer_data: F,
|
||||
) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel
|
||||
.reader(get_buffer_data.clone())
|
||||
.read_inputs()
|
||||
.ok_or(())?;
|
||||
let outputs = channel.reader(get_buffer_data).read_outputs().ok_or(())?;
|
||||
let out_iter = match outputs {
|
||||
ReadOutputs::Translations(iter) => iter,
|
||||
ReadOutputs::Scales(iter) => iter,
|
||||
ReadOutputs::Rotations(_) => return Err(()),
|
||||
ReadOutputs::MorphTargetWeights(_) => return Err(()),
|
||||
};
|
||||
let mut frames_container = TrackDataContainer::new(TrackValueKind::Vector3);
|
||||
for i in 0..3 {
|
||||
let curve_keys: Curve = inputs
|
||||
.clone()
|
||||
.zip(out_iter.clone())
|
||||
.map(|(time, o)| CurveKey::new(time, o[i], kind.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
frames_container.curves_mut()[i] = curve_keys;
|
||||
}
|
||||
Ok(frames_container)
|
||||
}
|
||||
|
||||
fn import_simple_morph_sampler<'a, 's, F>(
|
||||
channel: &'a gltf::animation::Channel,
|
||||
morph_index: usize,
|
||||
morph_count: usize,
|
||||
kind: CurveKeyKind,
|
||||
get_buffer_data: F,
|
||||
) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel
|
||||
.reader(get_buffer_data.clone())
|
||||
.read_inputs()
|
||||
.ok_or(())?;
|
||||
let outputs = channel.reader(get_buffer_data).read_outputs().ok_or(())?;
|
||||
let out_iter = match outputs {
|
||||
ReadOutputs::MorphTargetWeights(weights) => weights.into_f32(),
|
||||
_ => return Err(()),
|
||||
};
|
||||
let out_iter = select_iterator(out_iter, morph_index, morph_count);
|
||||
let mut frames_container = TrackDataContainer::new(TrackValueKind::Real);
|
||||
let curve_keys: Curve = inputs
|
||||
.zip(out_iter)
|
||||
.map(|(time, o)| CurveKey::new(time, o * 100.0, kind.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
frames_container.curves_mut()[0] = curve_keys;
|
||||
Ok(frames_container)
|
||||
}
|
||||
|
||||
fn import_cubic_sampler<'a, 's, F>(
|
||||
channel: &'a gltf::animation::Channel,
|
||||
get_buffer_data: F,
|
||||
) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel
|
||||
.reader(get_buffer_data.clone())
|
||||
.read_inputs()
|
||||
.ok_or(())?;
|
||||
let outputs = channel.reader(get_buffer_data).read_outputs().ok_or(())?;
|
||||
let out_iter = match outputs {
|
||||
ReadOutputs::Translations(iter) => iter,
|
||||
ReadOutputs::Scales(iter) => iter,
|
||||
ReadOutputs::Rotations(_) => return Err(()),
|
||||
ReadOutputs::MorphTargetWeights(_) => return Err(()),
|
||||
};
|
||||
let mut frames_container = TrackDataContainer::new(TrackValueKind::Vector3);
|
||||
for i in 0..3 {
|
||||
let curve_keys: Curve = iter_cubic_data(inputs.clone(), out_iter.clone())
|
||||
.map(|(time, o)| {
|
||||
CurveKey::new(
|
||||
time,
|
||||
o[1][i],
|
||||
CurveKeyKind::Cubic {
|
||||
left_tangent: o[0][i],
|
||||
right_tangent: o[2][i],
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
frames_container.curves_mut()[i] = curve_keys;
|
||||
}
|
||||
Ok(frames_container)
|
||||
}
|
||||
|
||||
fn import_cubic_morph_sampler<'a, 's, F>(
|
||||
channel: &'a gltf::animation::Channel,
|
||||
morph_index: usize,
|
||||
morph_count: usize,
|
||||
get_buffer_data: F,
|
||||
) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel
|
||||
.reader(get_buffer_data.clone())
|
||||
.read_inputs()
|
||||
.ok_or(())?;
|
||||
let outputs = channel.reader(get_buffer_data).read_outputs().ok_or(())?;
|
||||
let out_iter = match outputs {
|
||||
ReadOutputs::MorphTargetWeights(weights) => weights.into_f32(),
|
||||
_ => return Err(()),
|
||||
};
|
||||
let out_iter = select_iterator(out_iter, morph_index, morph_count);
|
||||
let iter = iter_cubic_data(inputs, out_iter);
|
||||
let mut frames_container = TrackDataContainer::new(TrackValueKind::Real);
|
||||
let curve_keys: Curve = iter
|
||||
.map(|(time, o)| {
|
||||
CurveKey::new(
|
||||
time,
|
||||
o[1] * 100.0,
|
||||
CurveKeyKind::Cubic {
|
||||
left_tangent: o[0] * 100.0,
|
||||
right_tangent: o[2] * 100.0,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
frames_container.curves_mut()[0] = curve_keys;
|
||||
Ok(frames_container)
|
||||
}
|
||||
|
||||
fn import_simple_rotation<'a, 's, F>(
|
||||
channel: &'a gltf::animation::Channel,
|
||||
kind: CurveKeyKind,
|
||||
get_buffer_data: F,
|
||||
) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel
|
||||
.reader(get_buffer_data.clone())
|
||||
.read_inputs()
|
||||
.ok_or(())?;
|
||||
let outputs = channel.reader(get_buffer_data).read_outputs().ok_or(())?;
|
||||
let out_iter = match outputs {
|
||||
ReadOutputs::Translations(_) => return Err(()),
|
||||
ReadOutputs::Scales(_) => return Err(()),
|
||||
ReadOutputs::Rotations(iter) => iter.into_f32(),
|
||||
ReadOutputs::MorphTargetWeights(_) => return Err(()),
|
||||
};
|
||||
let mut frames_container = TrackDataContainer::new(TrackValueKind::UnitQuaternion);
|
||||
for i in 0..3 {
|
||||
let curve_keys: Curve = inputs
|
||||
.clone()
|
||||
.zip(out_iter.clone())
|
||||
.map(|(time, o)| CurveKey::new(time, array_to_euler(o)[i], kind.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
frames_container.curves_mut()[i] = curve_keys;
|
||||
}
|
||||
Ok(frames_container)
|
||||
}
|
||||
|
||||
fn import_cubic_rotation<'a, 's, F>(
|
||||
channel: &'a gltf::animation::Channel,
|
||||
get_buffer_data: F,
|
||||
) -> Result<TrackDataContainer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let inputs = channel
|
||||
.reader(get_buffer_data.clone())
|
||||
.read_inputs()
|
||||
.ok_or(())?;
|
||||
let outputs = channel.reader(get_buffer_data).read_outputs().ok_or(())?;
|
||||
let out_iter = match outputs {
|
||||
ReadOutputs::Translations(_) => return Err(()),
|
||||
ReadOutputs::Scales(_) => return Err(()),
|
||||
ReadOutputs::Rotations(iter) => iter.into_f32(),
|
||||
ReadOutputs::MorphTargetWeights(_) => return Err(()),
|
||||
};
|
||||
let mut frames_container = TrackDataContainer::new(TrackValueKind::UnitQuaternion);
|
||||
for i in 0..3 {
|
||||
let curve_keys: Curve = iter_cubic_data(inputs.clone(), out_iter.clone())
|
||||
.map(|(time, o)| {
|
||||
let (in_tang, out_tang) = quaternion_to_euler_tangents(o[0], o[1], o[2]);
|
||||
let value = array_to_euler(o[1]);
|
||||
CurveKey::new(
|
||||
time,
|
||||
value[i],
|
||||
CurveKeyKind::Cubic {
|
||||
left_tangent: in_tang[i],
|
||||
right_tangent: out_tang[i],
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
frames_container.curves_mut()[i] = curve_keys;
|
||||
}
|
||||
Ok(frames_container)
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
(
|
||||
name: "GLTFShader",
|
||||
|
||||
// Each property's name must match respective uniform name.
|
||||
properties: [
|
||||
(
|
||||
name: "diffuseTexture",
|
||||
kind: Sampler(default: None, fallback: White),
|
||||
),
|
||||
(
|
||||
name: "normalTexture",
|
||||
kind: Sampler(default: None, fallback: Normal),
|
||||
),
|
||||
(
|
||||
name: "metallicRoughnessTexture",
|
||||
kind: Sampler(default: None, fallback: White),
|
||||
),
|
||||
(
|
||||
name: "heightTexture",
|
||||
kind: Sampler(default: None, fallback: Black),
|
||||
),
|
||||
(
|
||||
name: "emissionTexture",
|
||||
kind: Sampler(default: None, fallback: Black),
|
||||
),
|
||||
(
|
||||
name: "lightmapTexture",
|
||||
kind: Sampler(default: None, fallback: Black),
|
||||
),
|
||||
(
|
||||
name: "aoTexture",
|
||||
kind: Sampler(default: None, fallback: White),
|
||||
),
|
||||
(
|
||||
name: "texCoordScale",
|
||||
kind: Vector2((1.0, 1.0)),
|
||||
),
|
||||
(
|
||||
name: "layerIndex",
|
||||
kind: UInt(0),
|
||||
),
|
||||
(
|
||||
name: "emissionStrength",
|
||||
kind: Vector3((2.0, 2.0, 2.0)),
|
||||
),
|
||||
(
|
||||
name: "diffuseColor",
|
||||
kind: Color(r: 255, g: 255, b: 255, a: 255),
|
||||
),
|
||||
(
|
||||
name: "metallicFactor",
|
||||
kind: Float(0.0),
|
||||
),
|
||||
(
|
||||
name: "roughnessFactor",
|
||||
kind: Float(0.0),
|
||||
),
|
||||
(
|
||||
name: "parallaxCenter",
|
||||
kind: Float(0.0),
|
||||
),
|
||||
(
|
||||
name: "parallaxScale",
|
||||
kind: Float(0.08),
|
||||
),
|
||||
],
|
||||
|
||||
passes: [
|
||||
(
|
||||
name: "GBuffer",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: true,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
layout(location = 1) in vec2 vertexTexCoord;
|
||||
layout(location = 2) in vec3 vertexNormal;
|
||||
layout(location = 3) in vec4 vertexTangent;
|
||||
layout(location = 4) in vec4 boneWeights;
|
||||
layout(location = 5) in vec4 boneIndices;
|
||||
layout(location = 6) in vec2 vertexSecondTexCoord;
|
||||
|
||||
// Define uniforms with reserved names. Fyrox will automatically provide
|
||||
// required data to these uniforms.
|
||||
uniform mat4 fyrox_worldMatrix;
|
||||
uniform mat4 fyrox_worldViewProjection;
|
||||
uniform bool fyrox_useSkeletalAnimation;
|
||||
uniform sampler2D fyrox_boneMatrices;
|
||||
uniform sampler3D fyrox_blendShapesStorage;
|
||||
uniform float fyrox_blendShapesWeights[128];
|
||||
uniform int fyrox_blendShapesCount;
|
||||
|
||||
out vec3 position;
|
||||
out vec3 normal;
|
||||
out vec2 texCoord;
|
||||
out vec3 tangent;
|
||||
out vec3 binormal;
|
||||
out vec2 secondTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 localPosition = vec4(0);
|
||||
vec3 localNormal = vec3(0);
|
||||
vec3 localTangent = vec3(0);
|
||||
|
||||
vec4 inputPosition = vec4(vertexPosition, 1.0);
|
||||
vec3 inputNormal = vertexNormal;
|
||||
vec3 inputTangent = vertexTangent.xyz;
|
||||
|
||||
for (int i = 0; i < fyrox_blendShapesCount; ++i) {
|
||||
TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(fyrox_blendShapesStorage, gl_VertexID, i);
|
||||
float weight = fyrox_blendShapesWeights[i];
|
||||
inputPosition.xyz += offsets.position * weight;
|
||||
inputNormal += offsets.normal * weight;
|
||||
inputTangent += offsets.tangent * weight;
|
||||
}
|
||||
|
||||
if (fyrox_useSkeletalAnimation)
|
||||
{
|
||||
int i0 = int(boneIndices.x);
|
||||
int i1 = int(boneIndices.y);
|
||||
int i2 = int(boneIndices.z);
|
||||
int i3 = int(boneIndices.w);
|
||||
|
||||
mat4 m0 = S_FetchMatrix(fyrox_boneMatrices, i0);
|
||||
mat4 m1 = S_FetchMatrix(fyrox_boneMatrices, i1);
|
||||
mat4 m2 = S_FetchMatrix(fyrox_boneMatrices, i2);
|
||||
mat4 m3 = S_FetchMatrix(fyrox_boneMatrices, i3);
|
||||
|
||||
localPosition += m0 * inputPosition * boneWeights.x;
|
||||
localPosition += m1 * inputPosition * boneWeights.y;
|
||||
localPosition += m2 * inputPosition * boneWeights.z;
|
||||
localPosition += m3 * inputPosition * boneWeights.w;
|
||||
|
||||
localNormal += mat3(m0) * inputNormal * boneWeights.x;
|
||||
localNormal += mat3(m1) * inputNormal * boneWeights.y;
|
||||
localNormal += mat3(m2) * inputNormal * boneWeights.z;
|
||||
localNormal += mat3(m3) * inputNormal * boneWeights.w;
|
||||
|
||||
localTangent += mat3(m0) * inputTangent * boneWeights.x;
|
||||
localTangent += mat3(m1) * inputTangent * boneWeights.y;
|
||||
localTangent += mat3(m2) * inputTangent * boneWeights.z;
|
||||
localTangent += mat3(m3) * inputTangent * boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
localNormal = inputNormal;
|
||||
localTangent = inputTangent;
|
||||
}
|
||||
|
||||
mat3 nm = mat3(fyrox_worldMatrix);
|
||||
normal = normalize(nm * localNormal);
|
||||
tangent = normalize(nm * localTangent);
|
||||
binormal = normalize(vertexTangent.w * cross(normal, tangent));
|
||||
texCoord = vertexTexCoord;
|
||||
position = vec3(fyrox_worldMatrix * localPosition);
|
||||
secondTexCoord = vertexSecondTexCoord;
|
||||
|
||||
gl_Position = fyrox_worldViewProjection * localPosition;
|
||||
}
|
||||
"#,
|
||||
fragment_shader:
|
||||
r#"
|
||||
layout(location = 0) out vec4 outColor;
|
||||
layout(location = 1) out vec4 outNormal;
|
||||
layout(location = 2) out vec4 outAmbient;
|
||||
layout(location = 3) out vec4 outMaterial;
|
||||
layout(location = 4) out uint outDecalMask;
|
||||
|
||||
// Properties.
|
||||
uniform sampler2D diffuseTexture;
|
||||
uniform sampler2D normalTexture;
|
||||
uniform sampler2D metallicRoughnessTexture; // B for metallness, G for roughness
|
||||
uniform sampler2D heightTexture;
|
||||
uniform sampler2D emissionTexture;
|
||||
uniform sampler2D lightmapTexture;
|
||||
uniform sampler2D aoTexture;
|
||||
uniform vec2 texCoordScale;
|
||||
uniform uint layerIndex;
|
||||
uniform vec3 emissionStrength;
|
||||
uniform vec4 diffuseColor;
|
||||
uniform float metallicFactor;
|
||||
uniform float roughnessFactor;
|
||||
uniform float parallaxCenter;
|
||||
uniform float parallaxScale;
|
||||
|
||||
// Define uniforms with reserved names. Fyrox will automatically provide
|
||||
// required data to these uniforms.
|
||||
uniform vec3 fyrox_cameraPosition;
|
||||
uniform bool fyrox_usePOM;
|
||||
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
in vec2 texCoord;
|
||||
in vec3 tangent;
|
||||
in vec3 binormal;
|
||||
in vec2 secondTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
mat3 tangentSpace = mat3(tangent, binormal, normal);
|
||||
vec3 toFragment = normalize(position - fyrox_cameraPosition);
|
||||
|
||||
vec2 tc;
|
||||
if (fyrox_usePOM) {
|
||||
vec3 toFragmentTangentSpace = normalize(transpose(tangentSpace) * toFragment);
|
||||
tc = S_ComputeParallaxTextureCoordinates(
|
||||
heightTexture,
|
||||
toFragmentTangentSpace,
|
||||
texCoord * texCoordScale,
|
||||
parallaxCenter,
|
||||
parallaxScale
|
||||
);
|
||||
} else {
|
||||
tc = texCoord * texCoordScale;
|
||||
}
|
||||
|
||||
outColor = diffuseColor * texture(diffuseTexture, tc);
|
||||
|
||||
// Alpha test.
|
||||
if (outColor.a < 0.5) {
|
||||
discard;
|
||||
}
|
||||
outColor.a = 1.0;
|
||||
|
||||
vec4 n = normalize(texture(normalTexture, tc) * 2.0 - 1.0);
|
||||
outNormal = vec4(normalize(tangentSpace * n.xyz) * 0.5 + 0.5, 1.0);
|
||||
|
||||
outMaterial.x = metallicFactor * texture(metallicRoughnessTexture, tc).b; // Metallic
|
||||
outMaterial.y = roughnessFactor * texture(metallicRoughnessTexture, tc).g; // Roughness
|
||||
outMaterial.z = texture(aoTexture, tc).r;
|
||||
outMaterial.a = 1.0;
|
||||
|
||||
outAmbient.xyz = emissionStrength * texture(emissionTexture, tc).rgb + texture(lightmapTexture, secondTexCoord).rgb;
|
||||
outAmbient.a = 1.0;
|
||||
|
||||
outDecalMask = layerIndex;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "Forward",
|
||||
draw_parameters: DrawParameters(
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: true,
|
||||
blend: Some(BlendParameters(
|
||||
func: BlendFunc(
|
||||
sfactor: SrcAlpha,
|
||||
dfactor: OneMinusSrcAlpha,
|
||||
alpha_sfactor: SrcAlpha,
|
||||
alpha_dfactor: OneMinusSrcAlpha,
|
||||
),
|
||||
equation: BlendEquation(
|
||||
rgb: Add,
|
||||
alpha: Add
|
||||
)
|
||||
)),
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
),
|
||||
vertex_shader:
|
||||
r#"
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
layout(location = 1) in vec2 vertexTexCoord;
|
||||
layout(location = 4) in vec4 boneWeights;
|
||||
layout(location = 5) in vec4 boneIndices;
|
||||
|
||||
uniform mat4 fyrox_worldViewProjection;
|
||||
uniform bool fyrox_useSkeletalAnimation;
|
||||
uniform sampler2D fyrox_boneMatrices;
|
||||
uniform sampler3D fyrox_blendShapesStorage;
|
||||
uniform float fyrox_blendShapesWeights[128];
|
||||
uniform int fyrox_blendShapesCount;
|
||||
|
||||
out vec3 position;
|
||||
out vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 localPosition = vec4(0);
|
||||
|
||||
vec4 inputPosition = vec4(vertexPosition, 1.0);
|
||||
|
||||
for (int i = 0; i < fyrox_blendShapesCount; ++i) {
|
||||
TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(fyrox_blendShapesStorage, gl_VertexID, i);
|
||||
float weight = fyrox_blendShapesWeights[i];
|
||||
inputPosition.xyz += offsets.position * weight;
|
||||
}
|
||||
|
||||
if (fyrox_useSkeletalAnimation)
|
||||
{
|
||||
int i0 = int(boneIndices.x);
|
||||
int i1 = int(boneIndices.y);
|
||||
int i2 = int(boneIndices.z);
|
||||
int i3 = int(boneIndices.w);
|
||||
|
||||
mat4 m0 = S_FetchMatrix(fyrox_boneMatrices, i0);
|
||||
mat4 m1 = S_FetchMatrix(fyrox_boneMatrices, i1);
|
||||
mat4 m2 = S_FetchMatrix(fyrox_boneMatrices, i2);
|
||||
mat4 m3 = S_FetchMatrix(fyrox_boneMatrices, i3);
|
||||
|
||||
localPosition += m0 * inputPosition * boneWeights.x;
|
||||
localPosition += m1 * inputPosition * boneWeights.y;
|
||||
localPosition += m2 * inputPosition * boneWeights.z;
|
||||
localPosition += m3 * inputPosition * boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
gl_Position = fyrox_worldViewProjection * localPosition;
|
||||
texCoord = vertexTexCoord;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
uniform sampler2D diffuseTexture;
|
||||
uniform vec4 diffuseColor;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = diffuseColor * texture(diffuseTexture, texCoord);
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "DirectionalShadow",
|
||||
|
||||
draw_parameters: DrawParameters (
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: true,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
layout(location = 1) in vec2 vertexTexCoord;
|
||||
layout(location = 4) in vec4 boneWeights;
|
||||
layout(location = 5) in vec4 boneIndices;
|
||||
|
||||
uniform mat4 fyrox_worldViewProjection;
|
||||
uniform bool fyrox_useSkeletalAnimation;
|
||||
uniform sampler2D fyrox_boneMatrices;
|
||||
uniform sampler3D fyrox_blendShapesStorage;
|
||||
uniform float fyrox_blendShapesWeights[128];
|
||||
uniform int fyrox_blendShapesCount;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 localPosition = vec4(0);
|
||||
|
||||
vec4 inputPosition = vec4(vertexPosition, 1.0);
|
||||
|
||||
for (int i = 0; i < fyrox_blendShapesCount; ++i) {
|
||||
TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(fyrox_blendShapesStorage, gl_VertexID, i);
|
||||
float weight = fyrox_blendShapesWeights[i];
|
||||
inputPosition.xyz += offsets.position * weight;
|
||||
}
|
||||
|
||||
if (fyrox_useSkeletalAnimation)
|
||||
{
|
||||
vec4 vertex = vec4(vertexPosition, 1.0);
|
||||
|
||||
mat4 m0 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.x));
|
||||
mat4 m1 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.y));
|
||||
mat4 m2 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.z));
|
||||
mat4 m3 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.w));
|
||||
|
||||
localPosition += m0 * inputPosition * boneWeights.x;
|
||||
localPosition += m1 * inputPosition * boneWeights.y;
|
||||
localPosition += m2 * inputPosition * boneWeights.z;
|
||||
localPosition += m3 * inputPosition * boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
|
||||
gl_Position = fyrox_worldViewProjection * localPosition;
|
||||
texCoord = vertexTexCoord;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
uniform sampler2D diffuseTexture;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (texture(diffuseTexture, texCoord).a < 0.2) discard;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "SpotShadow",
|
||||
|
||||
draw_parameters: DrawParameters (
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: true,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
layout(location = 1) in vec2 vertexTexCoord;
|
||||
layout(location = 4) in vec4 boneWeights;
|
||||
layout(location = 5) in vec4 boneIndices;
|
||||
|
||||
uniform mat4 fyrox_worldViewProjection;
|
||||
uniform bool fyrox_useSkeletalAnimation;
|
||||
uniform sampler2D fyrox_boneMatrices;
|
||||
uniform sampler3D fyrox_blendShapesStorage;
|
||||
uniform float fyrox_blendShapesWeights[128];
|
||||
uniform int fyrox_blendShapesCount;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 localPosition = vec4(0);
|
||||
|
||||
vec4 inputPosition = vec4(vertexPosition, 1.0);
|
||||
|
||||
for (int i = 0; i < fyrox_blendShapesCount; ++i) {
|
||||
TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(fyrox_blendShapesStorage, gl_VertexID, i);
|
||||
float weight = fyrox_blendShapesWeights[i];
|
||||
inputPosition.xyz += offsets.position * weight;
|
||||
}
|
||||
|
||||
if (fyrox_useSkeletalAnimation)
|
||||
{
|
||||
vec4 vertex = vec4(vertexPosition, 1.0);
|
||||
|
||||
mat4 m0 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.x));
|
||||
mat4 m1 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.y));
|
||||
mat4 m2 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.z));
|
||||
mat4 m3 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.w));
|
||||
|
||||
localPosition += m0 * inputPosition * boneWeights.x;
|
||||
localPosition += m1 * inputPosition * boneWeights.y;
|
||||
localPosition += m2 * inputPosition * boneWeights.z;
|
||||
localPosition += m3 * inputPosition * boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
|
||||
gl_Position = fyrox_worldViewProjection * localPosition;
|
||||
texCoord = vertexTexCoord;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
uniform sampler2D diffuseTexture;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (texture(diffuseTexture, texCoord).a < 0.2) discard;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
(
|
||||
name: "PointShadow",
|
||||
|
||||
draw_parameters: DrawParameters (
|
||||
cull_face: Some(Back),
|
||||
color_write: ColorMask(
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
),
|
||||
depth_write: true,
|
||||
stencil_test: None,
|
||||
depth_test: true,
|
||||
blend: None,
|
||||
stencil_op: StencilOp(
|
||||
fail: Keep,
|
||||
zfail: Keep,
|
||||
zpass: Keep,
|
||||
write_mask: 0xFFFF_FFFF,
|
||||
),
|
||||
),
|
||||
|
||||
vertex_shader:
|
||||
r#"
|
||||
layout(location = 0) in vec3 vertexPosition;
|
||||
layout(location = 1) in vec2 vertexTexCoord;
|
||||
layout(location = 4) in vec4 boneWeights;
|
||||
layout(location = 5) in vec4 boneIndices;
|
||||
|
||||
uniform mat4 fyrox_worldMatrix;
|
||||
uniform mat4 fyrox_worldViewProjection;
|
||||
uniform bool fyrox_useSkeletalAnimation;
|
||||
uniform sampler2D fyrox_boneMatrices;
|
||||
uniform sampler3D fyrox_blendShapesStorage;
|
||||
uniform float fyrox_blendShapesWeights[128];
|
||||
uniform int fyrox_blendShapesCount;
|
||||
|
||||
out vec2 texCoord;
|
||||
out vec3 worldPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 localPosition = vec4(0);
|
||||
|
||||
vec4 inputPosition = vec4(vertexPosition, 1.0);
|
||||
|
||||
for (int i = 0; i < fyrox_blendShapesCount; ++i) {
|
||||
TBlendShapeOffsets offsets = S_FetchBlendShapeOffsets(fyrox_blendShapesStorage, gl_VertexID, i);
|
||||
float weight = fyrox_blendShapesWeights[i];
|
||||
inputPosition.xyz += offsets.position * weight;
|
||||
}
|
||||
|
||||
if (fyrox_useSkeletalAnimation)
|
||||
{
|
||||
vec4 vertex = vec4(vertexPosition, 1.0);
|
||||
|
||||
mat4 m0 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.x));
|
||||
mat4 m1 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.y));
|
||||
mat4 m2 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.z));
|
||||
mat4 m3 = S_FetchMatrix(fyrox_boneMatrices, int(boneIndices.w));
|
||||
|
||||
localPosition += m0 * inputPosition * boneWeights.x;
|
||||
localPosition += m1 * inputPosition * boneWeights.y;
|
||||
localPosition += m2 * inputPosition * boneWeights.z;
|
||||
localPosition += m3 * inputPosition * boneWeights.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
localPosition = inputPosition;
|
||||
}
|
||||
|
||||
gl_Position = fyrox_worldViewProjection * localPosition;
|
||||
worldPosition = (fyrox_worldMatrix * localPosition).xyz;
|
||||
texCoord = vertexTexCoord;
|
||||
}
|
||||
"#,
|
||||
|
||||
fragment_shader:
|
||||
r#"
|
||||
uniform sampler2D diffuseTexture;
|
||||
|
||||
uniform vec3 fyrox_lightPosition;
|
||||
|
||||
in vec2 texCoord;
|
||||
in vec3 worldPosition;
|
||||
|
||||
layout(location = 0) out float depth;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (texture(diffuseTexture, texCoord).a < 0.2) discard;
|
||||
depth = length(fyrox_lightPosition - worldPosition);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,118 @@
|
||||
/// Iterator that skips elements of its inner iterator so that it
|
||||
/// returns every nth element, starting from a given offset from the
|
||||
/// start of the inner iterator.
|
||||
///
|
||||
/// For example: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
||||
/// Becomes: [1, 4, 7]
|
||||
///
|
||||
/// In this case, it is returning the second element within each three elements.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SelectIterator<I> {
|
||||
source: I,
|
||||
offset: usize,
|
||||
window_size: usize,
|
||||
starting: bool,
|
||||
}
|
||||
|
||||
/// Create an iterator that skips elements of the given iterator to produce a single element
|
||||
/// within each group of window_size elements. The element returned is `offset` elements from the
|
||||
/// start of each window.
|
||||
///
|
||||
/// For example: select_iterator(iter, 3, 5)
|
||||
/// If iter produces: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
/// select_iterartor(iter, 3, 5]) procues: [3, 8]
|
||||
pub fn select_iterator<I>(source: I, offset: usize, window_size: usize) -> SelectIterator<I> {
|
||||
SelectIterator {
|
||||
source,
|
||||
offset,
|
||||
window_size,
|
||||
starting: true,
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for SelectIterator<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
type Item = <I as Iterator>::Item;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.starting {
|
||||
self.starting = false;
|
||||
for _ in 0..self.offset {
|
||||
self.source.next()?;
|
||||
}
|
||||
self.source.next()
|
||||
} else {
|
||||
for _ in 0..self.window_size - 1 {
|
||||
self.source.next()?;
|
||||
}
|
||||
self.source.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_cubic_data<I, O>(
|
||||
inputs: I,
|
||||
outputs: O,
|
||||
) -> impl Iterator<Item = (<I as Iterator>::Item, [<O as Iterator>::Item; 3])>
|
||||
where
|
||||
I: Iterator,
|
||||
O: Iterator,
|
||||
{
|
||||
CubicIter { inputs, outputs }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CubicIter<I, O> {
|
||||
inputs: I,
|
||||
outputs: O,
|
||||
}
|
||||
|
||||
impl<I, O> Iterator for CubicIter<I, O>
|
||||
where
|
||||
I: Iterator,
|
||||
O: Iterator,
|
||||
{
|
||||
type Item = (<I as Iterator>::Item, [<O as Iterator>::Item; 3]);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some((
|
||||
self.inputs.next()?,
|
||||
[
|
||||
self.outputs.next()?,
|
||||
self.outputs.next()?,
|
||||
self.outputs.next()?,
|
||||
],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn select_example1() {
|
||||
let iter = [0, 1, 2, 3, 4, 5, 6, 7, 8].into_iter();
|
||||
let result: Vec<_> = select_iterator(iter, 1, 3).collect();
|
||||
assert!(result.clone().into_iter().eq([1, 4, 7]), "{:?}", result);
|
||||
}
|
||||
#[test]
|
||||
fn select_example2() {
|
||||
let iter = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter();
|
||||
let result: Vec<_> = select_iterator(iter, 3, 5).collect();
|
||||
assert!(result.clone().into_iter().eq([3, 8]), "{:?}", result);
|
||||
}
|
||||
#[test]
|
||||
fn cubic1() {
|
||||
let input = [0, 1, 2, 3].into_iter();
|
||||
let output = [-1, 0, 1, 2, 3, 4, 5, 6].into_iter();
|
||||
let result: Vec<_> = iter_cubic_data(input, output).collect();
|
||||
assert!(
|
||||
result
|
||||
.clone()
|
||||
.into_iter()
|
||||
.eq([(0, [-1, 0, 1]), (1, [2, 3, 4])]),
|
||||
"{:?}",
|
||||
result
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{
|
||||
asset::{manager::ResourceManager, state::LoadError, untyped::ResourceKind, Resource},
|
||||
core::{algebra::Vector4, color::Color, log::Log, sstorage::ImmutableString},
|
||||
material::{
|
||||
shader::{SamplerFallback, Shader, ShaderResource},
|
||||
Material, MaterialResource, PropertyValue,
|
||||
},
|
||||
resource::{
|
||||
model::MaterialSearchOptions,
|
||||
texture::{Texture, TextureError, TextureImportOptions, TextureResource},
|
||||
},
|
||||
};
|
||||
use gltf::{buffer::View, image, Document};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use super::uri;
|
||||
|
||||
type Result<T> = std::result::Result<T, GltfMaterialError>;
|
||||
|
||||
use crate::resource::texture::TextureMagnificationFilter as FyroxMagFilter;
|
||||
use crate::resource::texture::TextureMinificationFilter as FyroxMinFilter;
|
||||
use gltf::texture::MagFilter as GltfMagFilter;
|
||||
use gltf::texture::MinFilter as GltfMinFilter;
|
||||
|
||||
pub const SHADER_NAME: &str = "glTF Shader";
|
||||
pub const SHADER_SRC: &str = include_str!("gltf_standard.shader");
|
||||
|
||||
lazy_static! {
|
||||
static ref GLTF_SHADER: ShaderResource =
|
||||
ShaderResource::new_ok(SHADER_NAME.into(), Shader::from_string(SHADER_SRC).unwrap());
|
||||
}
|
||||
|
||||
fn convert_mini(filter: GltfMinFilter) -> FyroxMinFilter {
|
||||
match filter {
|
||||
GltfMinFilter::Linear => FyroxMinFilter::Linear,
|
||||
GltfMinFilter::Nearest => FyroxMinFilter::Nearest,
|
||||
GltfMinFilter::LinearMipmapLinear => FyroxMinFilter::LinearMipMapLinear,
|
||||
GltfMinFilter::NearestMipmapLinear => FyroxMinFilter::NearestMipMapLinear,
|
||||
GltfMinFilter::LinearMipmapNearest => FyroxMinFilter::LinearMipMapNearest,
|
||||
GltfMinFilter::NearestMipmapNearest => FyroxMinFilter::NearestMipMapNearest,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mag(filter: GltfMagFilter) -> FyroxMagFilter {
|
||||
match filter {
|
||||
GltfMagFilter::Linear => FyroxMagFilter::Linear,
|
||||
GltfMagFilter::Nearest => FyroxMagFilter::Nearest,
|
||||
}
|
||||
}
|
||||
|
||||
use crate::resource::texture::TextureWrapMode as FyroxWrapMode;
|
||||
use gltf::texture::WrappingMode as GltfWrapMode;
|
||||
|
||||
fn convert_wrap(mode: GltfWrapMode) -> FyroxWrapMode {
|
||||
match mode {
|
||||
GltfWrapMode::Repeat => FyroxWrapMode::Repeat,
|
||||
GltfWrapMode::ClampToEdge => FyroxWrapMode::ClampToEdge,
|
||||
GltfWrapMode::MirroredRepeat => FyroxWrapMode::MirroredRepeat,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GltfMaterialError {
|
||||
ShaderLoadFailed,
|
||||
InvalidIndex,
|
||||
UnsupportedURI(Box<str>),
|
||||
TextureNotFound(Box<str>),
|
||||
Load(LoadError),
|
||||
Base64(base64::DecodeError),
|
||||
Texture(TextureError),
|
||||
}
|
||||
|
||||
impl From<LoadError> for GltfMaterialError {
|
||||
fn from(error: LoadError) -> Self {
|
||||
GltfMaterialError::Load(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for GltfMaterialError {
|
||||
fn from(error: base64::DecodeError) -> Self {
|
||||
GltfMaterialError::Base64(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextureError> for GltfMaterialError {
|
||||
fn from(error: TextureError) -> Self {
|
||||
GltfMaterialError::Texture(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SourceImage<'a> {
|
||||
External(&'a str),
|
||||
View(&'a [u8]),
|
||||
Embedded(Vec<u8>),
|
||||
}
|
||||
|
||||
pub fn decode_base64(source: &str) -> Result<Vec<u8>> {
|
||||
Ok(uri::decode_base64(source)?)
|
||||
}
|
||||
|
||||
/// Extract a list of [MaterialResource] from the give glTF document, if that document contains any.
|
||||
/// The resulting list of materials is guaranteed to be the same length as the list of materials
|
||||
/// in the document so that an index into the document's list of materials will be the same as the index
|
||||
/// of the matching MaterialResource in the returned list. This is important since the glTF document
|
||||
/// refers to materials by index.
|
||||
///
|
||||
/// * `doc`: The document in which to find the materials.
|
||||
///
|
||||
/// * `textures`: A slice containing a [TextureResource] for every texture defined in the document, in that order, so that
|
||||
/// a texture can be looked up using the index of a texture within the document. Materials in glTF specify their target
|
||||
/// textures by their index within the node list of the document, and these indices need to be translated into handles.
|
||||
///
|
||||
/// * `buffers`: A slice containing a list of byte-vectors, one for each buffer in the glTF document.
|
||||
/// Animations in glTF make reference to data stored in the document's list of buffers by index.
|
||||
/// This slcie allows an index into the document's list of buffers to be translated into actual bytes of data.
|
||||
///
|
||||
/// * `resource_manager`: A [ResourceManager] makes it possible to access shaders and create materials.
|
||||
pub async fn import_materials(
|
||||
gltf: &Document,
|
||||
textures: &[TextureResource],
|
||||
resource_manager: &ResourceManager,
|
||||
) -> Result<Vec<MaterialResource>> {
|
||||
let mut result: Vec<MaterialResource> = Vec::with_capacity(gltf.materials().len());
|
||||
for mat in gltf.materials() {
|
||||
match import_material(mat, textures, resource_manager).await {
|
||||
Ok(res) => result.push(res),
|
||||
Err(err) => {
|
||||
Log::err(format!("glTF material failed to import. Reason: {:?}", err));
|
||||
result.push(MaterialResource::new_ok(
|
||||
ResourceKind::Embedded,
|
||||
Material::default(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn import_material(
|
||||
mat: gltf::Material<'_>,
|
||||
textures: &[TextureResource],
|
||||
resource_manager: &ResourceManager,
|
||||
) -> Result<MaterialResource> {
|
||||
let shader: ShaderResource = GLTF_SHADER.clone(); //resource_manager.request(SHADER_PATH).await?;
|
||||
if !shader.is_ok() {
|
||||
return Err(GltfMaterialError::ShaderLoadFailed);
|
||||
}
|
||||
let mut result: Material = Material::from_shader(shader, Some(resource_manager.clone()));
|
||||
let pbr = mat.pbr_metallic_roughness();
|
||||
if let Some(tex) = pbr.base_color_texture() {
|
||||
set_texture(
|
||||
&mut result,
|
||||
"diffuseTexture",
|
||||
textures,
|
||||
tex.texture().index(),
|
||||
SamplerFallback::White,
|
||||
)?;
|
||||
}
|
||||
if let Some(tex) = mat.normal_texture() {
|
||||
set_texture(
|
||||
&mut result,
|
||||
"normalTexture",
|
||||
textures,
|
||||
tex.texture().index(),
|
||||
SamplerFallback::Normal,
|
||||
)?;
|
||||
}
|
||||
if let Some(tex) = pbr.metallic_roughness_texture() {
|
||||
set_texture(
|
||||
&mut result,
|
||||
"metallicRoughnessTexture",
|
||||
textures,
|
||||
tex.texture().index(),
|
||||
SamplerFallback::White,
|
||||
)?;
|
||||
}
|
||||
if let Some(tex) = mat.emissive_texture() {
|
||||
set_texture(
|
||||
&mut result,
|
||||
"emissionTexture",
|
||||
textures,
|
||||
tex.texture().index(),
|
||||
SamplerFallback::Black,
|
||||
)?;
|
||||
}
|
||||
if let Some(tex) = mat.occlusion_texture() {
|
||||
set_texture(
|
||||
&mut result,
|
||||
"aoTexture",
|
||||
textures,
|
||||
tex.texture().index(),
|
||||
SamplerFallback::White,
|
||||
)?;
|
||||
}
|
||||
set_material_color(
|
||||
&mut result,
|
||||
"diffuseColor",
|
||||
Vector4::<f32>::from(pbr.base_color_factor()).into(),
|
||||
)?;
|
||||
set_material_vector3(&mut result, "emissionStrength", mat.emissive_factor())?;
|
||||
set_material_scalar(&mut result, "metallicFactor", pbr.metallic_factor())?;
|
||||
set_material_scalar(&mut result, "roughnessFactor", pbr.roughness_factor())?;
|
||||
Ok(Resource::new_ok(ResourceKind::Embedded, result))
|
||||
}
|
||||
|
||||
fn set_material_scalar(material: &mut Material, name: &'static str, value: f32) -> Result<()> {
|
||||
let value: PropertyValue = PropertyValue::Float(value);
|
||||
match material.set_property(&ImmutableString::new(name), value) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => {
|
||||
Log::err(format!(
|
||||
"Unable to set material property {} for glTF material! Reason: {:?}",
|
||||
name, err
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_material_color(material: &mut Material, name: &'static str, color: Color) -> Result<()> {
|
||||
let value: PropertyValue = PropertyValue::Color(color);
|
||||
match material.set_property(&ImmutableString::new(name), value) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => {
|
||||
Log::err(format!(
|
||||
"Unable to set material property {} for GLTF material! Reason: {:?}",
|
||||
name, err
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_material_vector3(
|
||||
material: &mut Material,
|
||||
name: &'static str,
|
||||
vector: [f32; 3],
|
||||
) -> Result<()> {
|
||||
let value: PropertyValue = PropertyValue::Vector3(vector.into());
|
||||
match material.set_property(&ImmutableString::new(name), value) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => {
|
||||
Log::err(format!(
|
||||
"Unable to set material property {} for GLTF material! Reason: {:?}",
|
||||
name, err
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn set_material_vector4(
|
||||
material: &mut Material,
|
||||
name: &'static str,
|
||||
vector: [f32; 4],
|
||||
) -> Result<()> {
|
||||
let value: PropertyValue = PropertyValue::Vector4(vector.into());
|
||||
match material.set_property(&ImmutableString::new(name), value) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => {
|
||||
Log::err(format!(
|
||||
"Unable to set material property {} for GLTF material! Reason: {:?}",
|
||||
name, err
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_texture(
|
||||
material: &mut Material,
|
||||
name: &'static str,
|
||||
textures: &[TextureResource],
|
||||
index: usize,
|
||||
fallback: SamplerFallback,
|
||||
) -> Result<()> {
|
||||
let tex: TextureResource = textures
|
||||
.get(index)
|
||||
.ok_or(GltfMaterialError::InvalidIndex)?
|
||||
.clone();
|
||||
match material.set_property(
|
||||
&ImmutableString::new(name),
|
||||
PropertyValue::Sampler {
|
||||
value: Some(tex),
|
||||
fallback,
|
||||
},
|
||||
) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => {
|
||||
Log::err(format!(
|
||||
"Unable to set material property {} for GLTF material! Reason: {:?}",
|
||||
name, err
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import_images<'a, 'b>(
|
||||
gltf: &'a Document,
|
||||
buffers: &'b [Vec<u8>],
|
||||
) -> Result<Vec<SourceImage<'b>>>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
let mut result: Vec<SourceImage> = Vec::new();
|
||||
for image in gltf.images() {
|
||||
match image.source() {
|
||||
image::Source::Uri { uri, mime_type: _ } => result.push(import_image_from_uri(uri)?),
|
||||
image::Source::View { view, mime_type: _ } => {
|
||||
result.push(import_image_from_view(view, buffers)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn import_image_from_uri<'a>(uri: &'a str) -> Result<SourceImage<'a>> {
|
||||
let parsed_uri = uri::parse_uri(uri);
|
||||
match parsed_uri.scheme {
|
||||
uri::Scheme::Data if parsed_uri.data.is_some() => Ok(SourceImage::Embedded(decode_base64(
|
||||
parsed_uri.data.unwrap(),
|
||||
)?)),
|
||||
uri::Scheme::None => Ok(SourceImage::External(uri)),
|
||||
_ => Err(GltfMaterialError::UnsupportedURI(uri.into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn import_image_from_view<'a>(view: View, buffers: &'a [Vec<u8>]) -> Result<SourceImage<'a>> {
|
||||
let offset: usize = view.offset();
|
||||
let length: usize = view.length();
|
||||
let buf: &Vec<u8> = buffers
|
||||
.get(view.buffer().index())
|
||||
.ok_or(GltfMaterialError::InvalidIndex)?;
|
||||
Ok(SourceImage::View(&buf[offset..offset + length]))
|
||||
}
|
||||
|
||||
pub struct TextureContext<'a> {
|
||||
pub resource_manager: &'a ResourceManager,
|
||||
pub model_path: &'a Path,
|
||||
pub search_options: &'a MaterialSearchOptions,
|
||||
}
|
||||
|
||||
pub async fn import_textures<'a>(
|
||||
gltf: &'a Document,
|
||||
images: &[SourceImage<'a>],
|
||||
context: TextureContext<'a>,
|
||||
) -> Result<Vec<TextureResource>> {
|
||||
let mut result: Vec<TextureResource> = Vec::with_capacity(gltf.textures().len());
|
||||
for tex in gltf.textures() {
|
||||
let sampler = tex.sampler();
|
||||
let source = tex.source();
|
||||
let image = images
|
||||
.get(source.index())
|
||||
.ok_or(GltfMaterialError::InvalidIndex)?;
|
||||
match image {
|
||||
SourceImage::Embedded(data) => result.push(import_embedded_texture(sampler, data)?),
|
||||
SourceImage::View(data) => result.push(import_embedded_texture(sampler, data)?),
|
||||
SourceImage::External(filename) => {
|
||||
import_external_texture(filename, &context).await?;
|
||||
} // result.push(import_external_texture(filename, &context).await?),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn import_embedded_texture(
|
||||
sampler: gltf::texture::Sampler,
|
||||
data: &[u8],
|
||||
) -> Result<TextureResource> {
|
||||
let mut options = TextureImportOptions::default();
|
||||
if let Some(filter) = sampler.min_filter() {
|
||||
options.set_minification_filter(convert_mini(filter));
|
||||
}
|
||||
if let Some(filter) = sampler.mag_filter() {
|
||||
options.set_magnification_filter(convert_mag(filter));
|
||||
}
|
||||
options.set_s_wrap_mode(convert_wrap(sampler.wrap_s()));
|
||||
options.set_t_wrap_mode(convert_wrap(sampler.wrap_t()));
|
||||
let tex = Texture::load_from_memory(data, options)?;
|
||||
Ok(Resource::new_ok(ResourceKind::Embedded, tex))
|
||||
}
|
||||
|
||||
async fn import_external_texture(
|
||||
filename: &str,
|
||||
context: &TextureContext<'_>,
|
||||
) -> Result<TextureResource> {
|
||||
let path = search_for_path(filename, context)
|
||||
.await
|
||||
.ok_or_else(|| GltfMaterialError::TextureNotFound(filename.into()))?;
|
||||
Ok(context.resource_manager.request(path))
|
||||
}
|
||||
|
||||
async fn search_for_path(filename: &str, context: &TextureContext<'_>) -> Option<PathBuf> {
|
||||
match context.search_options {
|
||||
MaterialSearchOptions::MaterialsDirectory(ref directory) => Some(directory.join(filename)),
|
||||
MaterialSearchOptions::RecursiveUp => {
|
||||
let io = context.resource_manager.resource_io();
|
||||
let mut texture_path = None;
|
||||
let mut path: PathBuf = context.model_path.to_owned();
|
||||
while let Some(parent) = path.parent() {
|
||||
let candidate = parent.join(filename);
|
||||
if io.exists(&candidate).await {
|
||||
texture_path = Some(candidate);
|
||||
break;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
texture_path
|
||||
}
|
||||
MaterialSearchOptions::WorkingDirectory => {
|
||||
let io = context.resource_manager.resource_io();
|
||||
let mut texture_path = None;
|
||||
let path = Path::new(".");
|
||||
if let Ok(iter) = io.walk_directory(path).await {
|
||||
for dir in iter {
|
||||
if io.is_dir(&dir).await {
|
||||
let candidate = dir.join(filename);
|
||||
if candidate.exists() {
|
||||
texture_path = Some(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
texture_path
|
||||
}
|
||||
MaterialSearchOptions::UsePathDirectly => Some(filename.into()),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
///! [GltfLoader] enables the importing of *.gltf and *.glb files in the glTF format.
|
||||
///! This requires the "gltf" feature.
|
||||
use gltf::Document;
|
||||
use gltf::Gltf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::asset::io::ResourceIo;
|
||||
use crate::asset::loader;
|
||||
use crate::asset::manager::ResourceManager;
|
||||
use crate::asset::options;
|
||||
use crate::asset::state::LoadError;
|
||||
use crate::core::algebra::{Matrix4, Unit};
|
||||
use crate::core::log::Log;
|
||||
use crate::core::pool::Handle;
|
||||
use crate::core::TypeUuidProvider;
|
||||
use crate::graph::BaseSceneGraph;
|
||||
use crate::graph::NodeMapping;
|
||||
use crate::gui::core::io::FileLoadError;
|
||||
use crate::material::MaterialResource;
|
||||
use crate::resource::model::{MaterialSearchOptions, Model, ModelImportOptions};
|
||||
use crate::resource::texture::{TextureError, TextureResource};
|
||||
use crate::scene::animation::{AnimationContainer, AnimationPlayerBuilder};
|
||||
use crate::scene::base::BaseBuilder;
|
||||
use crate::scene::graph::Graph;
|
||||
use crate::scene::mesh::surface::{BlendShape, Surface, SurfaceSharedData};
|
||||
use crate::scene::mesh::{Mesh, MeshBuilder};
|
||||
use crate::scene::node::Node;
|
||||
use crate::scene::pivot::PivotBuilder;
|
||||
use crate::scene::transform::TransformBuilder;
|
||||
use crate::scene::Scene;
|
||||
|
||||
mod animation;
|
||||
mod iter;
|
||||
mod material;
|
||||
mod node_names;
|
||||
mod surface;
|
||||
mod uri;
|
||||
|
||||
use animation::import_animations;
|
||||
use material::*;
|
||||
pub use surface::SurfaceDataError;
|
||||
use surface::{build_surface_data, BlendShapeInfoContainer, GeometryStatistics};
|
||||
pub use uri::{parse_uri, Scheme, Uri};
|
||||
|
||||
type Result<T> = std::result::Result<T, GltfLoadError>;
|
||||
|
||||
const TARGET_NAMES_KEY: &str = "targetNames";
|
||||
|
||||
#[derive(Debug)]
|
||||
enum GltfLoadError {
|
||||
InvalidIndex,
|
||||
InvalidPath,
|
||||
UnsupportedURI(Box<str>),
|
||||
MissingEmbeddedBin,
|
||||
Gltf(gltf::Error),
|
||||
Texture(TextureError),
|
||||
File(FileLoadError),
|
||||
Base64(base64::DecodeError),
|
||||
Load(LoadError),
|
||||
Material(GltfMaterialError),
|
||||
Surface(SurfaceDataError),
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
JSON(serde_json::Error),
|
||||
}
|
||||
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
impl From<serde_json::Error> for GltfLoadError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
GltfLoadError::JSON(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gltf::Error> for GltfLoadError {
|
||||
fn from(error: gltf::Error) -> Self {
|
||||
GltfLoadError::Gltf(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextureError> for GltfLoadError {
|
||||
fn from(error: TextureError) -> Self {
|
||||
GltfLoadError::Texture(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileLoadError> for GltfLoadError {
|
||||
fn from(error: FileLoadError) -> Self {
|
||||
GltfLoadError::File(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadError> for GltfLoadError {
|
||||
fn from(error: LoadError) -> Self {
|
||||
GltfLoadError::Load(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for GltfLoadError {
|
||||
fn from(error: base64::DecodeError) -> Self {
|
||||
GltfLoadError::Base64(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GltfMaterialError> for GltfLoadError {
|
||||
fn from(error: GltfMaterialError) -> Self {
|
||||
GltfLoadError::Material(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SurfaceDataError> for GltfLoadError {
|
||||
fn from(error: SurfaceDataError) -> Self {
|
||||
GltfLoadError::Surface(error)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_base64(source: &str) -> Result<Vec<u8>> {
|
||||
Ok(uri::decode_base64(source)?)
|
||||
}
|
||||
|
||||
struct MeshData {
|
||||
surfaces: Vec<Surface>,
|
||||
blend_shapes: Vec<BlendShape>,
|
||||
}
|
||||
|
||||
struct NodeFamily {
|
||||
main_node: Handle<Node>,
|
||||
bone_children: Vec<SkinNodePair>,
|
||||
}
|
||||
|
||||
struct SkinNodePair {
|
||||
skin_index: usize,
|
||||
node: Handle<Node>,
|
||||
}
|
||||
|
||||
type SkinData = Vec<SkinBone>;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
struct SkinBone {
|
||||
pub node_index: usize,
|
||||
pub inv_bind_pose: Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl From<(usize, Matrix4<f32>)> for SkinBone {
|
||||
fn from(pair: (usize, Matrix4<f32>)) -> Self {
|
||||
let (node_index, inv_bind_pose) = pair;
|
||||
SkinBone {
|
||||
node_index,
|
||||
inv_bind_pose,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct SkinBonePair<'a> {
|
||||
skin_index: usize,
|
||||
bone: &'a SkinBone,
|
||||
}
|
||||
|
||||
struct SkinBoneIter<'a> {
|
||||
skin_index: usize,
|
||||
bone_index: usize,
|
||||
skin_list: &'a [SkinData],
|
||||
}
|
||||
|
||||
impl<'a> SkinBoneIter<'a> {
|
||||
fn new(skin_list: &'a [SkinData]) -> SkinBoneIter<'a> {
|
||||
SkinBoneIter {
|
||||
skin_index: 0,
|
||||
bone_index: 0,
|
||||
skin_list,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SkinBoneIter<'a> {
|
||||
type Item = SkinBonePair<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if self.skin_index >= self.skin_list.len() {
|
||||
return None;
|
||||
}
|
||||
let skin: &SkinData = &self.skin_list[self.skin_index];
|
||||
if self.bone_index >= skin.len() {
|
||||
self.bone_index = 0;
|
||||
self.skin_index += 1;
|
||||
} else {
|
||||
let bone = &skin[self.bone_index];
|
||||
self.bone_index += 1;
|
||||
return Some(SkinBonePair {
|
||||
skin_index: self.skin_index,
|
||||
bone,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImportContext {
|
||||
io: Arc<dyn ResourceIo>,
|
||||
resource_manager: ResourceManager,
|
||||
model_path: PathBuf,
|
||||
search_options: MaterialSearchOptions,
|
||||
}
|
||||
|
||||
impl ImportContext {
|
||||
fn as_texture_context(&self) -> TextureContext {
|
||||
TextureContext {
|
||||
resource_manager: &self.resource_manager,
|
||||
model_path: &self.model_path,
|
||||
search_options: &self.search_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ImportResults {
|
||||
buffers: Option<Vec<Vec<u8>>>,
|
||||
textures: Option<Vec<TextureResource>>,
|
||||
materials: Option<Vec<MaterialResource>>,
|
||||
skins: Option<Vec<SkinData>>,
|
||||
meshes: Option<Vec<MeshData>>,
|
||||
families: Option<Vec<NodeFamily>>,
|
||||
}
|
||||
|
||||
impl ImportResults {
|
||||
fn get_buffer_data_access<'s>(
|
||||
&'s self,
|
||||
) -> impl Clone + Fn(gltf::Buffer<'_>) -> Option<&'s [u8]> {
|
||||
|b| {
|
||||
self.buffers
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get(b.index())
|
||||
.map(Vec::as_slice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This object performs the loading of files in glTF format with extension "gltf" or "glb".
|
||||
pub struct GltfLoader {
|
||||
/// ResourceManager is needed so that textures and mesh data can be loaded from additional resources.
|
||||
/// The glTF format allows for other assets to be referenced by file path.
|
||||
pub resource_manager: ResourceManager,
|
||||
/// Import options control where this loader should search for additional resources.
|
||||
pub default_import_options: ModelImportOptions,
|
||||
}
|
||||
|
||||
impl loader::ResourceLoader for GltfLoader {
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["gltf", "glb"]
|
||||
}
|
||||
|
||||
fn data_type_uuid(&self) -> crate::core::type_traits::prelude::Uuid {
|
||||
Model::type_uuid()
|
||||
}
|
||||
|
||||
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> loader::BoxedLoaderFuture {
|
||||
let resource_manager = self.resource_manager.clone();
|
||||
let default_import_options = self.default_import_options.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let import_options = options::try_get_import_settings(&path, io.as_ref())
|
||||
.await
|
||||
.unwrap_or(default_import_options);
|
||||
|
||||
let model = load(path, io, resource_manager, import_options)
|
||||
.await
|
||||
.map_err(LoadError::new)?;
|
||||
|
||||
Ok(loader::LoaderPayload::new(model))
|
||||
})
|
||||
}
|
||||
|
||||
fn try_load_import_settings(
|
||||
&self,
|
||||
resource_path: PathBuf,
|
||||
io: Arc<dyn ResourceIo>,
|
||||
) -> loader::BoxedImportOptionsLoaderFuture {
|
||||
Box::pin(async move {
|
||||
options::try_get_import_settings_opaque::<ModelImportOptions>(&resource_path, &*io)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn default_import_options(&self) -> Option<Box<dyn options::BaseImportOptions>> {
|
||||
Some(Box::<ModelImportOptions>::default())
|
||||
}
|
||||
}
|
||||
|
||||
async fn load(
|
||||
path: PathBuf,
|
||||
io: Arc<dyn ResourceIo>,
|
||||
resource_manager: ResourceManager,
|
||||
options: ModelImportOptions,
|
||||
) -> Result<Model> {
|
||||
Log::info(format!("glTF load starting: {:?}", path));
|
||||
let mut scene = Scene::new();
|
||||
let context = ImportContext {
|
||||
io,
|
||||
resource_manager,
|
||||
model_path: path.clone(),
|
||||
search_options: options.material_search_options,
|
||||
};
|
||||
let root_name = path
|
||||
.file_name()
|
||||
.ok_or(GltfLoadError::InvalidPath)?
|
||||
.to_string_lossy();
|
||||
let root = scene.graph.get_root();
|
||||
scene.graph[root].set_name(root_name.clone());
|
||||
import_from_path(&mut scene.graph, &context).await?;
|
||||
node_names::resolve_name_conflicts(context.model_path.as_path(), &mut scene.graph);
|
||||
Log::info(format!("glTF load complete: {:?}", path));
|
||||
Ok(Model::new(NodeMapping::UseNames, scene))
|
||||
}
|
||||
|
||||
async fn import_from_path(graph: &mut Graph, context: &ImportContext) -> Result<()> {
|
||||
let file: Vec<u8> = context.io.load_file(context.model_path.as_path()).await?;
|
||||
import_from_slice(file.as_slice(), graph, context).await
|
||||
}
|
||||
|
||||
async fn import_from_slice(slice: &[u8], graph: &mut Graph, context: &ImportContext) -> Result<()> {
|
||||
let gltf: Gltf = Gltf::from_slice(slice)?;
|
||||
let doc = gltf.document;
|
||||
let data = gltf.blob;
|
||||
let mut imports: ImportResults = ImportResults::default();
|
||||
let _ = imports
|
||||
.buffers
|
||||
.insert(import_buffers(&doc, data, context).await?);
|
||||
let buffers: &[Vec<u8>] = imports.buffers.as_ref().unwrap().as_slice();
|
||||
let images: Vec<SourceImage> = import_images(&doc, buffers)?;
|
||||
let _ = imports
|
||||
.textures
|
||||
.insert(import_textures(&doc, images.as_slice(), context.as_texture_context()).await?);
|
||||
let textures = imports.textures.as_ref().unwrap().as_slice();
|
||||
let _ = imports
|
||||
.materials
|
||||
.insert(import_materials(&doc, textures, &context.resource_manager).await?);
|
||||
let materials = imports.materials.as_ref().unwrap().as_slice();
|
||||
let _ = imports.skins.insert(import_skins(&doc, &imports)?);
|
||||
let _ = imports.meshes.insert(import_meshes(
|
||||
&doc,
|
||||
&context.model_path,
|
||||
materials,
|
||||
buffers,
|
||||
)?);
|
||||
let _ = imports
|
||||
.families
|
||||
.insert(import_nodes(&doc, graph, &imports)?);
|
||||
link_child_nodes(&doc, graph, &imports)?;
|
||||
let node_handles: Vec<Handle<Node>> = imports
|
||||
.families
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|f| f.main_node)
|
||||
.collect();
|
||||
let animations = import_animations(&doc, &node_handles, buffers);
|
||||
if !animations.is_empty() {
|
||||
let mut anim_con = AnimationContainer::new();
|
||||
for animation in animations {
|
||||
anim_con.add(animation);
|
||||
}
|
||||
AnimationPlayerBuilder::new(BaseBuilder::new().with_name("AnimationPlayer"))
|
||||
.with_animations(anim_con)
|
||||
.build(graph);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn import_buffers(
|
||||
gltf: &Document,
|
||||
mut data_chunk: Option<Vec<u8>>,
|
||||
context: &ImportContext,
|
||||
) -> Result<Vec<Vec<u8>>> {
|
||||
let mut result: Vec<Vec<u8>> = Vec::with_capacity(gltf.buffers().len());
|
||||
for buf in gltf.buffers() {
|
||||
match buf.source() {
|
||||
gltf::buffer::Source::Bin => match data_chunk.take() {
|
||||
Some(data) => result.push(data),
|
||||
None => {
|
||||
return Err(GltfLoadError::MissingEmbeddedBin);
|
||||
}
|
||||
},
|
||||
gltf::buffer::Source::Uri(uri) => result.push(load_bin_from_uri(uri, context).await?),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn load_bin_from_uri(uri: &str, context: &ImportContext) -> Result<Vec<u8>> {
|
||||
let parsed_uri = uri::parse_uri(uri);
|
||||
match parsed_uri.scheme {
|
||||
uri::Scheme::Data if parsed_uri.data.is_some() => {
|
||||
Ok(decode_base64(parsed_uri.data.unwrap())?)
|
||||
}
|
||||
uri::Scheme::None => load_external_bin(uri, context).await,
|
||||
_ => Err(GltfLoadError::UnsupportedURI(uri.into())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_external_bin(path: &str, context: &ImportContext) -> Result<Vec<u8>> {
|
||||
let parent = context
|
||||
.model_path
|
||||
.parent()
|
||||
.ok_or(GltfLoadError::InvalidPath)?
|
||||
.to_owned();
|
||||
let path = parent.join(path);
|
||||
Ok(context.io.load_file(&path).await?)
|
||||
}
|
||||
|
||||
fn import_meshes(
|
||||
gltf: &Document,
|
||||
path: &Path,
|
||||
mats: &[MaterialResource],
|
||||
bufs: &[Vec<u8>],
|
||||
) -> Result<Vec<MeshData>> {
|
||||
let mut result: Vec<MeshData> = Vec::with_capacity(gltf.nodes().len());
|
||||
let mut stats = GeometryStatistics::default();
|
||||
for node in gltf.nodes() {
|
||||
if let Some(mesh) = node.mesh() {
|
||||
result.push(import_mesh(mesh, mats, bufs, &mut stats)?);
|
||||
}
|
||||
}
|
||||
if cfg!(feature = "mesh_analysis") {
|
||||
if stats.repeated_index_count > 0 {
|
||||
Log::err(format!(
|
||||
"{}: Model has triangles with repeated vertices: {}",
|
||||
path.to_string_lossy(),
|
||||
stats.repeated_index_count
|
||||
));
|
||||
} else {
|
||||
Log::info(format!(
|
||||
"{}: Model has no repeated vertices.",
|
||||
path.to_string_lossy()
|
||||
));
|
||||
}
|
||||
let min_length = stats.min_edge_length();
|
||||
if min_length == 0.0 {
|
||||
Log::err(format!(
|
||||
"{}: Mesh has a triangle with a zero-length edge!",
|
||||
path.to_string_lossy()
|
||||
));
|
||||
} else if min_length < f32::EPSILON {
|
||||
Log::err(format!(
|
||||
"{}: Mesh has a triangle with edge length: {}",
|
||||
path.to_string_lossy(),
|
||||
min_length
|
||||
));
|
||||
} else {
|
||||
Log::info(format!(
|
||||
"{}: Smallest triangle edge: {}",
|
||||
path.to_string_lossy(),
|
||||
min_length
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn import_mesh(
|
||||
mesh: gltf::Mesh,
|
||||
mats: &[MaterialResource],
|
||||
bufs: &[Vec<u8>],
|
||||
stats: &mut GeometryStatistics,
|
||||
) -> Result<MeshData> {
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
let morph_info = import_morph_info(&mesh)?;
|
||||
#[cfg(not(feature = "gltf_blend_shapes"))]
|
||||
let morph_info = BlendShapeInfoContainer::default();
|
||||
let mut surfs: Vec<Surface> = Vec::with_capacity(mesh.primitives().len());
|
||||
let mut blend_shapes: Option<Vec<BlendShape>> = None;
|
||||
for prim in mesh.primitives() {
|
||||
if let Some((surf, shapes)) = import_surface(prim, &morph_info, mats, bufs, stats)? {
|
||||
surfs.push(surf);
|
||||
blend_shapes.get_or_insert(shapes);
|
||||
}
|
||||
}
|
||||
Ok(MeshData {
|
||||
surfaces: surfs,
|
||||
blend_shapes: blend_shapes.unwrap_or(Vec::new()),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
fn import_morph_info(mesh: &gltf::Mesh) -> Result<BlendShapeInfoContainer> {
|
||||
let weights: &[f32] = mesh.weights().unwrap_or_default();
|
||||
let weights: Vec<f32> = weights.iter().map(|w| w * 100.0).collect();
|
||||
let extras = mesh.extras();
|
||||
let names = if let Some(extras) = extras {
|
||||
let extras: serde_json::Value = serde_json::from_str(extras.get())?;
|
||||
match extras {
|
||||
serde_json::Value::Object(map) => {
|
||||
if let Some(names) = map.get(TARGET_NAMES_KEY) {
|
||||
match names {
|
||||
serde_json::Value::Array(names) => {
|
||||
values_to_strings(names.as_slice()).unwrap_or(Vec::default())
|
||||
}
|
||||
_ => Vec::default(),
|
||||
}
|
||||
} else {
|
||||
Vec::default()
|
||||
}
|
||||
}
|
||||
_ => Vec::default(),
|
||||
}
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
if extras.is_some() && names.is_empty() {
|
||||
Log::warn(format!(
|
||||
"glTF: Unable to extract blend shape names from JSON: {}",
|
||||
extras.as_ref().unwrap().get()
|
||||
));
|
||||
}
|
||||
Ok(BlendShapeInfoContainer::new(names, weights))
|
||||
}
|
||||
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
fn values_to_strings(values: &[serde_json::Value]) -> Option<Vec<String>> {
|
||||
let mut result: Vec<String> = Vec::with_capacity(values.len());
|
||||
for v in values {
|
||||
if let serde_json::Value::String(str) = v {
|
||||
result.push(str.clone());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn import_surface(
|
||||
prim: gltf::Primitive,
|
||||
morph_info: &BlendShapeInfoContainer,
|
||||
mats: &[MaterialResource],
|
||||
bufs: &[Vec<u8>],
|
||||
stats: &mut GeometryStatistics,
|
||||
) -> Result<Option<(Surface, Vec<BlendShape>)>> {
|
||||
if let Some(data) = build_surface_data(&prim, morph_info, bufs, stats)? {
|
||||
let mut blend_shapes = Vec::new();
|
||||
if let Some(shape_con) = data.blend_shapes_container.as_ref() {
|
||||
blend_shapes = shape_con.blend_shapes.clone();
|
||||
}
|
||||
let mut surf = Surface::new(SurfaceSharedData::new(data));
|
||||
if let Some(mat_index) = prim.material().index() {
|
||||
surf.set_material(
|
||||
mats.get(mat_index)
|
||||
.ok_or(GltfLoadError::InvalidIndex)?
|
||||
.clone(),
|
||||
);
|
||||
Ok(Some((surf, blend_shapes)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn import_nodes(
|
||||
doc: &gltf::Document,
|
||||
graph: &mut Graph,
|
||||
imports: &ImportResults,
|
||||
) -> Result<Vec<NodeFamily>> {
|
||||
let skins: &[SkinData] = imports.skins.as_ref().unwrap().as_slice();
|
||||
let mut result: Vec<NodeFamily> = Vec::with_capacity(doc.nodes().len());
|
||||
for node in doc.nodes() {
|
||||
result.push(build_node_family(&node, skins, graph, imports)?);
|
||||
}
|
||||
for node in doc.nodes() {
|
||||
let family: &NodeFamily = result
|
||||
.get(node.index())
|
||||
.ok_or(GltfLoadError::InvalidIndex)?;
|
||||
if let Some(mesh) = graph[family.main_node].cast_mut::<Mesh>() {
|
||||
assign_bones_to_surfaces(node, mesh, result.as_slice())?;
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn build_node_family(
|
||||
node: &gltf::Node,
|
||||
skins: &[SkinData],
|
||||
graph: &mut Graph,
|
||||
imports: &ImportResults,
|
||||
) -> Result<NodeFamily> {
|
||||
let node_index = node.index();
|
||||
let skin_iter = SkinBoneIter::new(skins).filter(move |sb| sb.bone.node_index == node_index);
|
||||
let mut bones: Vec<SkinBonePair> = Vec::new();
|
||||
let mut new_handle: Option<Handle<Node>> = None;
|
||||
let mut bone_children: Vec<SkinNodePair> = Vec::new();
|
||||
let name = node.name().unwrap_or("");
|
||||
for pair in skin_iter {
|
||||
if bones.is_empty() {
|
||||
// Our first bone. Set the inv_bind_pose of the main node.
|
||||
// We only create children if we later find an inv_bind_pose that does not match this.
|
||||
let new_node = import_node(node, pair.bone.inv_bind_pose, imports)?;
|
||||
let _ = new_handle.insert(graph.add_node(new_node));
|
||||
// Record that we have seen this inv_bind_pose.
|
||||
bones.push(pair);
|
||||
} else if let Some(p) = bones.iter().find(|p| p.bone == pair.bone) {
|
||||
// We have a previously existing bone with the exact same inv_bind_pose.
|
||||
// Do not record having seen this inv_bind_pose.
|
||||
// Let the already recorded inv_bind_pose stand in for all bones with this inv_bind_pose.
|
||||
let prev_skin_index = p.skin_index;
|
||||
if let Some(prev_pair) = bone_children
|
||||
.iter()
|
||||
.find(|b| b.skin_index == prev_skin_index)
|
||||
{
|
||||
// We have a child for the skin of the previously existing bone. Re-use that child for this skin.
|
||||
bone_children.push(SkinNodePair {
|
||||
skin_index: pair.skin_index,
|
||||
node: prev_pair.node,
|
||||
});
|
||||
}
|
||||
// Otherwise, the previously existing bone must be using the main node, so do nothing.
|
||||
} else {
|
||||
// We have a never-seen-before inv_bind_pose, so create a child for that inv_bind_pose.
|
||||
let skin_index = pair.skin_index;
|
||||
let base_builder = BaseBuilder::new()
|
||||
.with_name(format!("{}:{}", name, skin_index))
|
||||
.with_inv_bind_pose_transform(pair.bone.inv_bind_pose);
|
||||
let handle: Handle<Node> = graph.add_node(PivotBuilder::new(base_builder).build_node());
|
||||
bone_children.push(SkinNodePair {
|
||||
skin_index,
|
||||
node: handle,
|
||||
});
|
||||
graph.link_nodes(handle, new_handle.unwrap());
|
||||
// Record that we have seen this inv_bind_pose.
|
||||
bones.push(pair);
|
||||
}
|
||||
}
|
||||
if let Some(handle) = new_handle {
|
||||
Ok(NodeFamily {
|
||||
main_node: handle,
|
||||
bone_children,
|
||||
})
|
||||
} else {
|
||||
Ok(NodeFamily {
|
||||
main_node: graph.add_node(import_node(node, Matrix4::identity(), imports)?),
|
||||
bone_children: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_bones_to_surfaces(
|
||||
node: gltf::Node,
|
||||
mesh: &mut Mesh,
|
||||
families: &[NodeFamily],
|
||||
) -> Result<()> {
|
||||
if let Some(skin) = node.skin() {
|
||||
let skin_index = skin.index();
|
||||
let mut bones: Vec<Handle<Node>> = Vec::with_capacity(skin.joints().len());
|
||||
for joint in skin.joints() {
|
||||
let joint_family = families
|
||||
.get(joint.index())
|
||||
.ok_or(GltfLoadError::InvalidIndex)?;
|
||||
let bone_children = &joint_family.bone_children;
|
||||
let handle =
|
||||
if let Some(pair) = bone_children.iter().find(|p| p.skin_index == skin_index) {
|
||||
pair.node
|
||||
} else {
|
||||
joint_family.main_node
|
||||
};
|
||||
bones.push(handle);
|
||||
}
|
||||
for surf in mesh.surfaces_mut() {
|
||||
surf.bones.set_value_and_mark_modified(bones.clone());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_node(
|
||||
node: &gltf::Node,
|
||||
inv_bind_pose: Matrix4<f32>,
|
||||
imports: &ImportResults,
|
||||
) -> Result<Node> {
|
||||
let meshes: &[MeshData] = imports.meshes.as_ref().unwrap().as_slice();
|
||||
let trans = node.transform().decomposed();
|
||||
let trans_builder: TransformBuilder = TransformBuilder::new()
|
||||
.with_local_position(trans.0.into())
|
||||
.with_local_rotation(Unit::new_normalize(trans.1.into()))
|
||||
.with_local_scale(trans.2.into());
|
||||
let name = node.name().unwrap_or("");
|
||||
let base_builder = BaseBuilder::new()
|
||||
.with_name(name)
|
||||
.with_local_transform(trans_builder.build())
|
||||
.with_inv_bind_pose_transform(inv_bind_pose);
|
||||
if let Some(mesh) = node.mesh() {
|
||||
let mut mesh_builder = MeshBuilder::new(base_builder);
|
||||
let mesh = meshes
|
||||
.get(mesh.index())
|
||||
.ok_or(GltfLoadError::InvalidIndex)?;
|
||||
mesh_builder = mesh_builder.with_blend_shapes(mesh.blend_shapes.clone());
|
||||
mesh_builder = mesh_builder.with_surfaces(mesh.surfaces.clone());
|
||||
Ok(mesh_builder.build_node())
|
||||
} else {
|
||||
Ok(PivotBuilder::new(base_builder).build_node())
|
||||
}
|
||||
}
|
||||
|
||||
fn link_child_nodes(doc: &Document, graph: &mut Graph, imports: &ImportResults) -> Result<()> {
|
||||
let families: &[NodeFamily] = imports.families.as_ref().unwrap().as_slice();
|
||||
for node in doc.nodes() {
|
||||
let parent_family = families
|
||||
.get(node.index())
|
||||
.ok_or(GltfLoadError::InvalidIndex)?;
|
||||
for child in node.children() {
|
||||
let child_family = families
|
||||
.get(child.index())
|
||||
.ok_or(GltfLoadError::InvalidIndex)?;
|
||||
graph.link_nodes(child_family.main_node, parent_family.main_node);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_skins(doc: &gltf::Document, imports: &ImportResults) -> Result<Vec<SkinData>> {
|
||||
let mut result: Vec<SkinData> = Vec::with_capacity(doc.skins().len());
|
||||
for skin in doc.skins() {
|
||||
let bone_node_indices = skin.joints().map(|n| n.index());
|
||||
let bone_pairs = {
|
||||
let reader = skin.reader(imports.get_buffer_data_access());
|
||||
if let Some(iter) = reader.read_inverse_bind_matrices() {
|
||||
bone_node_indices
|
||||
.zip(iter.map(Matrix4::from))
|
||||
.map(SkinBone::from)
|
||||
.collect()
|
||||
} else {
|
||||
bone_node_indices
|
||||
.zip(std::iter::repeat(Matrix4::identity()))
|
||||
.map(SkinBone::from)
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
result.push(bone_pairs);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
use crate::core::log::Log;
|
||||
use crate::core::pool::Handle;
|
||||
use crate::core::NameProvider;
|
||||
use crate::fxhash::{FxHashMap, FxHashSet};
|
||||
use crate::graph::BaseSceneGraph;
|
||||
use crate::graph::SceneGraph;
|
||||
use crate::scene::graph::Graph;
|
||||
use crate::scene::node::Node;
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NodeName {
|
||||
handle: Handle<Node>,
|
||||
name: RefCell<String>,
|
||||
}
|
||||
|
||||
/// Attempt to remove duplicate names by renaming the nodes of the graph
|
||||
/// without depending on the order of sibling nodes.
|
||||
///
|
||||
/// If a parent node has the same name as its child node, the child node
|
||||
/// is renamed to "parent_name+1". Grandchildren are renamed to "parent_name+2"
|
||||
/// and so on.
|
||||
///
|
||||
/// If two nodes have the same name, but their parents have different names,
|
||||
/// the nodes are renamed to "node_name+parentA" and "node_name+parentB".
|
||||
///
|
||||
/// If the nodes have parents with the same names, then grandparent names may
|
||||
/// be used if the grandparents have different names. If the whole ancestry
|
||||
/// of the nodes is searched without finding ancestors with distinct names
|
||||
/// then the nodes are not renamed.
|
||||
///
|
||||
/// If after the whole procedure there is found to still be nodes with duplicate names,
|
||||
/// an error is logged to alert the user to the problem.
|
||||
pub fn resolve_name_conflicts(path: &Path, graph: &mut Graph) {
|
||||
let node_sets: Vec<Vec<Handle<Node>>> = build_node_sets_from_graph(graph);
|
||||
for nodes in node_sets {
|
||||
if nodes.len() > 1 {
|
||||
resolve_conflict(nodes, graph);
|
||||
}
|
||||
}
|
||||
// Check if conflicts have actually been resolved.
|
||||
let mut name_set: FxHashSet<&str> = FxHashSet::default();
|
||||
for node in graph.linear_iter() {
|
||||
if !name_set.insert(node.name()) {
|
||||
Log::err(format!(
|
||||
"A node with existing name {} was found during the load of {} resource! \
|
||||
Do **NOT IGNORE** this message, please fix names in your model, otherwise \
|
||||
engine won't be able to correctly restore data from your resource!",
|
||||
node.name(),
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_node_sets_from_graph(graph: &Graph) -> Vec<Vec<Handle<Node>>> {
|
||||
let mut name_map: FxHashMap<&str, Vec<Handle<Node>>> = FxHashMap::default();
|
||||
for (handle, node) in graph.pair_iter() {
|
||||
let name = node.name();
|
||||
let list = name_map
|
||||
.entry(name)
|
||||
.or_insert_with(|| Vec::with_capacity(1));
|
||||
list.push(handle);
|
||||
}
|
||||
name_map.into_values().collect()
|
||||
}
|
||||
|
||||
fn build_node_sets_from_list(nodes: &Vec<NodeName>) -> Vec<Vec<&NodeName>> {
|
||||
let mut name_map: FxHashMap<&str, Vec<&NodeName>> = FxHashMap::default();
|
||||
let names: Vec<_> = nodes.iter().map(|n| n.name.borrow()).collect();
|
||||
for (node, name) in nodes.iter().zip(names.iter()) {
|
||||
let list = name_map
|
||||
.entry(name.as_str())
|
||||
.or_insert_with(|| Vec::with_capacity(1));
|
||||
list.push(node);
|
||||
}
|
||||
name_map.into_values().collect()
|
||||
}
|
||||
|
||||
fn resolve_conflict(handles: Vec<Handle<Node>>, graph: &mut Graph) {
|
||||
// Resolve cases where parents have the same names as children.
|
||||
let mut node_names = build_node_name_list(handles.as_slice(), graph);
|
||||
for NodeName { handle, name } in node_names.iter_mut() {
|
||||
let d = count_ancestor_depth(*handle, handles.as_slice(), graph);
|
||||
if d > 0 {
|
||||
name.borrow_mut().push('+');
|
||||
name.borrow_mut().push_str(d.to_string().as_str());
|
||||
}
|
||||
}
|
||||
// Build lists of nodes which still have duplicate names
|
||||
let node_sets = build_node_sets_from_list(&mut node_names);
|
||||
// Add ancestor names if that will eliminate duplicates.
|
||||
for set in node_sets {
|
||||
if set.len() > 1 {
|
||||
if let Some(names) = find_distinct_ancestor_names(set.as_slice(), graph) {
|
||||
for (node, name) in set.iter().zip(names.iter()) {
|
||||
node.name.borrow_mut().push('+');
|
||||
node.name.borrow_mut().push_str(*name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for node in node_names {
|
||||
graph[node.handle].set_name(node.name.borrow().as_str());
|
||||
}
|
||||
}
|
||||
|
||||
fn build_node_name_list(handles: &[Handle<Node>], graph: &Graph) -> Vec<NodeName> {
|
||||
handles
|
||||
.iter()
|
||||
.map(|h| NodeName {
|
||||
handle: *h,
|
||||
name: RefCell::new(graph[*h].name().to_owned()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn find_distinct_ancestor_names<'a>(
|
||||
node_names: &[&NodeName],
|
||||
graph: &'a Graph,
|
||||
) -> Option<Vec<&'a str>> {
|
||||
for i in 1.. {
|
||||
if let Some(names) = get_ancestor_names(node_names, i, graph) {
|
||||
if !contains_duplicate_name(names.as_slice()) {
|
||||
return Some(names);
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_ancestor_names<'a>(
|
||||
node_names: &[&NodeName],
|
||||
depth: usize,
|
||||
graph: &'a Graph,
|
||||
) -> Option<Vec<&'a str>> {
|
||||
let mut result: Vec<&'a str> = Vec::with_capacity(node_names.len());
|
||||
for n in node_names {
|
||||
if let Some(n) = get_ancestor_name(n.handle, depth, graph) {
|
||||
result.push(n);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn get_ancestor_name<'a>(
|
||||
mut handle: Handle<Node>,
|
||||
depth: usize,
|
||||
graph: &'a Graph,
|
||||
) -> Option<&'a str> {
|
||||
for _ in 0..depth {
|
||||
if let Some(n) = graph.try_get(handle) {
|
||||
handle = n.parent();
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
graph.try_get(handle).map(Node::name)
|
||||
}
|
||||
|
||||
fn contains_duplicate_name(names: &[&str]) -> bool {
|
||||
let mut iter = names.iter();
|
||||
while let Some(n) = iter.next() {
|
||||
let mut rest = iter.clone();
|
||||
if rest.any(|x| *n == *x) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn count_ancestor_depth(mut handle: Handle<Node>, list: &[Handle<Node>], graph: &Graph) -> usize {
|
||||
let mut count: usize = 0;
|
||||
loop {
|
||||
if let Some(node) = graph.try_get(handle) {
|
||||
handle = node.parent();
|
||||
if list.iter().any(|h| *h == handle) {
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
#![allow(unused)]
|
||||
use crate::asset::core::math::TriangleDefinition;
|
||||
use crate::core::algebra::{Vector2, Vector3, Vector4};
|
||||
use crate::core::log::Log;
|
||||
use crate::core::math::Vector3Ext;
|
||||
use crate::fxhash::FxHashMap;
|
||||
use crate::scene::mesh;
|
||||
use crate::scene::mesh::buffer::{
|
||||
self, TriangleBuffer, ValidationError, VertexAttributeUsage, VertexBuffer, VertexReadTrait,
|
||||
VertexTrait,
|
||||
};
|
||||
use crate::scene::mesh::surface::{InputBlendShapeData, SurfaceData};
|
||||
use crate::scene::mesh::vertex::{AnimatedVertex, SimpleVertex, StaticVertex};
|
||||
use gltf::buffer::Buffer;
|
||||
use gltf::mesh::util::ReadColors;
|
||||
use gltf::mesh::util::ReadIndices;
|
||||
use gltf::mesh::util::ReadJoints;
|
||||
use gltf::mesh::util::ReadTexCoords;
|
||||
use gltf::mesh::Mode;
|
||||
use gltf::mesh::Reader as GltfReader;
|
||||
use gltf::mesh::Semantic;
|
||||
use gltf::Accessor;
|
||||
use gltf::Primitive;
|
||||
use half::f16;
|
||||
|
||||
use super::iter::*;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::num::TryFromIntError;
|
||||
|
||||
/// This type represents any error that may occur while importing mesh data from glTF.
|
||||
#[derive(Debug)]
|
||||
pub enum SurfaceDataError {
|
||||
/// The mesh data had more vertex positions than some other vertex attributes.
|
||||
/// For example, there might be positions for 10 vertices but normals for only 9 vertices.
|
||||
CountMismatch,
|
||||
/// The mesh vertex data in the glTF files does not include position vectors.
|
||||
MissingPosition,
|
||||
/// The mesh vertex data in the glTF files does not include normal vectors.
|
||||
MissingNormal,
|
||||
/// The mesh vertex data in the glTF files does not include UV coordinates.
|
||||
MissingTexCoords,
|
||||
/// The mesh vertex data in the glTF files does not include bone weight values.
|
||||
MissingBoneWeight,
|
||||
/// The mesh vertex data in the glTF files does not include bone index values.
|
||||
MissingBoneIndex,
|
||||
/// Bone indices in glTF format can be stored as u8 or u16, but Fyrox only supports
|
||||
/// bone indices in u8. This error is produced if an index is found which does not fit
|
||||
/// into u8.
|
||||
InvalidBoneIndex,
|
||||
/// The glTF format includes options for drawing points and lines, but this module
|
||||
/// only supports drawing triangles.
|
||||
InvalidMode,
|
||||
/// An internal error in a glTF file. The glTF format uses indices to allow one
|
||||
/// resource to reference another within the same file. This error indicates that
|
||||
/// one of those indices was out-of-bounds. This should never happen.
|
||||
InvalidIndex,
|
||||
/// An error in converting u32 to usize, or from usize to u32.
|
||||
Int(TryFromIntError),
|
||||
/// Depending on the geometry type, certain numbers of vertices are errors.
|
||||
/// For example, if the geometry is a list of triangles, then the number of vertices
|
||||
/// needs to be a multiple of three. This error indicates a glTF file had the wrong
|
||||
/// number of vertices in some mesh.
|
||||
InvalidVertexCount(GeometryType, u32),
|
||||
/// An error may occur while constructing a mesh buffer.
|
||||
Validation(ValidationError),
|
||||
/// An error may occur while writing mesh data.
|
||||
Fetch(buffer::VertexFetchError),
|
||||
}
|
||||
|
||||
impl From<ValidationError> for SurfaceDataError {
|
||||
fn from(error: ValidationError) -> Self {
|
||||
SurfaceDataError::Validation(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryFromIntError> for SurfaceDataError {
|
||||
fn from(error: TryFromIntError) -> Self {
|
||||
SurfaceDataError::Int(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<buffer::VertexFetchError> for SurfaceDataError {
|
||||
fn from(error: buffer::VertexFetchError) -> Self {
|
||||
SurfaceDataError::Fetch(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GeometryType {
|
||||
Triangles,
|
||||
TriangleStrip,
|
||||
TriangleFan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct BlendShapeInfo {
|
||||
pub name: String,
|
||||
pub default_weight: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct BlendShapeInfoContainer {
|
||||
names: Vec<String>,
|
||||
weights: Vec<f32>,
|
||||
}
|
||||
|
||||
impl BlendShapeInfoContainer {
|
||||
pub fn new(names: Vec<String>, weights: Vec<f32>) -> Self {
|
||||
BlendShapeInfoContainer { names, weights }
|
||||
}
|
||||
pub fn get(&self, index: usize) -> BlendShapeInfo {
|
||||
BlendShapeInfo {
|
||||
name: self
|
||||
.names
|
||||
.get(index)
|
||||
.map(String::clone)
|
||||
.unwrap_or_else(|| index.to_string()),
|
||||
default_weight: self.weights.get(index).map(f32::clone).unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GeometryStatistics {
|
||||
pub min_edge_length_squared: f32,
|
||||
pub repeated_index_count: usize,
|
||||
}
|
||||
|
||||
impl GeometryStatistics {
|
||||
pub fn update_length(&mut self, len: f32) {
|
||||
if len < self.min_edge_length_squared {
|
||||
self.min_edge_length_squared = len;
|
||||
}
|
||||
}
|
||||
pub fn min_edge_length(&self) -> f32 {
|
||||
self.min_edge_length_squared.sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GeometryStatistics {
|
||||
fn default() -> Self {
|
||||
GeometryStatistics {
|
||||
min_edge_length_squared: f32::INFINITY,
|
||||
repeated_index_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, SurfaceDataError>;
|
||||
const DEFAULT_TANGENT: Vector4<f32> = Vector4::new(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum IndexData {
|
||||
Buffer(Vec<u32>),
|
||||
Direct(u32),
|
||||
}
|
||||
|
||||
impl IndexData {
|
||||
fn get_tri(&self, a: u32, b: u32, c: u32) -> Result<[u32; 3]> {
|
||||
Ok([self.get(a)?, self.get(b)?, self.get(c)?])
|
||||
}
|
||||
fn get(&self, source_index: u32) -> Result<u32> {
|
||||
match self {
|
||||
IndexData::Buffer(data) => Ok(data
|
||||
.get(usize::try_from(source_index)?)
|
||||
.ok_or(SurfaceDataError::InvalidIndex)?
|
||||
.clone()),
|
||||
IndexData::Direct(size) if source_index < *size => Ok(source_index),
|
||||
_ => Err(SurfaceDataError::InvalidIndex),
|
||||
}
|
||||
}
|
||||
fn len(&self) -> Result<u32> {
|
||||
match self {
|
||||
IndexData::Buffer(data) => Ok(u32::try_from(data.len())?),
|
||||
IndexData::Direct(size) => Ok(*size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_surface_data(
|
||||
primitive: &Primitive,
|
||||
morph_info: &BlendShapeInfoContainer,
|
||||
buffers: &[Vec<u8>],
|
||||
stats: &mut GeometryStatistics,
|
||||
) -> Result<Option<SurfaceData>> {
|
||||
match primitive.mode() {
|
||||
Mode::Points => {
|
||||
return Ok(None);
|
||||
}
|
||||
Mode::Lines => {
|
||||
return Ok(None);
|
||||
}
|
||||
Mode::LineLoop => {
|
||||
return Ok(None);
|
||||
}
|
||||
Mode::LineStrip => {
|
||||
return Ok(None);
|
||||
}
|
||||
Mode::Triangles => (),
|
||||
Mode::TriangleStrip => (),
|
||||
Mode::TriangleFan => (),
|
||||
}
|
||||
let vs: VertexBuffer = build_vertex_data(primitive, buffers)?;
|
||||
let tris: TriangleBuffer = build_triangle_data(primitive, vs.vertex_count(), buffers)?;
|
||||
#[cfg(feature = "mesh_analysis")]
|
||||
update_statistics(&vs, &tris, stats);
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
let morphs = build_morph_data(primitive, morph_info, buffers)?;
|
||||
#[cfg(not(feature = "blend_shapes"))]
|
||||
let morphs: Vec<InputBlendShapeData> = Vec::new();
|
||||
let mut surf = if !morphs.is_empty() {
|
||||
let shapes = mesh::surface::BlendShapesContainer::from_lists(&vs, morphs.as_slice());
|
||||
let mut surf = SurfaceData::new(vs, tris, false);
|
||||
surf.blend_shapes_container.insert(shapes);
|
||||
surf
|
||||
} else {
|
||||
SurfaceData::new(vs, tris, false)
|
||||
};
|
||||
let has_tex = primitive.get(&Semantic::TexCoords(0)).is_some();
|
||||
let has_norm = primitive.get(&Semantic::Normals).is_some();
|
||||
let has_tang = primitive.get(&Semantic::Tangents).is_some();
|
||||
if has_tex && !has_norm {
|
||||
surf.calculate_normals();
|
||||
surf.calculate_tangents();
|
||||
} else if has_tex && has_norm && !has_tang {
|
||||
surf.calculate_tangents();
|
||||
}
|
||||
Ok(Some(surf))
|
||||
}
|
||||
|
||||
#[cfg(feature = "mesh_analysis")]
|
||||
fn update_statistics(
|
||||
vs: &VertexBuffer,
|
||||
tris: &TriangleBuffer,
|
||||
stats: &mut GeometryStatistics,
|
||||
) -> Result<()> {
|
||||
for tri in tris.iter() {
|
||||
let indices = tri.0;
|
||||
if tri[0] == tri[1] || tri[1] == tri[2] || tri[0] == tri[2] {
|
||||
stats.repeated_index_count += 1;
|
||||
}
|
||||
stats.update_length(edge_length_squared(tri[0], tri[1], vs)?);
|
||||
stats.update_length(edge_length_squared(tri[1], tri[2], vs)?);
|
||||
stats.update_length(edge_length_squared(tri[0], tri[2], vs)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn edge_length_squared(a: u32, b: u32, vs: &VertexBuffer) -> Result<f32> {
|
||||
let a = usize::try_from(a)?;
|
||||
let b = usize::try_from(b)?;
|
||||
let a = vs.get(a).ok_or(SurfaceDataError::InvalidIndex)?;
|
||||
let b = vs.get(b).ok_or(SurfaceDataError::InvalidIndex)?;
|
||||
let a = a.read_3_f32(VertexAttributeUsage::Position)?;
|
||||
let b = b.read_3_f32(VertexAttributeUsage::Position)?;
|
||||
Ok(a.sqr_distance(&b))
|
||||
}
|
||||
|
||||
#[cfg(feature = "gltf_blend_shapes")]
|
||||
fn build_morph_data(
|
||||
primitive: &Primitive,
|
||||
morph_info: &BlendShapeInfoContainer,
|
||||
buffers: &[Vec<u8>],
|
||||
) -> Result<Vec<InputBlendShapeData>> {
|
||||
let reader = primitive.reader(|buf: Buffer| buffers.get(buf.index()).map(Vec::as_slice));
|
||||
let reader = reader.read_morph_targets();
|
||||
let mut result: Vec<InputBlendShapeData> = Vec::new();
|
||||
for (i, (pos, norm, tang)) in reader.enumerate() {
|
||||
let info = morph_info.get(i);
|
||||
let positions = if let Some(iter) = pos {
|
||||
iter_to_map(iter)
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
let normals = if let Some(iter) = norm {
|
||||
iter_to_map(iter)
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
let tangents = if let Some(iter) = tang {
|
||||
iter_to_map(iter)
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
result.push(InputBlendShapeData {
|
||||
default_weight: info.default_weight,
|
||||
name: info.name,
|
||||
positions,
|
||||
normals,
|
||||
tangents,
|
||||
});
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn iter_to_map<I>(iter: I) -> FxHashMap<u32, Vector3<f16>>
|
||||
where
|
||||
I: Iterator<Item = [f32; 3]>,
|
||||
{
|
||||
let mut map: FxHashMap<u32, Vector3<f16>> = FxHashMap::default();
|
||||
for (i, v) in iter.enumerate() {
|
||||
if v == [0.0; 3] {
|
||||
continue;
|
||||
}
|
||||
if let Ok(index) = u32::try_from(i) {
|
||||
let v: [f16; 3] = v.map(f16::from_f32);
|
||||
map.insert(index, Vector3::<f16>::from(v));
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
fn build_vertex_data(primitive: &Primitive, buffers: &[Vec<u8>]) -> Result<VertexBuffer> {
|
||||
build_vertex_data_with(primitive, |buf: Buffer| {
|
||||
buffers.get(buf.index()).map(Vec::as_slice)
|
||||
})
|
||||
}
|
||||
|
||||
fn build_triangle_data(
|
||||
primitive: &Primitive,
|
||||
vertex_count: u32,
|
||||
buffers: &[Vec<u8>],
|
||||
) -> Result<TriangleBuffer> {
|
||||
let reader = primitive.reader(|buf: Buffer| buffers.get(buf.index()).map(Vec::as_slice));
|
||||
let index_data = match reader.read_indices() {
|
||||
Some(index_reader) => IndexData::Buffer(index_reader.into_u32().collect()),
|
||||
None => IndexData::Direct(vertex_count),
|
||||
};
|
||||
let tris: Vec<TriangleDefinition> = (match primitive.mode() {
|
||||
Mode::Points => Err(SurfaceDataError::InvalidMode),
|
||||
Mode::Lines => Err(SurfaceDataError::InvalidMode),
|
||||
Mode::LineLoop => Err(SurfaceDataError::InvalidMode),
|
||||
Mode::LineStrip => Err(SurfaceDataError::InvalidMode),
|
||||
Mode::Triangles => build_triangles(&index_data),
|
||||
Mode::TriangleStrip => build_triangle_strip(&index_data),
|
||||
Mode::TriangleFan => build_triangle_fan(&index_data),
|
||||
})?;
|
||||
Ok(TriangleBuffer::new(tris))
|
||||
}
|
||||
|
||||
fn build_triangles(data: &IndexData) -> Result<Vec<TriangleDefinition>> {
|
||||
let vertex_count = data.len()?;
|
||||
if vertex_count == 0 || vertex_count % 3 != 0 {
|
||||
return Err(SurfaceDataError::InvalidVertexCount(
|
||||
GeometryType::Triangles,
|
||||
vertex_count,
|
||||
));
|
||||
}
|
||||
let tri_count: u32 = vertex_count / 3;
|
||||
let mut tris: Vec<TriangleDefinition> = Vec::with_capacity(tri_count as usize);
|
||||
for i in 0..tri_count {
|
||||
let v: u32 = i * 3;
|
||||
tris.push(TriangleDefinition(data.get_tri(v, v + 1, v + 2)?));
|
||||
}
|
||||
Ok(tris)
|
||||
}
|
||||
fn build_triangle_strip(data: &IndexData) -> Result<Vec<TriangleDefinition>> {
|
||||
let vertex_count = data.len()?;
|
||||
if vertex_count < 3 {
|
||||
return Err(SurfaceDataError::InvalidVertexCount(
|
||||
GeometryType::TriangleStrip,
|
||||
vertex_count,
|
||||
));
|
||||
}
|
||||
let tri_count: u32 = vertex_count - 2;
|
||||
let mut tris: Vec<TriangleDefinition> = Vec::with_capacity(tri_count as usize);
|
||||
for i in 0..tri_count {
|
||||
let odd = i % 2;
|
||||
tris.push(TriangleDefinition(data.get_tri(
|
||||
i,
|
||||
i + 1 + odd,
|
||||
i + 2 - odd,
|
||||
)?));
|
||||
}
|
||||
Ok(tris)
|
||||
}
|
||||
fn build_triangle_fan(data: &IndexData) -> Result<Vec<TriangleDefinition>> {
|
||||
let vertex_count = data.len()?;
|
||||
if vertex_count < 3 {
|
||||
return Err(SurfaceDataError::InvalidVertexCount(
|
||||
GeometryType::TriangleFan,
|
||||
vertex_count,
|
||||
));
|
||||
}
|
||||
let tri_count: u32 = vertex_count - 2;
|
||||
let mut tris: Vec<TriangleDefinition> = Vec::with_capacity(tri_count as usize);
|
||||
for i in 0..tri_count {
|
||||
tris.push(TriangleDefinition(data.get_tri(i + 1, i + 2, 0)?));
|
||||
}
|
||||
Ok(tris)
|
||||
}
|
||||
|
||||
fn build_vertex_data_with<'a, 's, F>(
|
||||
primitive: &'a Primitive,
|
||||
get_buffer_data: F,
|
||||
) -> Result<VertexBuffer>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let reader = primitive.reader(get_buffer_data.clone());
|
||||
if reader.read_weights(0).is_some() {
|
||||
let vs: Vec<AnimatedVertex> = AnimatedVertex::convert(primitive, get_buffer_data)?;
|
||||
Ok(VertexBuffer::new(vs.len(), vs)?)
|
||||
} else if reader.read_normals().is_some() {
|
||||
let vs: Vec<StaticVertex> = StaticVertex::convert(primitive, get_buffer_data)?;
|
||||
Ok(VertexBuffer::new(vs.len(), vs)?)
|
||||
} else {
|
||||
let vs: Vec<SimpleVertex> = SimpleVertex::convert(primitive, get_buffer_data)?;
|
||||
Ok(VertexBuffer::new(vs.len(), vs)?)
|
||||
}
|
||||
}
|
||||
|
||||
trait GltfVertexConvert: VertexTrait {
|
||||
fn convert<'a, 's, F>(primitive: &'a Primitive, get_buffer_data: F) -> Result<Vec<Self>>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>;
|
||||
}
|
||||
|
||||
impl GltfVertexConvert for SimpleVertex {
|
||||
fn convert<'a, 's, F>(primitive: &'a Primitive, get_buffer_data: F) -> Result<Vec<Self>>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let reader = primitive.reader(get_buffer_data);
|
||||
if let Some(iter) = reader.read_positions() {
|
||||
Ok(iter
|
||||
.map(Vector3::from)
|
||||
.map(|v| SimpleVertex { position: v })
|
||||
.collect())
|
||||
} else {
|
||||
Err(SurfaceDataError::MissingPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GltfVertexConvert for StaticVertex {
|
||||
fn convert<'a, 's, F>(primitive: &'a Primitive, get_buffer_data: F) -> Result<Vec<Self>>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let reader = primitive.reader(get_buffer_data);
|
||||
let mut pos_iter = reader
|
||||
.read_positions()
|
||||
.ok_or(SurfaceDataError::MissingPosition)?;
|
||||
let mut norm_iter = reader
|
||||
.read_normals()
|
||||
.ok_or(SurfaceDataError::MissingNormal)?;
|
||||
let mut tang_iter = reader.read_tangents();
|
||||
let mut uv_iter = reader
|
||||
.read_tex_coords(0)
|
||||
.ok_or(SurfaceDataError::MissingTexCoords)?
|
||||
.into_f32();
|
||||
let mut result: Vec<StaticVertex> = Vec::with_capacity(pos_iter.len());
|
||||
for pos in pos_iter {
|
||||
let pos: Vector3<f32> = Vector3::from(pos);
|
||||
let norm: Option<Vector3<f32>> = norm_iter.next().map(Vector3::from);
|
||||
let uv: Option<Vector2<f32>> = uv_iter.next().map(Vector2::from);
|
||||
let tang: Option<Vector4<f32>> = if let Some(iter) = tang_iter.as_mut() {
|
||||
iter.next().map(Vector4::from)
|
||||
} else {
|
||||
Some(DEFAULT_TANGENT)
|
||||
};
|
||||
if let (Some(normal), Some(tex_coord), Some(tangent)) = (norm, uv, tang) {
|
||||
result.push(StaticVertex {
|
||||
position: pos,
|
||||
normal,
|
||||
tex_coord,
|
||||
tangent,
|
||||
});
|
||||
} else {
|
||||
return Err(SurfaceDataError::CountMismatch);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl GltfVertexConvert for AnimatedVertex {
|
||||
fn convert<'a, 's, F>(primitive: &'a Primitive, get_buffer_data: F) -> Result<Vec<Self>>
|
||||
where
|
||||
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
|
||||
{
|
||||
let reader = primitive.reader(get_buffer_data);
|
||||
let mut pos_iter = reader
|
||||
.read_positions()
|
||||
.ok_or(SurfaceDataError::MissingPosition)?;
|
||||
let mut norm_iter = reader
|
||||
.read_normals()
|
||||
.ok_or(SurfaceDataError::MissingNormal)?;
|
||||
let mut tang_iter = reader.read_tangents();
|
||||
let mut uv_iter = reader
|
||||
.read_tex_coords(0)
|
||||
.ok_or(SurfaceDataError::MissingTexCoords)?
|
||||
.into_f32();
|
||||
let mut wgt_iter = reader
|
||||
.read_weights(0)
|
||||
.ok_or(SurfaceDataError::MissingBoneWeight)?
|
||||
.into_f32();
|
||||
let mut jnt_iter = reader
|
||||
.read_joints(0)
|
||||
.ok_or(SurfaceDataError::MissingBoneIndex)?;
|
||||
let mut result: Vec<AnimatedVertex> = Vec::with_capacity(pos_iter.len());
|
||||
for pos in pos_iter {
|
||||
let pos: Vector3<f32> = Vector3::from(pos);
|
||||
let norm: Option<Vector3<f32>> = norm_iter.next().map(Vector3::from);
|
||||
let uv: Option<Vector2<f32>> = uv_iter.next().map(Vector2::from);
|
||||
let bone_weights: Option<[f32; 4]> = wgt_iter.next();
|
||||
let bone_indices: Option<[u8; 4]> = read_valid_index(&mut jnt_iter)?;
|
||||
let tang: Option<Vector4<f32>> = if let Some(iter) = tang_iter.as_mut() {
|
||||
iter.next().map(Vector4::from)
|
||||
} else {
|
||||
Some(DEFAULT_TANGENT)
|
||||
};
|
||||
if let (
|
||||
Some(normal),
|
||||
Some(tex_coord),
|
||||
Some(tangent),
|
||||
Some(bone_weights),
|
||||
Some(bone_indices),
|
||||
) = (norm, uv, tang, bone_weights, bone_indices)
|
||||
{
|
||||
result.push(AnimatedVertex {
|
||||
position: pos,
|
||||
normal,
|
||||
tex_coord,
|
||||
tangent,
|
||||
bone_weights,
|
||||
bone_indices,
|
||||
});
|
||||
} else {
|
||||
return Err(SurfaceDataError::CountMismatch);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_valid_index(reader: &mut ReadJoints) -> Result<Option<[u8; 4]>> {
|
||||
match reader {
|
||||
ReadJoints::U8(iter) => Ok(iter.next()),
|
||||
ReadJoints::U16(iter) => {
|
||||
if let Some(value) = iter.next() {
|
||||
let mut result: [u8; 4] = [0; 4];
|
||||
for (i, v) in value.iter().enumerate() {
|
||||
result[i] = u8::try_from(*v).or(Err(SurfaceDataError::InvalidBoneIndex))?;
|
||||
}
|
||||
Ok(Some(result))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD as Base64Engine;
|
||||
use base64::engine::Engine as _;
|
||||
|
||||
/// A breakdown of the data contained within a URI.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Uri<'a> {
|
||||
/// Reference to the full URI str.
|
||||
pub original: &'a str,
|
||||
/// The recognized scheme type, if any.
|
||||
pub scheme: Scheme,
|
||||
/// The text that was used to identify the scheme, preceeding the first ':', if any.
|
||||
/// For example, if the URI were "https://www.somewebsite.org/books/RestInPractice.pdf"
|
||||
/// then `scheme_name` would be the "https" from the beginning.
|
||||
pub scheme_name: Option<&'a str>,
|
||||
/// Everything that follows the initial ':', or the whole original str if there is no ':'.
|
||||
/// If the URI were "https://www.somewebsite.org/books/RestInPractice.pdf"
|
||||
/// then `after_scheme` would be "www.somewebsite.org/books/RestInPractice.pdf".
|
||||
pub after_scheme: &'a str,
|
||||
/// If the scheme is "data" then `data_type` is the slice between the first ':' and the first ','.
|
||||
/// For example, if the URI were ""data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAF", then
|
||||
/// `data_type` would be "image/png;base64". If there is no ',' then `data_type` is the same as
|
||||
/// `after_scheme`. If the scheme is not "data" then `data_type` is None.
|
||||
pub data_type: Option<&'a str>,
|
||||
/// If the scheme is "data" then `data` is everything following the first ','.
|
||||
/// `data_type` is None if the scheme is not "data" or if there is no ','.
|
||||
pub data: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// One of the scheme types that the parser can recognize.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Scheme {
|
||||
/// A file URI indicates that this is an absolute path to a file.
|
||||
/// This method of identifying files is not supported.
|
||||
File,
|
||||
/// A data URI contains image or model data encoded in base64.
|
||||
Data,
|
||||
/// There was no ':' to identify a scheme name, which means that this URI
|
||||
/// will be interpreted as a relative file path and the file system will
|
||||
/// be searched for the named asset.
|
||||
None,
|
||||
/// A scheme name was specified using a ':', but it was not among these
|
||||
/// expected schemes.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Turn a URI str slice into a Uri structure containing the parts of the URI.
|
||||
pub fn parse_uri(source: &str) -> Uri {
|
||||
let (scheme_name, after_scheme): (Option<&str>, &str) = {
|
||||
let mut scheme_it = source.splitn(2, ':');
|
||||
let first = scheme_it.next();
|
||||
let second = scheme_it.next();
|
||||
if let Some(rest) = second {
|
||||
(first, rest)
|
||||
} else {
|
||||
(None, first.unwrap())
|
||||
}
|
||||
};
|
||||
if scheme_name == Some("data") {
|
||||
let mut it = after_scheme.splitn(2, ',');
|
||||
Uri {
|
||||
original: source,
|
||||
scheme: Scheme::Data,
|
||||
scheme_name,
|
||||
after_scheme,
|
||||
data_type: it.next(),
|
||||
data: it.next(),
|
||||
}
|
||||
} else if let Some(name) = scheme_name {
|
||||
let scheme = match name {
|
||||
"file" => Scheme::File,
|
||||
_ => Scheme::Other,
|
||||
};
|
||||
Uri {
|
||||
original: source,
|
||||
scheme,
|
||||
scheme_name,
|
||||
after_scheme,
|
||||
data_type: None,
|
||||
data: None,
|
||||
}
|
||||
} else {
|
||||
Uri {
|
||||
original: source,
|
||||
scheme: Scheme::None,
|
||||
scheme_name,
|
||||
after_scheme,
|
||||
data_type: None,
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_base64(source: &str) -> Result<Vec<u8>, base64::DecodeError> {
|
||||
Base64Engine.decode(source)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn split() {
|
||||
let r: Vec<_> = "abc:def".splitn(2, ':').collect();
|
||||
assert!(r == vec!["abc", "def"], "{:?}", r);
|
||||
}
|
||||
#[test]
|
||||
fn simple() {
|
||||
let u: Uri = parse_uri("Random stuff");
|
||||
assert!(
|
||||
matches!(
|
||||
u,
|
||||
Uri {
|
||||
original: "Random stuff",
|
||||
scheme: Scheme::None,
|
||||
scheme_name: None,
|
||||
after_scheme: "Random stuff",
|
||||
data_type: None,
|
||||
data: None,
|
||||
}
|
||||
),
|
||||
"{:?}",
|
||||
u
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn file() {
|
||||
let u: Uri = parse_uri("file:filename.test");
|
||||
assert!(
|
||||
matches!(
|
||||
u,
|
||||
Uri {
|
||||
original: "file:filename.test",
|
||||
scheme: Scheme::File,
|
||||
scheme_name: Some("file"),
|
||||
after_scheme: "filename.test",
|
||||
data_type: None,
|
||||
data: None,
|
||||
}
|
||||
),
|
||||
"{:?}",
|
||||
u
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn other() {
|
||||
let u: Uri = parse_uri("what:Stuff");
|
||||
assert!(
|
||||
matches!(
|
||||
u,
|
||||
Uri {
|
||||
original: "what:Stuff",
|
||||
scheme: Scheme::Other,
|
||||
scheme_name: Some("what"),
|
||||
after_scheme: "Stuff",
|
||||
data_type: None,
|
||||
data: None,
|
||||
}
|
||||
),
|
||||
"{:?}",
|
||||
u
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn data() {
|
||||
let u: Uri = parse_uri("data:type,ABCDEFG");
|
||||
assert!(
|
||||
matches!(
|
||||
u,
|
||||
Uri {
|
||||
original: "data:type,ABCDEFG",
|
||||
scheme: Scheme::Data,
|
||||
scheme_name: Some("data"),
|
||||
after_scheme: "type,ABCDEFG",
|
||||
data_type: Some("type"),
|
||||
data: Some("ABCDEFG"),
|
||||
}
|
||||
),
|
||||
"{:?}",
|
||||
u
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,7 @@
|
||||
|
||||
pub mod curve;
|
||||
pub mod fbx;
|
||||
#[cfg(feature = "gltf")]
|
||||
pub mod gltf;
|
||||
pub mod model;
|
||||
pub mod texture;
|
||||
|
||||
@@ -698,7 +698,7 @@ mod test {
|
||||
|
||||
pathfinder.remove_vertex(0);
|
||||
|
||||
assert_eq!(pathfinder.vertex(0).unwrap().neighbours, vec![]);
|
||||
assert_eq!(pathfinder.vertex(0).unwrap().neighbours, Vec::<u32>::new());
|
||||
assert_eq!(pathfinder.vertex(1), None);
|
||||
assert_eq!(pathfinder.vertex(2), None);
|
||||
}
|
||||
@@ -721,7 +721,7 @@ mod test {
|
||||
|
||||
pathfinder.insert_vertex(0, GraphVertex::new(Vector3::new(1.0, 1.0, 1.0)));
|
||||
|
||||
assert_eq!(pathfinder.vertex(0).unwrap().neighbours, vec![]);
|
||||
assert_eq!(pathfinder.vertex(0).unwrap().neighbours, Vec::<u32>::new());
|
||||
assert_eq!(pathfinder.vertex(1).unwrap().neighbours, vec![2, 3]);
|
||||
assert_eq!(pathfinder.vertex(2).unwrap().neighbours, vec![1, 3]);
|
||||
assert_eq!(pathfinder.vertex(3).unwrap().neighbours, vec![2, 1]);
|
||||
|
||||
@@ -6,6 +6,9 @@ edition = "2021"
|
||||
[features]
|
||||
default = ["fyrox-impl"]
|
||||
dylib = ["fyrox-dylib"]
|
||||
gltf = ["fyrox-impl/gltf", "fyrox-dylib/gltf"]
|
||||
mesh_analysis = ["fyrox-impl/mesh_analysis", "fyrox-dylib/mesh_analysis"]
|
||||
gltf_blend_shapes = ["fyrox-impl/gltf_blend_shapes", "fyrox-dylib/gltf_blend_shapes"]
|
||||
|
||||
[dependencies]
|
||||
fyrox-impl = { version = "0.33.1", path = "../fyrox-impl", optional = true }
|
||||
|
||||
Reference in New Issue
Block a user