Adding the first version of the glTF loader.

This commit is contained in:
b-guild
2024-04-02 21:46:09 -07:00
parent fc1619b458
commit 15b61fe269
16 changed files with 3368 additions and 6 deletions
+2 -2
View File
@@ -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);
}
}
+11 -2
View File
@@ -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)
}
+5
View File
@@ -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" }
+4
View File
@@ -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"
+9
View File
@@ -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
+479
View File
@@ -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);
}
"#,
)
],
)
+118
View File
@@ -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
);
}
}
+433
View File
@@ -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()),
}
}
+740
View File
@@ -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)
}
+192
View File
@@ -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
}
+554
View File
@@ -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)
}
}
}
}
+182
View File
@@ -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
)
}
}
+2
View File
@@ -4,5 +4,7 @@
pub mod curve;
pub mod fbx;
#[cfg(feature = "gltf")]
pub mod gltf;
pub mod model;
pub mod texture;
+2 -2
View File
@@ -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]);
+3
View File
@@ -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 }