forked from 1b-agent/opencadstudio-upstream
Render procedural checker materials correctly
This commit is contained in:
1 parent
12c1c330fc
commit
07acd1284c
9 files changed
+440
-39
No files matched your search
Generated
+3
-3
@@ -80,7 +80,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.5.5"
|
||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=88fdb556dc8a292931a52b52ba42fc322dd39c23#88fdb556dc8a292931a52b52ba42fc322dd39c23"
|
||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=6f3711809e39e9ffdf16a63086079b6475d97175#6f3711809e39e9ffdf16a63086079b6475d97175"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
@@ -895,7 +895,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=a3185be8189d78d6e93b4ea5b9238b184da4f3cf#a3185be8189d78d6e93b4ea5b9238b184da4f3cf"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=935d90b86a99377dc95cba92e99a9b109180e2f6#935d90b86a99377dc95cba92e99a9b109180e2f6"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
@@ -906,7 +906,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cadkernel-constraints"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=a3185be8189d78d6e93b4ea5b9238b184da4f3cf#a3185be8189d78d6e93b4ea5b9238b184da4f3cf"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=935d90b86a99377dc95cba92e99a9b109180e2f6#935d90b86a99377dc95cba92e99a9b109180e2f6"
|
||||
dependencies = [
|
||||
"nalgebra",
|
||||
]
|
||||
|
||||
+3
-3
@@ -30,9 +30,9 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
||||
rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "88fdb556dc8a292931a52b52ba42fc322dd39c23", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "a3185be8189d78d6e93b4ea5b9238b184da4f3cf", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "a3185be8189d78d6e93b4ea5b9238b184da4f3cf" }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "935d90b86a99377dc95cba92e99a9b109180e2f6", features = ["acis", "offset"] }
|
||||
cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "935d90b86a99377dc95cba92e99a9b109180e2f6" }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "88fdb556dc8a292931a52b52ba42fc322dd39c23", optional = true, features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175", optional = true, features = ["serde"] }
|
||||
|
||||
# Runtime IPC and serialization (host feature only).
|
||||
interprocess = { version = "2", optional = true }
|
||||
@@ -38,7 +38,7 @@ serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
cargo-lock = "11"
|
||||
# acadrust is scanned at build time to generate the embedded type registry.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "88fdb556dc8a292931a52b52ba42fc322dd39c23", features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
@@ -8,7 +8,7 @@ publish = false
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "88fdb556dc8a292931a52b52ba42fc322dd39c23", features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175", features = ["serde"] }
|
||||
bincode = "1.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
|
||||
@@ -10,4 +10,4 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ocs_plugin_api = { path = "../ocs_plugin_api", features = ["host"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "88fdb556dc8a292931a52b52ba42fc322dd39c23", features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175", features = ["serde"] }
|
||||
@@ -12,4 +12,4 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ocs_plugin_api = { path = "../../crates/ocs_plugin_api", features = ["host"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "88fdb556dc8a292931a52b52ba42fc322dd39c23" }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "6f3711809e39e9ffdf16a63086079b6475d97175" }
|
||||
@@ -1,4 +1,5 @@
|
||||
use acadrust::objects::{Material, MaterialColor, MaterialMap, ObjectType};
|
||||
use acadrust::xdata::XDataValue;
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -39,6 +40,7 @@ pub struct MeshMaterial {
|
||||
pub final_gather: i16,
|
||||
pub color_bleed_scale: f32,
|
||||
pub advanced_data_present: bool,
|
||||
pub mapper: Option<MeshMaterialMapper>,
|
||||
pub diffuse_map: MeshTextureMap,
|
||||
pub specular_map: MeshTextureMap,
|
||||
pub reflection_map: MeshTextureMap,
|
||||
@@ -48,6 +50,82 @@ pub struct MeshMaterial {
|
||||
pub normal_map: MeshTextureMap,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MeshMaterialMapper {
|
||||
pub origin: [f64; 3],
|
||||
pub inverse_basis: [[f64; 3]; 3],
|
||||
pub normal_basis: [[f64; 3]; 3],
|
||||
}
|
||||
|
||||
impl MeshMaterialMapper {
|
||||
pub fn map_position(&self, high: [f32; 3], low: [f32; 3]) -> [f32; 3] {
|
||||
let point = [
|
||||
high[0] as f64 + low[0] as f64 - self.origin[0],
|
||||
high[1] as f64 + low[1] as f64 - self.origin[1],
|
||||
high[2] as f64 + low[2] as f64 - self.origin[2],
|
||||
];
|
||||
[
|
||||
(self.inverse_basis[0][0] * point[0]
|
||||
+ self.inverse_basis[0][1] * point[1]
|
||||
+ self.inverse_basis[0][2] * point[2]) as f32,
|
||||
(self.inverse_basis[1][0] * point[0]
|
||||
+ self.inverse_basis[1][1] * point[1]
|
||||
+ self.inverse_basis[1][2] * point[2]) as f32,
|
||||
(self.inverse_basis[2][0] * point[0]
|
||||
+ self.inverse_basis[2][1] * point[1]
|
||||
+ self.inverse_basis[2][2] * point[2]) as f32,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn map_normal(&self, normal: [f32; 3]) -> [f32; 3] {
|
||||
let mapped = [
|
||||
self.normal_basis[0][0] * normal[0] as f64
|
||||
+ self.normal_basis[0][1] * normal[1] as f64
|
||||
+ self.normal_basis[0][2] * normal[2] as f64,
|
||||
self.normal_basis[1][0] * normal[0] as f64
|
||||
+ self.normal_basis[1][1] * normal[1] as f64
|
||||
+ self.normal_basis[1][2] * normal[2] as f64,
|
||||
self.normal_basis[2][0] * normal[0] as f64
|
||||
+ self.normal_basis[2][1] * normal[1] as f64
|
||||
+ self.normal_basis[2][2] * normal[2] as f64,
|
||||
];
|
||||
let length =
|
||||
(mapped[0] * mapped[0] + mapped[1] * mapped[1] + mapped[2] * mapped[2]).sqrt();
|
||||
if length > f64::EPSILON {
|
||||
[
|
||||
(mapped[0] / length) as f32,
|
||||
(mapped[1] / length) as f32,
|
||||
(mapped[2] / length) as f32,
|
||||
]
|
||||
} else {
|
||||
normal
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_bounds(&self, bounds: [f32; 6]) -> [f32; 6] {
|
||||
let mut mapped = [
|
||||
f32::INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
];
|
||||
for x in [bounds[0], bounds[3]] {
|
||||
for y in [bounds[1], bounds[4]] {
|
||||
for z in [bounds[2], bounds[5]] {
|
||||
let point = self.map_position([x, y, z], [0.0; 3]);
|
||||
for axis in 0..3 {
|
||||
mapped[axis] = mapped[axis].min(point[axis]);
|
||||
mapped[axis + 3] = mapped[axis + 3].max(point[axis]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mapped
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MeshTextureMap {
|
||||
pub blend_factor: f32,
|
||||
@@ -146,6 +224,7 @@ impl MeshMaterial {
|
||||
final_gather: 0,
|
||||
color_bleed_scale: 1.0,
|
||||
advanced_data_present: false,
|
||||
mapper: None,
|
||||
diffuse_map: MeshTextureMap::default(),
|
||||
specular_map: MeshTextureMap::default(),
|
||||
reflection_map: MeshTextureMap::default(),
|
||||
@@ -210,6 +289,7 @@ impl MeshMaterial {
|
||||
final_gather: material.final_gather,
|
||||
color_bleed_scale: material.color_bleed_scale as f32,
|
||||
advanced_data_present: material.advanced_data_present,
|
||||
mapper: None,
|
||||
diffuse_map,
|
||||
specular_map,
|
||||
reflection_map,
|
||||
@@ -283,7 +363,7 @@ fn procedural_map_image(map: &MaterialMap) -> Option<Arc<MaterialImage>> {
|
||||
let mut rgba = Vec::with_capacity((size * size * 4) as usize);
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let color = if ((x / 8) + (y / 8)) & 1 == 0 {
|
||||
let color = if (x < size / 2) == (y < size / 2) {
|
||||
color1
|
||||
} else {
|
||||
color2
|
||||
@@ -315,9 +395,11 @@ pub fn resolve_material_with_base(
|
||||
) -> MeshMaterial {
|
||||
let common = entity.common();
|
||||
if common.material_flags == 1 {
|
||||
return by_block
|
||||
let mut material = by_block
|
||||
.cloned()
|
||||
.unwrap_or_else(|| MeshMaterial::entity_color(entity_color));
|
||||
material.mapper = entity_material_mapper(entity);
|
||||
return material;
|
||||
}
|
||||
let handle = if common.material_flags == 3 {
|
||||
common.material_handle
|
||||
@@ -331,7 +413,7 @@ pub fn resolve_material_with_base(
|
||||
let Some(handle) = handle else {
|
||||
return MeshMaterial::entity_color(entity_color);
|
||||
};
|
||||
match document.objects.get(&handle) {
|
||||
let mut material = match document.objects.get(&handle) {
|
||||
Some(ObjectType::Material(material))
|
||||
if material.name.eq_ignore_ascii_case("ByLayer")
|
||||
|| material.name.eq_ignore_ascii_case("ByBlock") =>
|
||||
@@ -342,7 +424,58 @@ pub fn resolve_material_with_base(
|
||||
MeshMaterial::from_dwg(handle, material, entity_color, base_dir)
|
||||
}
|
||||
_ => MeshMaterial::entity_color(entity_color),
|
||||
};
|
||||
material.mapper = entity_material_mapper(entity);
|
||||
material
|
||||
}
|
||||
|
||||
fn entity_material_mapper(entity: &EntityType) -> Option<MeshMaterialMapper> {
|
||||
let record = entity
|
||||
.common()
|
||||
.extended_data
|
||||
.get_record("ACAD_MATERIAL_MAPPER")?;
|
||||
let mut positions = record.values.iter().filter_map(|value| match value {
|
||||
XDataValue::Position3D(point) => Some([point.x, point.y, point.z]),
|
||||
_ => None,
|
||||
});
|
||||
let origin = positions.next()?;
|
||||
let x = positions.next()?;
|
||||
let y = positions.next()?;
|
||||
let z = positions.next()?;
|
||||
let basis = glam::DMat3::from_cols(
|
||||
glam::DVec3::from_array([
|
||||
x[0] - origin[0],
|
||||
x[1] - origin[1],
|
||||
x[2] - origin[2],
|
||||
]),
|
||||
glam::DVec3::from_array([
|
||||
y[0] - origin[0],
|
||||
y[1] - origin[1],
|
||||
y[2] - origin[2],
|
||||
]),
|
||||
glam::DVec3::from_array([
|
||||
z[0] - origin[0],
|
||||
z[1] - origin[1],
|
||||
z[2] - origin[2],
|
||||
]),
|
||||
);
|
||||
if basis.determinant().abs() <= f64::EPSILON {
|
||||
return None;
|
||||
}
|
||||
let inverse = basis.inverse().to_cols_array();
|
||||
Some(MeshMaterialMapper {
|
||||
origin,
|
||||
inverse_basis: [
|
||||
[inverse[0], inverse[3], inverse[6]],
|
||||
[inverse[1], inverse[4], inverse[7]],
|
||||
[inverse[2], inverse[5], inverse[8]],
|
||||
],
|
||||
normal_basis: [
|
||||
[x[0] - origin[0], x[1] - origin[1], x[2] - origin[2]],
|
||||
[y[0] - origin[0], y[1] - origin[1], y[2] - origin[2]],
|
||||
[z[0] - origin[0], z[1] - origin[1], z[2] - origin[2]],
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_layer_material_with_base(
|
||||
@@ -384,7 +517,9 @@ pub fn resolve_material_handle_with_base(
|
||||
fallback.clone()
|
||||
}
|
||||
Some(ObjectType::Material(material)) => {
|
||||
MeshMaterial::from_dwg(handle, material, fallback.diffuse, base_dir)
|
||||
let mut material = MeshMaterial::from_dwg(handle, material, fallback.diffuse, base_dir);
|
||||
material.mapper = fallback.mapper;
|
||||
material
|
||||
}
|
||||
_ => fallback.clone(),
|
||||
}
|
||||
@@ -442,3 +577,56 @@ fn load_map_image(map: &MaterialMap, base_dir: Option<&Path>) -> Option<Arc<Mate
|
||||
fn load_map_image(_map: &MaterialMap, _base_dir: Option<&Path>) -> Option<Arc<MaterialImage>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use acadrust::entities::Line;
|
||||
use acadrust::objects::MaterialTexture;
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
|
||||
#[test]
|
||||
fn procedural_checker_is_one_repeatable_two_by_two_tile() {
|
||||
let mut map = MaterialMap::default();
|
||||
map.texture = Some(MaterialTexture {
|
||||
color1: MaterialColor {
|
||||
flag: 1,
|
||||
factor: 1.0,
|
||||
rgb: Some(0xFF0000),
|
||||
},
|
||||
color2: MaterialColor {
|
||||
flag: 1,
|
||||
factor: 1.0,
|
||||
rgb: Some(0x00FF00),
|
||||
},
|
||||
..MaterialTexture::default()
|
||||
});
|
||||
|
||||
let image = procedural_map_image(&map).expect("procedural image");
|
||||
let pixel = |x: usize, y: usize| &image.rgba[(y * 64 + x) * 4..][..4];
|
||||
assert_eq!(pixel(0, 0), [255, 0, 0, 255]);
|
||||
assert_eq!(pixel(32, 0), [0, 255, 0, 255]);
|
||||
assert_eq!(pixel(0, 32), [0, 255, 0, 255]);
|
||||
assert_eq!(pixel(32, 32), [255, 0, 0, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_material_mapper_preserves_its_coordinate_frame() {
|
||||
let mut line = Line::new();
|
||||
let mut record = ExtendedDataRecord::new("ACAD_MATERIAL_MAPPER");
|
||||
record.values = vec![
|
||||
XDataValue::Integer16(2),
|
||||
XDataValue::Integer16(0),
|
||||
XDataValue::Integer16(0),
|
||||
XDataValue::Position3D(Vector3::new(10.0, 20.0, 30.0)),
|
||||
XDataValue::Position3D(Vector3::new(10.0, 22.0, 30.0)),
|
||||
XDataValue::Position3D(Vector3::new(7.0, 20.0, 30.0)),
|
||||
XDataValue::Position3D(Vector3::new(10.0, 20.0, 34.0)),
|
||||
];
|
||||
line.common.extended_data.add_record(record);
|
||||
let mapper = entity_material_mapper(&EntityType::Line(line)).expect("valid mapper");
|
||||
|
||||
assert_eq!(mapper.map_position([4.0, 22.0, 42.0], [0.0; 3]), [1.0, 2.0, 3.0]);
|
||||
}
|
||||
}
|
||||
+234
-24
@@ -1037,9 +1037,28 @@ fn material_key(
|
||||
key[34] = material.final_gather as u32;
|
||||
key[35] = material.color_bleed_scale.to_bits();
|
||||
key[36] = material.advanced_data_present as u32;
|
||||
let mapper = material_mapper_key(material);
|
||||
key[37] = mapper as u32;
|
||||
key[38] = (mapper >> 32) as u32;
|
||||
key[39] = material.mapper.is_some() as u32;
|
||||
MaterialBatchKey(key)
|
||||
}
|
||||
|
||||
fn material_mapper_key(
|
||||
material: &crate::scene::model::material_model::MeshMaterial,
|
||||
) -> u64 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let Some(mapper) = material.mapper else {
|
||||
return 0;
|
||||
};
|
||||
let mut hasher = rustc_hash::FxHasher::default();
|
||||
mapper.origin.map(f64::to_bits).hash(&mut hasher);
|
||||
for row in mapper.inverse_basis {
|
||||
row.map(f64::to_bits).hash(&mut hasher);
|
||||
}
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
fn morton_axis(mut value: u32) -> u64 {
|
||||
value &= 0x3ff;
|
||||
let mut result = 0u64;
|
||||
@@ -1197,6 +1216,62 @@ fn material_has_textures(
|
||||
})
|
||||
}
|
||||
|
||||
fn material_has_box_projection(
|
||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||
) -> bool {
|
||||
material.is_some_and(|material| {
|
||||
[
|
||||
&material.diffuse_map,
|
||||
&material.specular_map,
|
||||
&material.reflection_map,
|
||||
&material.opacity_map,
|
||||
&material.bump_map,
|
||||
&material.refraction_map,
|
||||
&material.normal_map,
|
||||
]
|
||||
.into_iter()
|
||||
.any(|map| map.image.is_some() && map.projection == 2)
|
||||
})
|
||||
}
|
||||
|
||||
fn triangle_mapping_normal(mesh: &MeshModel, triangle: &[u32]) -> [f32; 3] {
|
||||
let vertex = |corner: usize| {
|
||||
let index = triangle[corner] as usize;
|
||||
(
|
||||
mesh.verts[index],
|
||||
mesh.verts_low.get(index).copied().unwrap_or([0.0; 3]),
|
||||
)
|
||||
};
|
||||
let (a, a_low) = vertex(0);
|
||||
let (b, b_low) = vertex(1);
|
||||
let (c, c_low) = vertex(2);
|
||||
let ab = [
|
||||
(b[0] - a[0]) + (b_low[0] - a_low[0]),
|
||||
(b[1] - a[1]) + (b_low[1] - a_low[1]),
|
||||
(b[2] - a[2]) + (b_low[2] - a_low[2]),
|
||||
];
|
||||
let ac = [
|
||||
(c[0] - a[0]) + (c_low[0] - a_low[0]),
|
||||
(c[1] - a[1]) + (c_low[1] - a_low[1]),
|
||||
(c[2] - a[2]) + (c_low[2] - a_low[2]),
|
||||
];
|
||||
let normal = [
|
||||
ab[1] * ac[2] - ab[2] * ac[1],
|
||||
ab[2] * ac[0] - ab[0] * ac[2],
|
||||
ab[0] * ac[1] - ab[1] * ac[0],
|
||||
];
|
||||
let length = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2])
|
||||
.sqrt();
|
||||
if length > f32::EPSILON {
|
||||
[normal[0] / length, normal[1] / length, normal[2] / length]
|
||||
} else {
|
||||
mesh.normals
|
||||
.get(triangle[0] as usize)
|
||||
.copied()
|
||||
.unwrap_or([0.0, 1.0, 0.0])
|
||||
}
|
||||
}
|
||||
|
||||
fn build_instanced_chunks(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
@@ -1215,9 +1290,10 @@ fn build_instanced_chunks(
|
||||
let compact_vertices = !material_has_textures(material);
|
||||
let material_identity = if material_has_textures(material) {
|
||||
material.map_or(0, |material| {
|
||||
material
|
||||
let handle = material
|
||||
.handle
|
||||
.map_or(material as *const _ as usize as u64, |handle| handle.value())
|
||||
.map_or(material as *const _ as usize as u64, |handle| handle.value());
|
||||
handle ^ material_mapper_key(material).rotate_left(17)
|
||||
})
|
||||
} else {
|
||||
0
|
||||
@@ -1229,7 +1305,8 @@ fn build_instanced_chunks(
|
||||
};
|
||||
let budget = super::gpu_budget::buffer_budget(device);
|
||||
let needs_wire_vertices = first.include_edges && source.edge_verts.is_empty();
|
||||
let split_geometry = mesh
|
||||
let split_geometry = (first.include_faces && material_has_box_projection(material))
|
||||
|| mesh
|
||||
.verts
|
||||
.len()
|
||||
.saturating_mul(std::mem::size_of::<MeshVertex>())
|
||||
@@ -1257,21 +1334,24 @@ fn build_instanced_chunks(
|
||||
|
||||
let has_normals = mesh.normals.len() == mesh.verts.len();
|
||||
let bounds = mesh_bounds(mesh);
|
||||
let vertex = |index: usize| {
|
||||
let vertex = |index: usize, mapping_normal: Option<[f32; 3]>| {
|
||||
let normal = if has_normals {
|
||||
mesh.normals[index]
|
||||
} else {
|
||||
[0.0, 1.0, 0.0]
|
||||
};
|
||||
let mapping_normal = mapping_normal.unwrap_or(normal);
|
||||
let position = mesh.verts[index];
|
||||
let position_low = mesh.verts_low.get(index).copied().unwrap_or([0.0; 3]);
|
||||
let uvs = material_uvs(
|
||||
material,
|
||||
position,
|
||||
normal,
|
||||
position_low,
|
||||
mapping_normal,
|
||||
bounds,
|
||||
position,
|
||||
normal,
|
||||
position_low,
|
||||
mapping_normal,
|
||||
bounds,
|
||||
);
|
||||
MeshVertex {
|
||||
@@ -1289,7 +1369,7 @@ fn build_instanced_chunks(
|
||||
};
|
||||
let verts: Vec<_> =
|
||||
if !split_geometry && (shared_vertex_buffer.is_none() || needs_wire_vertices) {
|
||||
(0..mesh.verts.len()).map(vertex).collect()
|
||||
(0..mesh.verts.len()).map(|index| vertex(index, None)).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
@@ -1398,10 +1478,16 @@ fn build_instanced_chunks(
|
||||
.max(1);
|
||||
if first.include_faces || needs_wire_vertices {
|
||||
for triangles in first.indices.chunks(max_triangles * 3) {
|
||||
let vertices: Vec<_> = triangles
|
||||
.iter()
|
||||
.map(|index| vertex(*index as usize))
|
||||
.collect();
|
||||
let mut vertices = Vec::with_capacity(triangles.len());
|
||||
for triangle in triangles.chunks_exact(3) {
|
||||
let mapping_normal = material_has_box_projection(material)
|
||||
.then(|| triangle_mapping_normal(mesh, triangle));
|
||||
vertices.extend(
|
||||
triangle
|
||||
.iter()
|
||||
.map(|index| vertex(*index as usize, mapping_normal)),
|
||||
);
|
||||
}
|
||||
let indices: Vec<_> = (0..vertices.len() as u32).collect();
|
||||
let wire_indices: Vec<_> = if needs_wire_vertices {
|
||||
indices
|
||||
@@ -1592,24 +1678,55 @@ fn material_is_transparent(
|
||||
|| material.is_some_and(|material| {
|
||||
material.translucence > 0.0
|
||||
|| (material.channel_flags as u32 & 0x08 != 0
|
||||
&& material.opacity_map.image.is_some())
|
||||
&& material
|
||||
.opacity_map
|
||||
.image
|
||||
.as_deref()
|
||||
.is_some_and(|image| !opacity_image_is_binary_cutout(image)))
|
||||
})
|
||||
}
|
||||
|
||||
fn opacity_image_is_binary_cutout(
|
||||
image: &crate::scene::model::material_model::MaterialImage,
|
||||
) -> bool {
|
||||
let mut pixels = image.rgba.chunks_exact(4);
|
||||
!image.rgba.is_empty()
|
||||
&& pixels.all(|pixel| matches!(pixel[0], 0 | 255))
|
||||
&& pixels.remainder().is_empty()
|
||||
}
|
||||
|
||||
fn material_map_uv(
|
||||
map: &crate::scene::model::material_model::MeshTextureMap,
|
||||
mapper: Option<&crate::scene::model::material_model::MeshMaterialMapper>,
|
||||
local_position: [f32; 3],
|
||||
local_position_low: [f32; 3],
|
||||
local_normal: [f32; 3],
|
||||
local_bounds: [f32; 6],
|
||||
model_position: [f32; 3],
|
||||
model_position_low: [f32; 3],
|
||||
model_normal: [f32; 3],
|
||||
model_bounds: [f32; 6],
|
||||
) -> [f32; 2] {
|
||||
let (mut position, normal, bounds) = if map.auto_transform & 4 != 0 {
|
||||
(model_position, model_normal, model_bounds)
|
||||
let (mut position, position_low, mut normal, mut bounds) = if map.auto_transform & 4 != 0 {
|
||||
(
|
||||
model_position,
|
||||
model_position_low,
|
||||
model_normal,
|
||||
model_bounds,
|
||||
)
|
||||
} else {
|
||||
(local_position, local_normal, local_bounds)
|
||||
(
|
||||
local_position,
|
||||
local_position_low,
|
||||
local_normal,
|
||||
local_bounds,
|
||||
)
|
||||
};
|
||||
if let Some(mapper) = mapper {
|
||||
position = mapper.map_position(position, position_low);
|
||||
normal = mapper.map_normal(normal);
|
||||
bounds = mapper.map_bounds(bounds);
|
||||
}
|
||||
if map.auto_transform & 2 != 0 {
|
||||
for axis in 0..3 {
|
||||
let extent = bounds[axis + 3] - bounds[axis];
|
||||
@@ -1659,9 +1776,11 @@ fn material_map_uv(
|
||||
fn material_uvs(
|
||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||
local_position: [f32; 3],
|
||||
local_position_low: [f32; 3],
|
||||
local_normal: [f32; 3],
|
||||
local_bounds: [f32; 6],
|
||||
model_position: [f32; 3],
|
||||
model_position_low: [f32; 3],
|
||||
model_normal: [f32; 3],
|
||||
model_bounds: [f32; 6],
|
||||
) -> [[f32; 2]; 7] {
|
||||
@@ -1674,10 +1793,13 @@ fn material_uvs(
|
||||
} else {
|
||||
material_map_uv(
|
||||
map,
|
||||
material.mapper.as_ref(),
|
||||
local_position,
|
||||
local_position_low,
|
||||
local_normal,
|
||||
local_bounds,
|
||||
model_position,
|
||||
model_position_low,
|
||||
model_normal,
|
||||
model_bounds,
|
||||
)
|
||||
@@ -2068,7 +2190,9 @@ pub fn build_mesh_batch_filtered(
|
||||
.visual_style
|
||||
.as_ref()
|
||||
.map_or(part.display_color, |style| style.edge_color(part.display_color));
|
||||
let vtx = |vi: usize| {
|
||||
let vtx = |vi: usize,
|
||||
local_mapping_normal: Option<[f32; 3]>,
|
||||
model_mapping_normal: Option<[f32; 3]>| {
|
||||
let normal = if has_normals {
|
||||
mesh.normals[vi]
|
||||
} else {
|
||||
@@ -2079,6 +2203,11 @@ pub fn build_mesh_batch_filtered(
|
||||
.get(vi)
|
||||
.copied()
|
||||
.unwrap_or(mesh.verts[vi]);
|
||||
let local_position_low = uv_mesh
|
||||
.verts_low
|
||||
.get(vi)
|
||||
.copied()
|
||||
.unwrap_or_else(|| mesh.verts_low.get(vi).copied().unwrap_or([0.0; 3]));
|
||||
let local_normal = uv_mesh
|
||||
.normals
|
||||
.get(vi)
|
||||
@@ -2087,10 +2216,12 @@ pub fn build_mesh_batch_filtered(
|
||||
let uv = material_uvs(
|
||||
material,
|
||||
local_position,
|
||||
local_normal,
|
||||
local_position_low,
|
||||
local_mapping_normal.unwrap_or(local_normal),
|
||||
local_bounds,
|
||||
mesh.verts[vi],
|
||||
normal,
|
||||
mesh.verts_low.get(vi).copied().unwrap_or([0.0; 3]),
|
||||
model_mapping_normal.unwrap_or(normal),
|
||||
model_bounds,
|
||||
);
|
||||
MeshVertex {
|
||||
@@ -2206,7 +2337,8 @@ pub fn build_mesh_batch_filtered(
|
||||
|
||||
// A single mesh larger than a whole chunk: emit as triangle-soup
|
||||
// sub-chunks (corners expanded, no vertex sharing) so each buffer fits.
|
||||
if mesh.verts.len() > max_verts || mesh_tris > max_tris {
|
||||
let box_projected = part.include_faces && material_has_box_projection(material);
|
||||
if mesh.verts.len() > max_verts || mesh_tris > max_tris || box_projected {
|
||||
if !verts.is_empty() || !edge_verts.is_empty() {
|
||||
chunks.push(make_chunk(
|
||||
device,
|
||||
@@ -2244,10 +2376,26 @@ pub fn build_mesh_batch_filtered(
|
||||
let (mut sv, mut si, mut swi) = (Vec::new(), Vec::new(), Vec::new());
|
||||
for tri in t..end {
|
||||
let ix = &part.indices[tri * 3..tri * 3 + 3];
|
||||
let local_mapping_normal =
|
||||
box_projected.then(|| triangle_mapping_normal(uv_mesh, ix));
|
||||
let model_mapping_normal =
|
||||
box_projected.then(|| triangle_mapping_normal(mesh, ix));
|
||||
let b = sv.len() as u32;
|
||||
sv.push(vtx(ix[0] as usize));
|
||||
sv.push(vtx(ix[1] as usize));
|
||||
sv.push(vtx(ix[2] as usize));
|
||||
sv.push(vtx(
|
||||
ix[0] as usize,
|
||||
local_mapping_normal,
|
||||
model_mapping_normal,
|
||||
));
|
||||
sv.push(vtx(
|
||||
ix[1] as usize,
|
||||
local_mapping_normal,
|
||||
model_mapping_normal,
|
||||
));
|
||||
sv.push(vtx(
|
||||
ix[2] as usize,
|
||||
local_mapping_normal,
|
||||
model_mapping_normal,
|
||||
));
|
||||
if part.include_faces {
|
||||
si.extend_from_slice(&[b, b + 1, b + 2]);
|
||||
}
|
||||
@@ -2359,7 +2507,7 @@ pub fn build_mesh_batch_filtered(
|
||||
}
|
||||
let base = verts.len() as u32;
|
||||
for i in 0..mesh.verts.len() {
|
||||
verts.push(vtx(i));
|
||||
verts.push(vtx(i, None, None));
|
||||
}
|
||||
if part.include_faces {
|
||||
let fill = if is_transp { &mut transp_indices } else { &mut indices };
|
||||
@@ -2459,7 +2607,69 @@ pub fn build_mesh_batch_filtered(
|
||||
|
||||
#[cfg(test)]
|
||||
mod texture_limit_tests {
|
||||
use super::downscale_rgba_to_limit;
|
||||
use super::{downscale_rgba_to_limit, opacity_image_is_binary_cutout};
|
||||
use crate::scene::model::material_model::MaterialImage;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn binary_opacity_image_uses_cutout_rendering() {
|
||||
let image = MaterialImage {
|
||||
width: 2,
|
||||
height: 1,
|
||||
rgba: Arc::new(vec![0, 0, 0, 255, 255, 255, 255, 255]),
|
||||
path: String::new(),
|
||||
};
|
||||
assert!(opacity_image_is_binary_cutout(&image));
|
||||
|
||||
let blended = MaterialImage {
|
||||
rgba: Arc::new(vec![127, 127, 127, 255]),
|
||||
width: 1,
|
||||
height: 1,
|
||||
path: String::new(),
|
||||
};
|
||||
assert!(!opacity_image_is_binary_cutout(&blended));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_mapped_uv_is_stable_at_large_coordinates() {
|
||||
let map = crate::scene::model::material_model::MeshTextureMap::default();
|
||||
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
|
||||
let local_mapper = crate::scene::model::material_model::MeshMaterialMapper {
|
||||
origin: [1.0, 2.0, 3.0],
|
||||
inverse_basis: identity,
|
||||
normal_basis: identity,
|
||||
};
|
||||
let local = super::material_map_uv(
|
||||
&map,
|
||||
Some(&local_mapper),
|
||||
[3.0, 4.0, 5.0],
|
||||
[0.0; 3],
|
||||
[0.0, 0.0, 1.0],
|
||||
[1.0, 2.0, 3.0, 11.0, 12.0, 13.0],
|
||||
[3.0, 4.0, 5.0],
|
||||
[0.0; 3],
|
||||
[0.0, 0.0, 1.0],
|
||||
[1.0, 2.0, 3.0, 11.0, 12.0, 13.0],
|
||||
);
|
||||
let shifted_mapper = crate::scene::model::material_model::MeshMaterialMapper {
|
||||
origin: [1_200_001.0, -799_998.0, 3.0],
|
||||
inverse_basis: identity,
|
||||
normal_basis: identity,
|
||||
};
|
||||
let shifted = super::material_map_uv(
|
||||
&map,
|
||||
Some(&shifted_mapper),
|
||||
[1_200_003.0, -799_996.0, 5.0],
|
||||
[0.0; 3],
|
||||
[0.0, 0.0, 1.0],
|
||||
[1_200_001.0, -799_998.0, 3.0, 1_200_011.0, -799_988.0, 13.0],
|
||||
[1_200_003.0, -799_996.0, 5.0],
|
||||
[0.0; 3],
|
||||
[0.0, 0.0, 1.0],
|
||||
[1_200_001.0, -799_998.0, 3.0, 1_200_011.0, -799_988.0, 13.0],
|
||||
);
|
||||
assert_eq!(local, shifted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn downscale_keeps_both_dimensions_within_the_limit() {
|
||||
|
||||
@@ -583,6 +583,9 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
}
|
||||
material_alpha *= 1.0 - clamp(surface.ambient.a * max(surface.advanced.w, 0.0), 0.0, 0.95);
|
||||
material_alpha *= clamp(u.visual_style.y, 0.0, 1.0);
|
||||
if (u.transparency_enable > 0.5 && material_alpha <= 0.001) {
|
||||
discard;
|
||||
}
|
||||
let alpha = select(1.0, material_alpha, u.transparency_enable > 0.5);
|
||||
return vec4<f32>(rgb, alpha);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user