use crate::{
core::{
algebra::{Matrix4, Point3, Vector3, Vector4},
color::Color,
math::aabb::AxisAlignedBoundingBox,
parking_lot::Mutex,
pool::Handle,
reflect::prelude::*,
type_traits::prelude::*,
variable::InheritableVariable,
visitor::prelude::*,
SafeLock,
},
graph::SceneGraph,
graphics::ElementRange,
material::{
Material, MaterialResource, MaterialResourceBinding, MaterialResourceExtension,
MaterialTextureBinding,
},
renderer::{
self,
bundle::{RenderContext, RenderDataBundleStorageTrait, SurfaceInstanceData},
cache::DynamicSurfaceCache,
},
resource::texture::PLACEHOLDER,
scene::{
base::{Base, BaseBuilder},
debug::{Line, SceneDrawingContext},
graph::Graph,
mesh::{
buffer::{
TriangleBuffer, TriangleBufferRefMut, VertexAttributeDescriptor,
VertexAttributeUsage, VertexBuffer, VertexBufferRefMut, VertexReadTrait,
VertexViewMut, VertexWriteTrait,
},
surface::SurfaceBuilder,
surface::{BlendShape, Surface, SurfaceData, SurfaceResource},
},
node::constructor::NodeConstructor,
node::{Node, NodeTrait, RdcControlFlow, SyncContext},
},
};
use fxhash::{FxHashMap, FxHasher};
use fyrox_graph::constructor::ConstructorProvider;
use fyrox_resource::untyped::ResourceKind;
use std::{
cell::Cell,
hash::{Hash, Hasher},
ops::{Deref, DerefMut},
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
pub mod buffer;
pub mod surface;
pub mod vertex;
#[derive(
Default,
Copy,
Clone,
PartialOrd,
PartialEq,
Eq,
Ord,
Hash,
Debug,
Visit,
Reflect,
AsRefStr,
EnumString,
VariantNames,
TypeUuidProvider,
)]
#[type_uuid(id = "009bccb6-42e4-4dc6-bb26-6a8a70b3fab9")]
#[repr(u32)]
pub enum RenderPath {
#[default]
Deferred = 0,
Forward = 1,
}
fn transform_vertex(mut vertex: VertexViewMut, world: &Matrix4<f32>) {
if let Ok(position) = vertex.cast_attribute::<Vector3<f32>>(VertexAttributeUsage::Position) {
*position = world.transform_point(&(*position).into()).coords;
}
if let Ok(normal) = vertex.cast_attribute::<Vector3<f32>>(VertexAttributeUsage::Normal) {
*normal = world.transform_vector(normal);
}
if let Ok(tangent) = vertex.cast_attribute::<Vector4<f32>>(VertexAttributeUsage::Tangent) {
let new_tangent = world.transform_vector(&tangent.xyz());
*tangent = Vector4::new(
new_tangent.x,
new_tangent.y,
new_tangent.z,
tangent.w,
);
}
}
#[derive(
Default,
Copy,
Clone,
PartialOrd,
PartialEq,
Eq,
Ord,
Hash,
Debug,
Visit,
Reflect,
AsRefStr,
EnumString,
VariantNames,
TypeUuidProvider,
)]
#[type_uuid(id = "745e6f32-63f5-46fe-8edb-9708699ae328")]
#[repr(u32)]
pub enum BatchingMode {
#[default]
None,
Static,
Dynamic,
}
#[derive(Debug, Clone)]
struct Batch {
data: SurfaceResource,
material: MaterialResource,
}
#[derive(Debug, Default, Clone)]
struct BatchContainer {
batches: FxHashMap<u64, Batch>,
}
impl BatchContainer {
fn fill(&mut self, from: Handle<Node>, ctx: &mut RenderContext) {
for (descendant_handle, descendant) in ctx.graph.traverse_iter(from) {
if descendant_handle == from {
continue;
}
descendant.collect_render_data(&mut RenderContext {
render_mask: ctx.render_mask,
elapsed_time: ctx.elapsed_time,
observer_position: ctx.observer_position,
frustum: None,
storage: self,
graph: ctx.graph,
render_pass_name: ctx.render_pass_name,
dynamic_surface_cache: ctx.dynamic_surface_cache,
});
}
}
}
#[derive(Debug, Default)]
struct BatchContainerWrapper(Mutex<BatchContainer>);
impl Clone for BatchContainerWrapper {
fn clone(&self) -> Self {
Self(Mutex::new(self.0.safe_lock().clone()))
}
}
impl RenderDataBundleStorageTrait for BatchContainer {
fn push_triangles(
&mut self,
dynamic_surface_cache: &mut DynamicSurfaceCache,
layout: &[VertexAttributeDescriptor],
material: &MaterialResource,
_render_path: RenderPath,
_sort_index: u64,
_node_handle: Handle<Node>,
func: &mut dyn FnMut(VertexBufferRefMut, TriangleBufferRefMut),
) {
let mut hasher = FxHasher::default();
layout.hash(&mut hasher);
hasher.write_u64(material.key());
let batch_hash = hasher.finish();
let batch = self.batches.entry(batch_hash).or_insert_with(|| Batch {
data: dynamic_surface_cache.get_or_create(batch_hash, layout),
material: material.clone(),
});
let mut batch_data_guard = batch.data.data_ref();
let batch_data = &mut *batch_data_guard;
func(
batch_data.vertex_buffer.modify(),
batch_data.geometry_buffer.modify(),
);
}
fn push(
&mut self,
data: &SurfaceResource,
material: &MaterialResource,
_render_path: RenderPath,
_sort_index: u64,
instance_data: SurfaceInstanceData,
) {
let src_data = data.data_ref();
let mut hasher = FxHasher::default();
src_data.vertex_buffer.layout().hash(&mut hasher);
hasher.write_u64(material.key());
let batch_hash = hasher.finish();
let batch = self.batches.entry(batch_hash).or_insert_with(|| Batch {
data: SurfaceResource::new_ok(
Uuid::new_v4(),
ResourceKind::Embedded,
SurfaceData::new(
src_data.vertex_buffer.clone_empty(4096),
TriangleBuffer::new(Vec::with_capacity(4096)),
),
),
material: material.clone(),
});
let mut batch_data_guard = batch.data.data_ref();
let batch_data = &mut *batch_data_guard;
let start_vertex_index = batch_data.vertex_buffer.vertex_count();
let mut batch_vertex_buffer = batch_data.vertex_buffer.modify();
for src_vertex in src_data.vertex_buffer.iter() {
batch_vertex_buffer
.push_vertex_raw(&src_vertex.transform(&mut |vertex| {
transform_vertex(vertex, &instance_data.world_transform)
}))
.expect("Vertex size must match!");
}
let mut batch_geometry_buffer = batch_data.geometry_buffer.modify();
batch_geometry_buffer.push_triangles_with_offset(
start_vertex_index,
src_data.geometry_buffer.triangles_ref(),
);
}
}
#[derive(Debug, Reflect, Clone, Visit, ComponentProvider)]
#[reflect(derived_type = "Node")]
pub struct Mesh {
#[visit(rename = "Common")]
base: Base,
#[reflect(setter = "set_surfaces")]
surfaces: InheritableVariable<Vec<Surface>>,
#[reflect(setter = "set_render_path")]
render_path: InheritableVariable<RenderPath>,
#[visit(optional)]
#[reflect(setter = "set_batching_mode")]
batching_mode: InheritableVariable<BatchingMode>,
#[visit(optional)]
blend_shapes_property_name: String,
#[visit(optional)]
blend_shapes: InheritableVariable<Vec<BlendShape>>,
#[reflect(hidden)]
#[visit(skip)]
local_bounding_box: Cell<AxisAlignedBoundingBox>,
#[reflect(hidden)]
#[visit(skip)]
local_bounding_box_dirty: Cell<bool>,
#[reflect(hidden)]
#[visit(skip)]
world_bounding_box: Cell<AxisAlignedBoundingBox>,
#[reflect(hidden)]
#[visit(skip)]
batch_container: BatchContainerWrapper,
}
impl Default for Mesh {
fn default() -> Self {
Self {
base: Default::default(),
surfaces: Default::default(),
local_bounding_box: Default::default(),
world_bounding_box: Default::default(),
local_bounding_box_dirty: Cell::new(true),
render_path: InheritableVariable::new_modified(RenderPath::Deferred),
batching_mode: Default::default(),
blend_shapes_property_name: Mesh::DEFAULT_BLEND_SHAPES_PROPERTY_NAME.to_string(),
blend_shapes: Default::default(),
batch_container: Default::default(),
}
}
}
impl Deref for Mesh {
type Target = Base;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for Mesh {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl TypeUuidProvider for Mesh {
fn type_uuid() -> Uuid {
uuid!("caaf9d7b-bd74-48ce-b7cc-57e9dc65c2e6")
}
}
impl Mesh {
pub const DEFAULT_BLEND_SHAPES_PROPERTY_NAME: &'static str = "blendShapesStorage";
pub fn set_surfaces(&mut self, surfaces: Vec<Surface>) -> Vec<Surface> {
self.surfaces.set_value_and_mark_modified(surfaces)
}
#[inline]
pub fn surfaces(&self) -> &[Surface] {
&self.surfaces
}
#[inline]
pub fn surfaces_mut(&mut self) -> &mut [Surface] {
self.local_bounding_box_dirty.set(true);
self.surfaces.get_value_mut_silent()
}
#[inline]
pub fn clear_surfaces(&mut self) {
self.surfaces.get_value_mut_and_mark_modified().clear();
self.local_bounding_box_dirty.set(true);
}
#[inline]
pub fn add_surface(&mut self, surface: Surface) {
self.surfaces
.get_value_mut_and_mark_modified()
.push(surface);
self.local_bounding_box_dirty.set(true);
}
pub fn blend_shapes(&self) -> &[BlendShape] {
&self.blend_shapes
}
pub fn blend_shapes_mut(&mut self) -> &mut [BlendShape] {
self.blend_shapes.get_value_mut_and_mark_modified()
}
pub fn set_render_path(&mut self, render_path: RenderPath) -> RenderPath {
self.render_path.set_value_and_mark_modified(render_path)
}
pub fn render_path(&self) -> RenderPath {
*self.render_path
}
pub fn accurate_world_bounding_box(&self, graph: &Graph) -> AxisAlignedBoundingBox {
let mut bounding_box = AxisAlignedBoundingBox::default();
for surface in self.surfaces.iter() {
let data = surface.data();
let data = data.data_ref();
if surface.bones().is_empty() {
for view in data.vertex_buffer.iter() {
let Ok(vertex_pos) = view.read_3_f32(VertexAttributeUsage::Position) else {
break;
};
bounding_box.add_point(
self.global_transform()
.transform_point(&Point3::from(vertex_pos))
.coords,
);
}
} else {
let bone_matrices = surface
.bones()
.iter()
.map(|&b| {
let bone_node = &graph[b];
bone_node.global_transform() * bone_node.inv_bind_pose_transform()
})
.collect::<Vec<Matrix4<f32>>>();
for view in data.vertex_buffer.iter() {
let mut position = Vector3::default();
let Ok(vertex_pos) = view.read_3_f32(VertexAttributeUsage::Position) else {
break;
};
let Ok(bone_indices) = view.read_4_u8(VertexAttributeUsage::BoneIndices) else {
break;
};
let Ok(bone_weights) = view.read_4_f32(VertexAttributeUsage::BoneWeight) else {
break;
};
for (&bone_index, &weight) in bone_indices.iter().zip(bone_weights.iter()) {
position += bone_matrices[bone_index as usize]
.transform_point(&Point3::from(vertex_pos))
.coords
.scale(weight);
}
bounding_box.add_point(position);
}
}
}
bounding_box
}
pub fn set_batching_mode(&mut self, mode: BatchingMode) -> BatchingMode {
if let BatchingMode::None | BatchingMode::Dynamic = mode {
std::mem::take(&mut self.batch_container);
}
self.batching_mode.set_value_and_mark_modified(mode)
}
pub fn batching_mode(&self) -> BatchingMode {
*self.batching_mode
}
}
fn extend_aabb_from_vertex_buffer(
vertex_buffer: &VertexBuffer,
bounding_box: &mut AxisAlignedBoundingBox,
) {
if let Some(position_attribute_view) =
vertex_buffer.attribute_view::<Vector3<f32>>(VertexAttributeUsage::Position)
{
for i in 0..vertex_buffer.vertex_count() as usize {
bounding_box.add_point(*position_attribute_view.get(i).unwrap());
}
}
}
fn placeholder_material() -> MaterialResource {
let mut material = Material::standard();
material.bind("diffuseTexture", PLACEHOLDER.resource());
MaterialResource::new_ok(Uuid::new_v4(), ResourceKind::Embedded, material)
}
impl ConstructorProvider<Node, Graph> for Mesh {
fn constructor() -> NodeConstructor {
NodeConstructor::new::<Self>()
.with_variant("Empty", |_| {
MeshBuilder::new(BaseBuilder::new()).build_node().into()
})
.with_variant("Cube", |_| {
MeshBuilder::new(BaseBuilder::new().with_name("Cube"))
.with_surfaces(vec![SurfaceBuilder::new(surface::CUBE.resource.clone())
.with_material(placeholder_material())
.build()])
.build_node()
.into()
})
.with_variant("Cone", |_| {
MeshBuilder::new(BaseBuilder::new().with_name("Cone"))
.with_surfaces(vec![SurfaceBuilder::new(surface::CONE.resource.clone())
.with_material(placeholder_material())
.build()])
.build_node()
.into()
})
.with_variant("Cylinder", |_| {
MeshBuilder::new(BaseBuilder::new().with_name("Cylinder"))
.with_surfaces(vec![SurfaceBuilder::new(
surface::CYLINDER.resource.clone(),
)
.with_material(placeholder_material())
.build()])
.build_node()
.into()
})
.with_variant("Sphere", |_| {
MeshBuilder::new(BaseBuilder::new().with_name("Sphere"))
.with_surfaces(vec![SurfaceBuilder::new(surface::SPHERE.resource.clone())
.with_material(placeholder_material())
.build()])
.build_node()
.into()
})
.with_variant("Quad", |_| {
MeshBuilder::new(BaseBuilder::new().with_name("Quad"))
.with_surfaces(vec![SurfaceBuilder::new(surface::QUAD.resource.clone())
.with_material(placeholder_material())
.build()])
.build_node()
.into()
})
.with_group("Mesh")
}
}
impl NodeTrait for Mesh {
fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
if self.local_bounding_box_dirty.get() {
let mut bounding_box = AxisAlignedBoundingBox::default();
if let BatchingMode::Static = *self.batching_mode {
let container = self.batch_container.0.safe_lock();
for batch in container.batches.values() {
let data = batch.data.data_ref();
extend_aabb_from_vertex_buffer(&data.vertex_buffer, &mut bounding_box);
}
} else {
for surface in self.surfaces.iter() {
let data = surface.data();
if data.is_ok() {
let data = data.data_ref();
extend_aabb_from_vertex_buffer(&data.vertex_buffer, &mut bounding_box);
}
}
}
self.local_bounding_box.set(bounding_box);
self.local_bounding_box_dirty.set(false);
}
self.local_bounding_box.get()
}
fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
self.world_bounding_box.get()
}
fn id(&self) -> Uuid {
Self::type_uuid()
}
fn on_global_transform_changed(
&self,
new_global_transform: &Matrix4<f32>,
context: &mut SyncContext,
) {
if self.surfaces.iter().any(|s| !s.bones.is_empty()) {
let mut world_aabb = self.local_bounding_box().transform(new_global_transform);
for surface in self.surfaces.iter() {
for &bone in surface.bones() {
if let Ok(node) = context.nodes.try_borrow(bone) {
world_aabb.add_point(node.global_position())
}
}
}
self.world_bounding_box.set(world_aabb)
} else {
self.world_bounding_box
.set(self.local_bounding_box().transform(new_global_transform));
}
}
fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
if !self.should_be_rendered(ctx.frustum, ctx.render_mask) {
return RdcControlFlow::Continue;
}
if renderer::is_shadow_pass(ctx.render_pass_name) && !self.cast_shadows() {
return RdcControlFlow::Continue;
}
let sorting_index = ctx.calculate_sorting_index(self.global_position());
if let BatchingMode::Static = *self.batching_mode {
let mut container = self.batch_container.0.safe_lock();
if container.batches.is_empty() {
container.fill(self.handle(), ctx);
}
for batch in container.batches.values() {
ctx.storage.push(
&batch.data,
&batch.material,
self.render_path(),
sorting_index,
SurfaceInstanceData {
world_transform: Matrix4::identity(),
bone_matrices: Default::default(),
blend_shapes_weights: Default::default(),
element_range: ElementRange::Full,
node_handle: self.handle(),
},
);
}
RdcControlFlow::Break
} else {
for surface in self.surfaces().iter() {
if !surface.data_ref().is_ok() {
continue;
}
let is_skinned = !surface.bones.is_empty();
let world = if is_skinned {
Matrix4::identity()
} else {
self.global_transform()
};
let batching_mode = match *self.batching_mode {
BatchingMode::None => BatchingMode::None,
BatchingMode::Static => BatchingMode::Static,
BatchingMode::Dynamic => {
let surface_data_guard = surface.data_ref().data_ref();
if self.blend_shapes().is_empty()
&& surface.bones().is_empty()
&& surface_data_guard.vertex_buffer.vertex_count() < 256
{
BatchingMode::Dynamic
} else {
BatchingMode::None
}
}
};
match batching_mode {
BatchingMode::None => {
let surface_data = surface.data_ref();
let substitute_material = surface_data
.data_ref()
.blend_shapes_container
.as_ref()
.and_then(|c| c.blend_shape_storage.as_ref())
.map(|texture| {
let material_copy = surface.material().deep_copy();
material_copy.data_ref().bind(
&self.blend_shapes_property_name,
MaterialResourceBinding::Texture(MaterialTextureBinding {
value: Some(texture.clone()),
}),
);
material_copy
});
ctx.storage.push(
surface_data,
substitute_material.as_ref().unwrap_or(surface.material()),
self.render_path(),
sorting_index,
SurfaceInstanceData {
world_transform: world,
bone_matrices: surface
.bones
.iter()
.map(|bone_handle| {
if let Ok(bone_node) = ctx.graph.try_get_node(*bone_handle)
{
bone_node.global_transform()
* bone_node.inv_bind_pose_transform()
} else {
Matrix4::identity()
}
})
.collect::<Vec<_>>(),
blend_shapes_weights: self
.blend_shapes()
.iter()
.map(|bs| bs.weight / 100.0)
.collect(),
element_range: ElementRange::Full,
node_handle: self.handle(),
},
);
}
BatchingMode::Dynamic => {
let surface_data_guard = surface.data_ref().data_ref();
ctx.storage.push_triangles(
ctx.dynamic_surface_cache,
&surface_data_guard
.vertex_buffer
.layout_descriptor()
.collect::<Vec<_>>(),
surface.material(),
*self.render_path,
sorting_index,
self.handle(),
&mut move |mut vertex_buffer, mut triangle_buffer| {
let start_vertex_index = vertex_buffer.vertex_count();
for vertex in surface_data_guard.vertex_buffer.iter() {
vertex_buffer
.push_vertex_raw(&vertex.transform(&mut |vertex| {
transform_vertex(vertex, &world)
}))
.unwrap();
}
triangle_buffer.push_triangles_with_offset(
start_vertex_index,
surface_data_guard.geometry_buffer.triangles_ref(),
)
},
);
}
_ => (),
}
}
RdcControlFlow::Continue
}
}
fn debug_draw(&self, ctx: &mut SceneDrawingContext) {
let transform = self.global_transform();
for surface in self.surfaces() {
for vertex in surface.data().data_ref().vertex_buffer.iter() {
let len = 0.025;
let position = transform
.transform_point(&Point3::from(
vertex.read_3_f32(VertexAttributeUsage::Position).unwrap(),
))
.coords;
let vertex_tangent = vertex.read_4_f32(VertexAttributeUsage::Tangent).unwrap();
let tangent = transform
.transform_vector(&vertex_tangent.xyz())
.normalize()
.scale(len);
let normal = transform
.transform_vector(
&vertex
.read_3_f32(VertexAttributeUsage::Normal)
.unwrap()
.xyz(),
)
.normalize()
.scale(len);
let binormal = normal
.xyz()
.cross(&tangent)
.scale(vertex_tangent.w)
.normalize()
.scale(len);
ctx.add_line(Line {
begin: position,
end: position + tangent,
color: Color::RED,
});
ctx.add_line(Line {
begin: position,
end: position + normal,
color: Color::BLUE,
});
ctx.add_line(Line {
begin: position,
end: position + binormal,
color: Color::GREEN,
});
}
}
}
}
pub struct MeshBuilder {
base_builder: BaseBuilder,
surfaces: Vec<Surface>,
render_path: RenderPath,
blend_shapes: Vec<BlendShape>,
batching_mode: BatchingMode,
blend_shapes_property_name: String,
}
impl MeshBuilder {
pub fn new(base_builder: BaseBuilder) -> Self {
Self {
base_builder,
surfaces: Default::default(),
render_path: RenderPath::Deferred,
blend_shapes: Default::default(),
batching_mode: BatchingMode::None,
blend_shapes_property_name: Mesh::DEFAULT_BLEND_SHAPES_PROPERTY_NAME.to_string(),
}
}
pub fn with_surfaces(mut self, surfaces: Vec<Surface>) -> Self {
self.surfaces = surfaces;
self
}
pub fn with_render_path(mut self, render_path: RenderPath) -> Self {
self.render_path = render_path;
self
}
pub fn with_blend_shapes(mut self, blend_shapes: Vec<BlendShape>) -> Self {
self.blend_shapes = blend_shapes;
self
}
pub fn with_batching_mode(mut self, mode: BatchingMode) -> Self {
self.batching_mode = mode;
self
}
pub fn with_blend_shapes_property_name(mut self, name: String) -> Self {
self.blend_shapes_property_name = name;
self
}
pub fn build_node(self) -> Node {
Node::new(Mesh {
blend_shapes: self.blend_shapes.into(),
base: self.base_builder.build_base(),
surfaces: self.surfaces.into(),
local_bounding_box: Default::default(),
local_bounding_box_dirty: Cell::new(true),
render_path: self.render_path.into(),
world_bounding_box: Default::default(),
batching_mode: self.batching_mode.into(),
batch_container: Default::default(),
blend_shapes_property_name: self.blend_shapes_property_name,
})
}
pub fn build(self, graph: &mut Graph) -> Handle<Mesh> {
graph.add_node(self.build_node()).to_variant()
}
}