Render procedural checker materials correctly

This commit is contained in:
Hakan Seven committed 2026-09-13 01:50:42 +03:00
1 parent 12c1c330fc
commit 07acd1284c
9 files changed
+440 -39

No files matched your search

Generated
+3 -3
View File
@@ -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
View File
@@ -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"] }
+2 -2
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"] }
+1 -1
View File
@@ -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" }
+192 -4
View File
@@ -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
View File
@@ -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() {
+3
View File
@@ -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);
}