diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index c456b3008850f..16cfc2c1ed688 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -2,7 +2,11 @@ name = "bevy_mikktspace" version = "0.8.0-dev" edition = "2021" -authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] +authors = [ + "Benjamin Wasty ", + "David Harvey-Macaulay ", + "Layl Bongers ", +] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/bevy" homepage = "https://bevyengine.org" @@ -12,6 +16,10 @@ keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"] [dependencies] glam = "0.20.0" +bitflags = "1.3.2" +# id-arena = "2.2.1" +# smallvec = { version = "1.6", features = ["union"] } +ordered-float = "3.0.0" [[example]] name = "generate" diff --git a/crates/bevy_mikktspace/examples/generate.rs b/crates/bevy_mikktspace/examples/generate.rs index a8cefb8809ff4..cd1ce24d52edc 100644 --- a/crates/bevy_mikktspace/examples/generate.rs +++ b/crates/bevy_mikktspace/examples/generate.rs @@ -1,5 +1,6 @@ #![allow(clippy::bool_assert_comparison, clippy::useless_conversion)] +use bevy_mikktspace::FaceKind; use glam::{Vec2, Vec3}; pub type Face = [u32; 3]; @@ -26,8 +27,8 @@ impl bevy_mikktspace::Geometry for Mesh { self.faces.len() } - fn num_vertices_of_face(&self, _face: usize) -> usize { - 3 + fn num_vertices_of_face(&self, _face: usize) -> FaceKind { + FaceKind::Triangle } fn position(&self, face: usize, vert: usize) -> [f32; 3] { diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index c05b8c1566ee3..e147568ad238f 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -26,30 +26,19 @@ //! misrepresented as being the original software. //! //! 3. This notice may not be removed or altered from any source distribution. +//! +// Comments starting with `C:` are copied as-is from the original +// Note that some comments may originate from the original but not be marked as such -#![allow( - clippy::all, - clippy::doc_markdown, - clippy::redundant_else, - clippy::match_same_arms, - clippy::semicolon_if_nothing_returned, - clippy::explicit_iter_loop, - clippy::map_flatten, - dead_code, - mutable_transmutes, - non_camel_case_types, - non_snake_case, - non_upper_case_globals, - unused_mut, - unused_assignments, - unused_variables -)] +#![allow(non_camel_case_types, non_snake_case)] -use std::ptr::null_mut; +mod degenerate; +mod setup; +use bitflags::bitflags; use glam::Vec3; -use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; +use crate::{get_normal, get_position, FaceKind, Geometry}; #[derive(Copy, Clone)] pub struct STSpace { @@ -74,58 +63,55 @@ impl STSpace { } } -// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -// normal map sampler must use the exact inverse of the pixel shader transformation. -// The most efficient transformation we can possibly do in the pixel shader is -// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -// pixel shader (fast transform out) -// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -// where vNt is the tangent space normal. The normal map sampler must likewise use the -// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -// sampler does (exact inverse of pixel shader): -// float3 row0 = cross(vB, vN); -// float3 row1 = cross(vN, vT); -// float3 row2 = cross(vT, vB); -// float fSign = dot(vT, row0)<0 ? -1 : 1; -// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -// where vNout is the sampled normal in some chosen 3D space. -// -// Should you choose to reconstruct the bitangent in the pixel shader instead -// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -// However, this must be used both by the sampler and your tools/rendering pipeline. -// internal structure +bitflags! { + pub struct TriangleFlags: u8 { + /// This triangle has multiple vertices at the same point + const DEGENERATE = 1; + /// This triangle is part of a quad where one (but not both) + /// of its triangles are degenerate (i.e. exactly two of the quad's + /// vertices are in the same location) + const QUAD_ONE_DEGENERATE_TRI = 2; + const GROUP_WITH_ANY = 4; + const ORIENT_PRESERVING = 8; + } +} #[derive(Copy, Clone)] pub struct STriInfo { + /// Indices of neighbouring triangles across this triangle's edges pub FaceNeighbors: [i32; 3], - pub AssignedGroup: [*mut SGroup; 3], + /// The group each vertex belongs to. + /// This should be Option, but rustc hasn't added that yet + pub AssignedGroup: [usize; 3], pub vOs: Vec3, pub vOt: Vec3, pub fMagS: f32, pub fMagT: f32, + /// The face in the user's module this triangle comes from pub iOrgFaceNumber: i32, - pub iFlag: i32, + // Flags set for this triangle + pub iFlag: TriangleFlags, pub iTSpacesOffs: i32, - pub vert_num: [u8; 4], + // The vertices of the face 'iOrgFaceNumber' this triangle covers + // This has only a limited set of valid values - as required for quads. + // - TODO: Convert to a repr(u8) enum to compress. + // In theory, this could be compressed inside TriangleFlags too. + pub vert_num: [u8; 3], } impl STriInfo { fn zero() -> Self { Self { - FaceNeighbors: [0, 0, 0], - AssignedGroup: [null_mut(), null_mut(), null_mut()], + FaceNeighbors: [-1, -1, -1], + AssignedGroup: [usize::MAX; 3], vOs: Default::default(), vOt: Default::default(), fMagS: 0.0, fMagT: 0.0, iOrgFaceNumber: 0, - iFlag: 0, + iFlag: TriangleFlags::empty(), iTSpacesOffs: 0, - vert_num: [0, 0, 0, 0], + vert_num: [0, 0, 0], } } } @@ -133,7 +119,7 @@ impl STriInfo { #[derive(Copy, Clone)] pub struct SGroup { pub iNrFaces: i32, - pub pFaceIndices: *mut i32, + pub pFaceIndices: usize, pub iVertexRepresentitive: i32, pub bOrientPreservering: bool, } @@ -142,14 +128,14 @@ impl SGroup { fn zero() -> Self { Self { iNrFaces: 0, - pFaceIndices: null_mut(), + pFaceIndices: usize::MAX, iVertexRepresentitive: 0, bOrientPreservering: false, } } } -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct SSubGroup { pub iNrFaces: i32, pub pTriMembers: Vec, @@ -164,118 +150,94 @@ impl SSubGroup { } } -#[derive(Copy, Clone)] -pub union SEdge { - pub unnamed: unnamed, - pub array: [i32; 3], -} - -impl SEdge { - fn zero() -> Self { - Self { array: [0, 0, 0] } - } -} - -#[derive(Copy, Clone)] -pub struct unnamed { +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct SEdge { + // The first vertex's (global) index. This is the minimum index pub i0: i32, + // The second vertex's (global) index pub i1: i32, + // The face this edge is associated with pub f: i32, } -#[derive(Copy, Clone)] -pub struct STmpVert { - pub vert: [f32; 3], - pub index: i32, -} - -impl STmpVert { - fn zero() -> Self { - Self { - vert: [0.0, 0.0, 0.0], - index: 0, - } - } -} +// Entry point +pub fn genTangSpace(geometry: &mut impl Geometry, fAngularThreshold: f32) -> bool { + // TODO: Accept in radians by default here? + let fThresCos = (fAngularThreshold.to_radians()).cos(); -pub unsafe fn genTangSpace(geometry: &mut I, fAngularThreshold: f32) -> bool { - let mut iNrTrianglesIn = 0; - let mut f = 0; - let mut t = 0; - let mut i = 0; - let mut iNrTSPaces = 0; - let mut iTotTris = 0; - let mut iDegenTriangles = 0; - let mut iNrMaxGroups = 0; - let mut iNrActiveGroups: i32 = 0i32; - let mut index = 0; let iNrFaces = geometry.num_faces(); - let mut bRes: bool = false; - let fThresCos: f32 = - ((fAngularThreshold * 3.14159265358979323846f64 as f32 / 180.0f32) as f64).cos() as f32; - f = 0; - while f < iNrFaces { + let mut iNrTrianglesIn = 0; + for f in 0..iNrFaces { let verts = geometry.num_vertices_of_face(f); - if verts == 3 { - iNrTrianglesIn += 1 - } else if verts == 4 { - iNrTrianglesIn += 2 + match verts { + FaceKind::Triangle => iNrTrianglesIn += 1, + FaceKind::Quad => iNrTrianglesIn += 2, } - f += 1 } - if iNrTrianglesIn <= 0 { + + if iNrTrianglesIn == 0 { + // Easier if we can assume there's at least one face later + // No tangents need to be generated return false; } - + let iNrTrianglesIn = iNrTrianglesIn; let mut piTriListIn = vec![0i32; 3 * iNrTrianglesIn]; let mut pTriInfos = vec![STriInfo::zero(); iNrTrianglesIn]; - iNrTSPaces = GenerateInitialVerticesIndexList( + // C: Make an initial triangle --> face index list + // This also handles quads + // TODO: Make this return triangle_info and tri_face_map + // probably in a single structure. + let iNrTSPaces = setup::GenerateInitialVerticesIndexList( &mut pTriInfos, &mut piTriListIn, geometry, iNrTrianglesIn, ); - GenerateSharedVerticesIndexList(piTriListIn.as_mut_ptr(), geometry, iNrTrianglesIn); - iTotTris = iNrTrianglesIn; - iDegenTriangles = 0; - t = 0; - while t < iTotTris as usize { - let i0 = piTriListIn[t * 3 + 0]; + // C: Make a welded index list of identical positions and attributes (pos, norm, texc) + setup::GenerateSharedVerticesIndexList(&mut piTriListIn, geometry); + + let iTotTris = iNrTrianglesIn; + let mut iDegenTriangles = 0; + // C: Mark all degenerate triangles + for t in 0..(iTotTris as usize) { + let i0 = piTriListIn[t * 3]; let i1 = piTriListIn[t * 3 + 1]; let i2 = piTriListIn[t * 3 + 2]; let p0 = get_position(geometry, i0 as usize); let p1 = get_position(geometry, i1 as usize); let p2 = get_position(geometry, i2 as usize); if p0 == p1 || p0 == p2 || p1 == p2 { - pTriInfos[t].iFlag |= 1i32; - iDegenTriangles += 1 - } - t += 1 - } - iNrTrianglesIn = iTotTris - iDegenTriangles; - DegenPrologue( - pTriInfos.as_mut_ptr(), - piTriListIn.as_mut_ptr(), + pTriInfos[t].iFlag.insert(TriangleFlags::DEGENERATE); + iDegenTriangles += 1; + } + } + let iNrTrianglesIn = iTotTris - iDegenTriangles; + // C: Mark all triangle pairs that belong to a quad with only one + // C: good triangle. These need special treatment in DegenEpilogue(). + // C: Additionally, move all good triangles to the start of + // C: pTriInfos[] and piTriListIn[] without changing order and + // C: put the degenerate triangles last. + // Note: A quad can have degenerate triangles if two vertices are in the same location + degenerate::DegenPrologue( + &mut pTriInfos, + &mut piTriListIn, iNrTrianglesIn as i32, iTotTris as i32, ); - InitTriInfo( - pTriInfos.as_mut_ptr(), - piTriListIn.as_ptr(), - geometry, - iNrTrianglesIn, - ); - iNrMaxGroups = iNrTrianglesIn * 3; + // C: Evaluate triangle level attributes and neighbor list + setup::InitTriInfo(&mut pTriInfos, &piTriListIn, geometry, iNrTrianglesIn); + //C: Based on the 4 rules, identify groups based on connectivity + let iNrMaxGroups = iNrTrianglesIn * 3; let mut pGroups = vec![SGroup::zero(); iNrMaxGroups]; let mut piGroupTrianglesBuffer = vec![0; iNrTrianglesIn * 3]; - iNrActiveGroups = Build4RuleGroups( - pTriInfos.as_mut_ptr(), - pGroups.as_mut_ptr(), - piGroupTrianglesBuffer.as_mut_ptr(), - piTriListIn.as_ptr(), + let iNrActiveGroups = Build4RuleGroups( + &mut pTriInfos, + &mut pGroups, + &mut piGroupTrianglesBuffer, + &piTriListIn, iNrTrianglesIn as i32, ); @@ -290,302 +252,179 @@ pub unsafe fn genTangSpace(geometry: &mut I, fAngularThreshold: f32 iNrTSPaces ]; - bRes = GenerateTSpaces( + let bRes = GenerateTSpaces( &mut psTspace, - pTriInfos.as_ptr(), - pGroups.as_ptr(), + &pTriInfos, + &pGroups, iNrActiveGroups, - piTriListIn.as_ptr(), + &piTriListIn, fThresCos, geometry, + &piGroupTrianglesBuffer, ); if !bRes { return false; } - DegenEpilogue( - psTspace.as_mut_ptr(), - pTriInfos.as_mut_ptr(), - piTriListIn.as_mut_ptr(), + degenerate::DegenEpilogue( + &mut psTspace, + &mut pTriInfos, + &mut piTriListIn, geometry, iNrTrianglesIn as i32, iTotTris as i32, ); - index = 0; - f = 0; - while f < iNrFaces { - let verts_0 = geometry.num_vertices_of_face(f); - if !(verts_0 != 3 && verts_0 != 4) { - i = 0; - while i < verts_0 { - let mut pTSpace: *const STSpace = &mut psTspace[index] as *mut STSpace; - let mut tang = Vec3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); - let mut bitang = Vec3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); - geometry.set_tangent( - tang.into(), - bitang.into(), - (*pTSpace).fMagS, - (*pTSpace).fMagT, - (*pTSpace).bOrient, - f, - i, - ); - index += 1; - i += 1 - } - } - f += 1 - } - return true; -} -unsafe fn DegenEpilogue( - mut psTspace: *mut STSpace, - mut pTriInfos: *mut STriInfo, - mut piTriListIn: *mut i32, - geometry: &mut I, - iNrTrianglesIn: i32, - iTotTris: i32, -) { - let mut t: i32 = 0i32; - let mut i: i32 = 0i32; - t = iNrTrianglesIn; - while t < iTotTris { - let bSkip: bool = if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { - true - } else { - false - }; - if !bSkip { - i = 0i32; - while i < 3i32 { - let index1: i32 = *piTriListIn.offset((t * 3i32 + i) as isize); - let mut bNotFound: bool = true; - let mut j: i32 = 0i32; - while bNotFound && j < 3i32 * iNrTrianglesIn { - let index2: i32 = *piTriListIn.offset(j as isize); - if index1 == index2 { - bNotFound = false - } else { - j += 1 - } - } - if !bNotFound { - let iTri: i32 = j / 3i32; - let iVert: i32 = j % 3i32; - let iSrcVert: i32 = - (*pTriInfos.offset(iTri as isize)).vert_num[iVert as usize] as i32; - let iSrcOffs: i32 = (*pTriInfos.offset(iTri as isize)).iTSpacesOffs; - let iDstVert: i32 = (*pTriInfos.offset(t as isize)).vert_num[i as usize] as i32; - let iDstOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; - *psTspace.offset((iDstOffs + iDstVert) as isize) = - *psTspace.offset((iSrcOffs + iSrcVert) as isize) - } - i += 1 - } - } - t += 1 - } - t = 0i32; - while t < iNrTrianglesIn { - if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { - let mut vDstP = Vec3::new(0.0, 0.0, 0.0); - let mut iOrgF: i32 = -1i32; - let mut i_0: i32 = 0i32; - let mut bNotFound_0: bool = false; - let mut pV: *mut u8 = (*pTriInfos.offset(t as isize)).vert_num.as_mut_ptr(); - let mut iFlag: i32 = 1i32 << *pV.offset(0isize) as i32 - | 1i32 << *pV.offset(1isize) as i32 - | 1i32 << *pV.offset(2isize) as i32; - let mut iMissingIndex: i32 = 0i32; - if iFlag & 2i32 == 0i32 { - iMissingIndex = 1i32 - } else if iFlag & 4i32 == 0i32 { - iMissingIndex = 2i32 - } else if iFlag & 8i32 == 0i32 { - iMissingIndex = 3i32 - } - iOrgF = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - vDstP = get_position( - geometry, - face_vert_to_index(iOrgF as usize, iMissingIndex as usize), + // Output generated triangles + let mut index = 0; + for f in 0..iNrFaces { + let verts_0 = geometry.num_vertices_of_face(f); + for i in 0..verts_0.num_vertices() { + let pTSpace = &psTspace[index]; + geometry.set_tangent( + pTSpace.vOs.into(), + pTSpace.vOt.into(), + pTSpace.fMagS, + pTSpace.fMagT, + (*pTSpace).bOrient, + f, + i, ); - bNotFound_0 = true; - i_0 = 0i32; - while bNotFound_0 && i_0 < 3i32 { - let iVert_0: i32 = *pV.offset(i_0 as isize) as i32; - let vSrcP = get_position( - geometry, - face_vert_to_index(iOrgF as usize, iVert_0 as usize), - ); - if vSrcP == vDstP { - let iOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; - *psTspace.offset((iOffs + iMissingIndex) as isize) = - *psTspace.offset((iOffs + iVert_0) as isize); - bNotFound_0 = false - } else { - i_0 += 1 - } - } + index += 1; } - t += 1 } + + true } -unsafe fn GenerateTSpaces( +fn GenerateTSpaces( psTspace: &mut [STSpace], - mut pTriInfos: *const STriInfo, - mut pGroups: *const SGroup, + pTriInfos: &[STriInfo], + pGroups: &[SGroup], iNrActiveGroups: i32, - mut piTriListIn: *const i32, + piTriListIn: &[i32], fThresCos: f32, - geometry: &mut I, + geometry: &impl Geometry, + piGroupTrianglesBuffer: &[i32], ) -> bool { - let mut iMaxNrFaces: usize = 0; - let mut iUniqueTspaces = 0; - let mut g: i32 = 0i32; - let mut i: i32 = 0i32; - g = 0i32; - while g < iNrActiveGroups { - if iMaxNrFaces < (*pGroups.offset(g as isize)).iNrFaces as usize { - iMaxNrFaces = (*pGroups.offset(g as isize)).iNrFaces as usize - } - g += 1 - } - if iMaxNrFaces == 0 { - return true; - } + let iMaxNrFaces = pGroups[..iNrActiveGroups as usize] + .iter() + .map(|it| it.iNrFaces) + .max(); + + let iMaxNrFaces = match iMaxNrFaces { + Some(0) | None => return true, + Some(iMaxNrFaces) => iMaxNrFaces as usize, + }; let mut pSubGroupTspace = vec![STSpace::zero(); iMaxNrFaces]; let mut pUniSubGroups = vec![SSubGroup::zero(); iMaxNrFaces]; let mut pTmpMembers = vec![0i32; iMaxNrFaces]; - iUniqueTspaces = 0; - g = 0i32; - while g < iNrActiveGroups { - let mut pGroup: *const SGroup = &*pGroups.offset(g as isize) as *const SGroup; + for g in 0..iNrActiveGroups { + let pGroup: &SGroup = &pGroups[g as usize]; let mut iUniqueSubGroups = 0; - let mut s = 0; - i = 0i32; - while i < (*pGroup).iNrFaces { - let f: i32 = *(*pGroup).pFaceIndices.offset(i as isize); - let mut index: i32 = -1i32; - let mut iVertIndex: i32 = -1i32; - let mut iOF_1: i32 = -1i32; - let mut iMembers: usize = 0; - let mut j: i32 = 0i32; - let mut l: usize = 0; + + for i in 0..pGroup.iNrFaces { + let f = piGroupTrianglesBuffer[pGroup.pFaceIndices + i as usize]; let mut tmp_group: SSubGroup = SSubGroup { iNrFaces: 0, pTriMembers: Vec::new(), }; - let mut bFound: bool = false; - let mut n = Vec3::new(0.0, 0.0, 0.0); - let mut vOs = Vec3::new(0.0, 0.0, 0.0); - let mut vOt = Vec3::new(0.0, 0.0, 0.0); - if (*pTriInfos.offset(f as isize)).AssignedGroup[0usize] == pGroup as *mut SGroup { - index = 0i32 - } else if (*pTriInfos.offset(f as isize)).AssignedGroup[1usize] == pGroup as *mut SGroup - { - index = 1i32 - } else if (*pTriInfos.offset(f as isize)).AssignedGroup[2usize] == pGroup as *mut SGroup - { - index = 2i32 - } - iVertIndex = *piTriListIn.offset((f * 3i32 + index) as isize); - n = get_normal(geometry, iVertIndex as usize); - let mut vOs = (*pTriInfos.offset(f as isize)).vOs - - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); - let mut vOt = (*pTriInfos.offset(f as isize)).vOt - - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); - if VNotZero(vOs) { - vOs = Normalize(vOs) - } - if VNotZero(vOt) { - vOt = Normalize(vOt) - } - iOF_1 = (*pTriInfos.offset(f as isize)).iOrgFaceNumber; - iMembers = 0; - j = 0i32; - while j < (*pGroup).iNrFaces { - let t: i32 = *(*pGroup).pFaceIndices.offset(j as isize); - let iOF_2: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs - - (n.dot((*pTriInfos.offset(t as isize)).vOs) * n); - let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt - - (n.dot((*pTriInfos.offset(t as isize)).vOt) * n); - if VNotZero(vOs2) { - vOs2 = Normalize(vOs2) - } - if VNotZero(vOt2) { - vOt2 = Normalize(vOt2) - } - let bAny: bool = if ((*pTriInfos.offset(f as isize)).iFlag - | (*pTriInfos.offset(t as isize)).iFlag) - & 4i32 - != 0i32 - { - true - } else { - false - }; + let index = pTriInfos[f as usize] + .AssignedGroup + .iter() + .position(|&it| it == g as usize) + .unwrap(); + + let iVertIndex = piTriListIn[(f * 3 + index as i32) as usize]; + assert!(iVertIndex == (*pGroup).iVertexRepresentitive); + let n = get_normal(geometry, iVertIndex as usize); + let mut vOs = pTriInfos[f as usize].vOs - (n.dot(pTriInfos[f as usize].vOs) * n); + let mut vOt = pTriInfos[f as usize].vOt - (n.dot(pTriInfos[f as usize].vOt) * n); + vOs = vOs.normalize_or_zero(); + vOt = vOt.normalize_or_zero(); + + let iOF_1 = pTriInfos[f as usize].iOrgFaceNumber; + let mut iMembers = 0; + + for j in 0..pGroup.iNrFaces { + let t: i32 = piGroupTrianglesBuffer[pGroup.pFaceIndices + j as usize]; + let tri = &pTriInfos[t as usize]; + let iOF_2: i32 = tri.iOrgFaceNumber; + let mut vOs2 = tri.vOs - (n.dot(tri.vOs) * n); + let mut vOt2 = tri.vOt - (n.dot(tri.vOt) * n); + vOs2 = vOs2.normalize_or_zero(); + vOt2 = vOt2.normalize_or_zero(); + + let bAny: bool = (pTriInfos[f as usize].iFlag | pTriInfos[t as usize].iFlag) + .contains(TriangleFlags::GROUP_WITH_ANY); let bSameOrgFace: bool = iOF_1 == iOF_2; let fCosS: f32 = vOs.dot(vOs2); let fCosT: f32 = vOt.dot(vOt2); + debug_assert!(f != t || bSameOrgFace); // sanity check if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { - let fresh0 = iMembers; - iMembers = iMembers + 1; - pTmpMembers[fresh0] = t + pTmpMembers[iMembers] = t; + iMembers += 1; } - j += 1 } if iMembers > 1 { - let mut uSeed: u32 = 39871946i32 as u32; - QuickSort(pTmpMembers.as_mut_ptr(), 0i32, (iMembers - 1) as i32, uSeed); + pTmpMembers[0..(iMembers - 1)].sort_unstable(); } tmp_group.iNrFaces = iMembers as i32; tmp_group.pTriMembers = pTmpMembers.clone(); - bFound = false; - l = 0; - while l < iUniqueSubGroups && !bFound { - bFound = CompareSubGroups(&mut tmp_group, &mut pUniSubGroups[l]); - if !bFound { - l += 1 + + let mut found = None; + #[allow(clippy::needless_range_loop)] + for l in 0..iUniqueSubGroups { + if tmp_group == pUniSubGroups[l] { + found = Some(l); + break; } } - if !bFound { - pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as i32; - pUniSubGroups[iUniqueSubGroups].pTriMembers = tmp_group.pTriMembers.clone(); + let idx; + if let Some(it) = found { + idx = it; + } else { + idx = iUniqueSubGroups; pSubGroupTspace[iUniqueSubGroups] = EvalTspace( - tmp_group.pTriMembers.as_mut_ptr(), + &mut tmp_group.pTriMembers, iMembers as i32, piTriListIn, pTriInfos, geometry, (*pGroup).iVertexRepresentitive, ); - iUniqueSubGroups += 1 - } - let iOffs = (*pTriInfos.offset(f as isize)).iTSpacesOffs as usize; - let iVert = (*pTriInfos.offset(f as isize)).vert_num[index as usize] as usize; - let mut pTS_out: *mut STSpace = &mut psTspace[iOffs + iVert] as *mut STSpace; + // C: if no match was found we allocate a new subgroup + pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as i32; + pUniSubGroups[iUniqueSubGroups].pTriMembers = tmp_group.pTriMembers; + iUniqueSubGroups += 1; + } + let iOffs = pTriInfos[f as usize].iTSpacesOffs as usize; + let iVert = pTriInfos[f as usize].vert_num[index as usize] as usize; + let mut pTS_out = &mut psTspace[iOffs + iVert]; + assert!(pTS_out.iCounter < 2); + debug_assert!( + (*pGroup).bOrientPreservering + == pTriInfos[f as usize] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING) + ); if (*pTS_out).iCounter == 1i32 { - *pTS_out = AvgTSpace(pTS_out, &mut pSubGroupTspace[l]); + *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[idx]); (*pTS_out).iCounter = 2i32; - (*pTS_out).bOrient = (*pGroup).bOrientPreservering + (*pTS_out).bOrient = (*pGroup).bOrientPreservering; } else { - *pTS_out = pSubGroupTspace[l]; + debug_assert!(pTS_out.iCounter == 0); + *pTS_out = pSubGroupTspace[idx]; (*pTS_out).iCounter = 1i32; - (*pTS_out).bOrient = (*pGroup).bOrientPreservering + (*pTS_out).bOrient = (*pGroup).bOrientPreservering; } - i += 1 } - iUniqueTspaces += iUniqueSubGroups; - g += 1 } - return true; + true } -unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpace { +fn AvgTSpace(pTS0: &STSpace, pTS1: &STSpace) -> STSpace { let mut ts_res: STSpace = STSpace { vOs: Vec3::new(0.0, 0.0, 0.0), fMagS: 0., @@ -602,1208 +441,228 @@ unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpa ts_res.fMagS = (*pTS0).fMagS; ts_res.fMagT = (*pTS0).fMagT; ts_res.vOs = (*pTS0).vOs; - ts_res.vOt = (*pTS0).vOt + ts_res.vOt = (*pTS0).vOt; } else { ts_res.fMagS = 0.5f32 * ((*pTS0).fMagS + (*pTS1).fMagS); ts_res.fMagT = 0.5f32 * ((*pTS0).fMagT + (*pTS1).fMagT); ts_res.vOs = (*pTS0).vOs + (*pTS1).vOs; ts_res.vOt = (*pTS0).vOt + (*pTS1).vOt; - if VNotZero(ts_res.vOs) { - ts_res.vOs = Normalize(ts_res.vOs) - } - if VNotZero(ts_res.vOt) { - ts_res.vOt = Normalize(ts_res.vOt) - } + ts_res.vOs = ts_res.vOs.normalize_or_zero(); + ts_res.vOt = ts_res.vOt.normalize_or_zero(); } - return ts_res; -} - -unsafe fn Normalize(v: Vec3) -> Vec3 { - return (1.0 / v.length()) * v; + ts_res } -unsafe fn VNotZero(v: Vec3) -> bool { - NotZero(v.x) || NotZero(v.y) || NotZero(v.z) -} - -unsafe fn NotZero(fX: f32) -> bool { - fX.abs() > 1.17549435e-38f32 -} - -unsafe fn EvalTspace( - mut face_indices: *mut i32, +fn EvalTspace( + face_indices: &mut [i32], iFaces: i32, - mut piTriListIn: *const i32, - mut pTriInfos: *const STriInfo, - geometry: &mut I, + piTriListIn: &[i32], + pTriInfos: &[STriInfo], + geometry: &impl Geometry, iVertexRepresentitive: i32, ) -> STSpace { - let mut res: STSpace = STSpace { - vOs: Vec3::new(0.0, 0.0, 0.0), - fMagS: 0., - vOt: Vec3::new(0.0, 0.0, 0.0), - fMagT: 0., - iCounter: 0, - bOrient: false, - }; + let mut res: STSpace = STSpace::zero(); let mut fAngleSum: f32 = 0i32 as f32; - let mut face: i32 = 0i32; - res.vOs.x = 0.0f32; - res.vOs.y = 0.0f32; - res.vOs.z = 0.0f32; - res.vOt.x = 0.0f32; - res.vOt.y = 0.0f32; - res.vOt.z = 0.0f32; - res.fMagS = 0i32 as f32; - res.fMagT = 0i32 as f32; - face = 0i32; - while face < iFaces { - let f: i32 = *face_indices.offset(face as isize); - if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 { - let mut n = Vec3::new(0.0, 0.0, 0.0); - let mut vOs = Vec3::new(0.0, 0.0, 0.0); - let mut vOt = Vec3::new(0.0, 0.0, 0.0); - let mut p0 = Vec3::new(0.0, 0.0, 0.0); - let mut p1 = Vec3::new(0.0, 0.0, 0.0); - let mut p2 = Vec3::new(0.0, 0.0, 0.0); - let mut v1 = Vec3::new(0.0, 0.0, 0.0); - let mut v2 = Vec3::new(0.0, 0.0, 0.0); - let mut fCos: f32 = 0.; - let mut fAngle: f32 = 0.; - let mut fMagS: f32 = 0.; - let mut fMagT: f32 = 0.; - let mut i: i32 = -1i32; - let mut index: i32 = -1i32; - let mut i0: i32 = -1i32; - let mut i1: i32 = -1i32; - let mut i2: i32 = -1i32; - if *piTriListIn.offset((3i32 * f + 0i32) as isize) == iVertexRepresentitive { - i = 0i32 - } else if *piTriListIn.offset((3i32 * f + 1i32) as isize) == iVertexRepresentitive { - i = 1i32 - } else if *piTriListIn.offset((3i32 * f + 2i32) as isize) == iVertexRepresentitive { - i = 2i32 - } - index = *piTriListIn.offset((3i32 * f + i) as isize); - n = get_normal(geometry, index as usize); - let mut vOs = (*pTriInfos.offset(f as isize)).vOs - - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); - let mut vOt = (*pTriInfos.offset(f as isize)).vOt - - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); - if VNotZero(vOs) { - vOs = Normalize(vOs) - } - if VNotZero(vOt) { - vOt = Normalize(vOt) - } - i2 = *piTriListIn.offset((3i32 * f + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); - i1 = *piTriListIn.offset((3i32 * f + i) as isize); - i0 = *piTriListIn.offset((3i32 * f + if i > 0i32 { i - 1i32 } else { 2i32 }) as isize); - p0 = get_position(geometry, i0 as usize); - p1 = get_position(geometry, i1 as usize); - p2 = get_position(geometry, i2 as usize); - v1 = p0 - p1; - v2 = p2 - p1; - let mut v1 = v1 - (n.dot(v1) * n); - if VNotZero(v1) { - v1 = Normalize(v1) - } - let mut v2 = v2 - (n.dot(v2) * n); - if VNotZero(v2) { - v2 = Normalize(v2) - } - let fCos = v1.dot(v2); - let fCos = if fCos > 1i32 as f32 { - 1i32 as f32 - } else if fCos < -1i32 as f32 { - -1i32 as f32 + for face in 0..iFaces { + let f: i32 = face_indices[face as usize]; + if !pTriInfos[f as usize] + .iFlag + .contains(TriangleFlags::GROUP_WITH_ANY) + { + let i: i32 = if piTriListIn[(3i32 * f) as usize] == iVertexRepresentitive { + 0i32 + } else if piTriListIn[(3i32 * f + 1i32) as usize] == iVertexRepresentitive { + 1i32 + } else if piTriListIn[(3i32 * f + 2i32) as usize] == iVertexRepresentitive { + 2i32 } else { - fCos + panic!(); }; - fAngle = (fCos as f64).acos() as f32; - fMagS = (*pTriInfos.offset(f as isize)).fMagS; - fMagT = (*pTriInfos.offset(f as isize)).fMagT; - res.vOs = res.vOs + (fAngle * vOs); - res.vOt = res.vOt + (fAngle * vOt); + let index = piTriListIn[(3i32 * f + i) as usize]; + let n = get_normal(geometry, index as usize); + let mut vOs = pTriInfos[f as usize].vOs - (n.dot(pTriInfos[f as usize].vOs) * n); + let mut vOt = pTriInfos[f as usize].vOt - (n.dot(pTriInfos[f as usize].vOt) * n); + vOs = vOs.normalize_or_zero(); + vOt = vOt.normalize_or_zero(); + + let i2 = piTriListIn[(3i32 * f + (i + 1) % 3) as usize]; + let i1 = piTriListIn[(3i32 * f + i) as usize]; + let i0 = piTriListIn[(3i32 * f + (i + 2) % 3) as usize]; + let p0 = get_position(geometry, i0 as usize); + let p1 = get_position(geometry, i1 as usize); + let p2 = get_position(geometry, i2 as usize); + let v1 = p0 - p1; + let v2 = p2 - p1; + let mut v1 = v1 - (n.dot(v1) * n); + v1 = v1.normalize_or_zero(); + + let mut v2 = v2 - (n.dot(v2) * n); + v2 = v2.normalize_or_zero(); + let fCos = v1.dot(v2).clamp(-1., 1.); + + let fAngle = (fCos as f64).acos() as f32; + let fMagS = pTriInfos[f as usize].fMagS; + let fMagT = pTriInfos[f as usize].fMagT; + res.vOs += fAngle * vOs; + res.vOt += fAngle * vOt; res.fMagS += fAngle * fMagS; res.fMagT += fAngle * fMagT; - fAngleSum += fAngle + fAngleSum += fAngle; } - face += 1 - } - if VNotZero(res.vOs) { - res.vOs = Normalize(res.vOs) - } - if VNotZero(res.vOt) { - res.vOt = Normalize(res.vOt) } + res.vOs = res.vOs.normalize_or_zero(); + res.vOt = res.vOt.normalize_or_zero(); + if fAngleSum > 0i32 as f32 { res.fMagS /= fAngleSum; - res.fMagT /= fAngleSum + res.fMagT /= fAngleSum; } - return res; + res } -unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) -> bool { - let mut bStillSame: bool = true; - let mut i = 0; - if (*pg1).iNrFaces != (*pg2).iNrFaces { - return false; - } - while i < (*pg1).iNrFaces as usize && bStillSame { - bStillSame = if (*pg1).pTriMembers[i] == (*pg2).pTriMembers[i] { - true - } else { - false - }; - if bStillSame { - i += 1 - } - } - return bStillSame; -} -unsafe fn QuickSort(mut pSortBuffer: *mut i32, mut iLeft: i32, mut iRight: i32, mut uSeed: u32) { - let mut iL: i32 = 0; - let mut iR: i32 = 0; - let mut n: i32 = 0; - let mut index: i32 = 0; - let mut iMid: i32 = 0; - let mut iTmp: i32 = 0; - - // Random - let mut t: u32 = uSeed & 31i32 as u32; - t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as u32).wrapping_sub(t)); - uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as u32); - // Random end - - iL = iLeft; - iR = iRight; - n = iR - iL + 1i32; - index = uSeed.wrapping_rem(n as u32) as i32; - iMid = *pSortBuffer.offset((index + iL) as isize); - loop { - while *pSortBuffer.offset(iL as isize) < iMid { - iL += 1 - } - while *pSortBuffer.offset(iR as isize) > iMid { - iR -= 1 - } - if iL <= iR { - iTmp = *pSortBuffer.offset(iL as isize); - *pSortBuffer.offset(iL as isize) = *pSortBuffer.offset(iR as isize); - *pSortBuffer.offset(iR as isize) = iTmp; - iL += 1; - iR -= 1 - } - if !(iL <= iR) { - break; - } - } - if iLeft < iR { - QuickSort(pSortBuffer, iLeft, iR, uSeed); - } - if iL < iRight { - QuickSort(pSortBuffer, iL, iRight, uSeed); - }; -} -unsafe fn Build4RuleGroups( - mut pTriInfos: *mut STriInfo, - mut pGroups: *mut SGroup, - mut piGroupTrianglesBuffer: *mut i32, - mut piTriListIn: *const i32, +fn Build4RuleGroups( + mut pTriInfos: &mut [STriInfo], + pGroups: &mut [SGroup], + piGroupTrianglesBuffer: &mut [i32], + piTriListIn: &[i32], iNrTrianglesIn: i32, ) -> i32 { - let iNrMaxGroups: i32 = iNrTrianglesIn * 3i32; let mut iNrActiveGroups: i32 = 0i32; let mut iOffset: i32 = 0i32; - let mut f: i32 = 0i32; - let mut i: i32 = 0i32; - f = 0i32; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 - && (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize].is_null() + let iNrMaxGroups = iNrTrianglesIn * 3; + + for f in 0..iNrTrianglesIn { + for i in 0..3i32 { + if !pTriInfos[f as usize] + .iFlag + .contains(TriangleFlags::GROUP_WITH_ANY) + && pTriInfos[f as usize].AssignedGroup[i as usize] == usize::MAX { - let mut bOrPre: bool = false; - let mut neigh_indexL: i32 = 0; - let mut neigh_indexR: i32 = 0; - let vert_index: i32 = *piTriListIn.offset((f * 3i32 + i) as isize); - let ref mut fresh2 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; - *fresh2 = &mut *pGroups.offset(iNrActiveGroups as isize) as *mut SGroup; - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]) - .iVertexRepresentitive = vert_index; - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).bOrientPreservering = - (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32; - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces = 0i32; - let ref mut fresh3 = - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).pFaceIndices; - *fresh3 = &mut *piGroupTrianglesBuffer.offset(iOffset as isize) as *mut i32; + let vert_index: i32 = piTriListIn[(f * 3 + i) as usize]; + debug_assert!(iNrActiveGroups < iNrMaxGroups); + pTriInfos[f as usize].AssignedGroup[i as usize] = iNrActiveGroups as usize; + let group = &mut pGroups[pTriInfos[f as usize].AssignedGroup[i as usize]]; + group.iVertexRepresentitive = vert_index; + group.bOrientPreservering = pTriInfos[f as usize] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); + group.iNrFaces = 0i32; + group.pFaceIndices = iOffset as usize; iNrActiveGroups += 1; - AddTriToGroup((*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], f); - bOrPre = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - neigh_indexL = (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize]; - neigh_indexR = (*pTriInfos.offset(f as isize)).FaceNeighbors - [(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; + AddTriToGroup(piGroupTrianglesBuffer, group, f); + let bOrPre = pTriInfos[f as usize] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); + let neigh_indexL = pTriInfos[f as usize].FaceNeighbors[i as usize]; + let neigh_indexR = pTriInfos[f as usize].FaceNeighbors[((i + 2) % 3) as usize]; if neigh_indexL >= 0i32 { + let index = pTriInfos[f as usize].AssignedGroup[i as usize]; let bAnswer: bool = AssignRecur( piTriListIn, pTriInfos, neigh_indexL, - (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], + &mut pGroups[index], + index, + piGroupTrianglesBuffer, ); - let bOrPre2: bool = - if (*pTriInfos.offset(neigh_indexL as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - let bDiff: bool = if bOrPre != bOrPre2 { true } else { false }; + let bOrPre2: bool = pTriInfos[neigh_indexL as usize] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); + let bDiff: bool = bOrPre != bOrPre2; + debug_assert!(bAnswer || bDiff); } if neigh_indexR >= 0i32 { + let index = pTriInfos[f as usize].AssignedGroup[i as usize]; let bAnswer_0: bool = AssignRecur( piTriListIn, pTriInfos, neigh_indexR, - (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], + &mut pGroups[index], + index, + piGroupTrianglesBuffer, ); - let bOrPre2_0: bool = - if (*pTriInfos.offset(neigh_indexR as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - let bDiff_0: bool = if bOrPre != bOrPre2_0 { true } else { false }; + let bOrPre2_0: bool = pTriInfos[neigh_indexR as usize] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); + let bDiff_0: bool = bOrPre != bOrPre2_0; + debug_assert!(bAnswer_0 || bDiff_0); } - iOffset += (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces + iOffset += pGroups[pTriInfos[f as usize].AssignedGroup[i as usize]].iNrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + debug_assert!(iOffset <= iNrMaxGroups); } - i += 1 } - f += 1 } - return iNrActiveGroups; + iNrActiveGroups } // /////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// -unsafe fn AssignRecur( - mut piTriListIn: *const i32, - mut psTriInfos: *mut STriInfo, +fn AssignRecur( + piTriListIn: &[i32], + psTriInfos: &mut [STriInfo], iMyTriIndex: i32, - mut pGroup: *mut SGroup, + pGroup: &mut SGroup, + group_idx: usize, + piGroupTrianglesBuffer: &mut [i32], ) -> bool { - let mut pMyTriInfo: *mut STriInfo = - &mut *psTriInfos.offset(iMyTriIndex as isize) as *mut STriInfo; + let mut pMyTriInfo = &mut psTriInfos[iMyTriIndex as usize]; // track down vertex - let iVertRep: i32 = (*pGroup).iVertexRepresentitive; - let mut pVerts: *const i32 = - &*piTriListIn.offset((3i32 * iMyTriIndex + 0i32) as isize) as *const i32; - let mut i: i32 = -1i32; - if *pVerts.offset(0isize) == iVertRep { - i = 0i32 - } else if *pVerts.offset(1isize) == iVertRep { - i = 1i32 - } else if *pVerts.offset(2isize) == iVertRep { - i = 2i32 - } - if (*pMyTriInfo).AssignedGroup[i as usize] == pGroup { + let iVertRep: i32 = pGroup.iVertexRepresentitive; + let pVerts = &piTriListIn[3 * iMyTriIndex as usize..][..3]; + let i = pVerts.iter().position(|&it| it == iVertRep).unwrap(); + if pMyTriInfo.AssignedGroup[i as usize] == group_idx { return true; - } else { - if !(*pMyTriInfo).AssignedGroup[i as usize].is_null() { - return false; - } } - if (*pMyTriInfo).iFlag & 4i32 != 0i32 { - if (*pMyTriInfo).AssignedGroup[0usize].is_null() - && (*pMyTriInfo).AssignedGroup[1usize].is_null() - && (*pMyTriInfo).AssignedGroup[2usize].is_null() - { - (*pMyTriInfo).iFlag &= !8i32; - (*pMyTriInfo).iFlag |= if (*pGroup).bOrientPreservering { - 8i32 - } else { - 0i32 - } - } + if !pMyTriInfo.AssignedGroup[i as usize] == usize::MAX { + return false; } - let bOrient: bool = if (*pMyTriInfo).iFlag & 8i32 != 0i32 { - true - } else { - false - }; + + if (*pMyTriInfo).iFlag.contains(TriangleFlags::GROUP_WITH_ANY) + && (*pMyTriInfo).AssignedGroup[0usize] == usize::MAX + && (*pMyTriInfo).AssignedGroup[1usize] == usize::MAX + && (*pMyTriInfo).AssignedGroup[2usize] == usize::MAX + { + (*pMyTriInfo).iFlag.set( + TriangleFlags::ORIENT_PRESERVING, + (*pGroup).bOrientPreservering, + ); + } + let bOrient: bool = (*pMyTriInfo) + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); if bOrient != (*pGroup).bOrientPreservering { return false; } - AddTriToGroup(pGroup, iMyTriIndex); - (*pMyTriInfo).AssignedGroup[i as usize] = pGroup; - let neigh_indexL: i32 = (*pMyTriInfo).FaceNeighbors[i as usize]; - let neigh_indexR: i32 = - (*pMyTriInfo).FaceNeighbors[(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; + AddTriToGroup(piGroupTrianglesBuffer, pGroup, iMyTriIndex); + pMyTriInfo.AssignedGroup[i as usize] = group_idx; + let neigh_indexL = (*pMyTriInfo).FaceNeighbors[i as usize]; + let neigh_indexR = (*pMyTriInfo).FaceNeighbors[(if i > 0 { i - 1 } else { 2 }) as usize]; if neigh_indexL >= 0i32 { - AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + AssignRecur( + piTriListIn, + psTriInfos, + neigh_indexL, + pGroup, + group_idx, + piGroupTrianglesBuffer, + ); } if neigh_indexR >= 0i32 { - AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); - } - return true; -} -unsafe fn AddTriToGroup(mut pGroup: *mut SGroup, iTriIndex: i32) { - *(*pGroup).pFaceIndices.offset((*pGroup).iNrFaces as isize) = iTriIndex; - (*pGroup).iNrFaces += 1; -} -unsafe fn InitTriInfo( - mut pTriInfos: *mut STriInfo, - mut piTriListIn: *const i32, - geometry: &mut I, - iNrTrianglesIn: usize, -) { - let mut f = 0; - let mut i = 0; - let mut t = 0; - f = 0; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] = -1i32; - let ref mut fresh4 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; - *fresh4 = 0 as *mut SGroup; - (*pTriInfos.offset(f as isize)).vOs.x = 0.0f32; - (*pTriInfos.offset(f as isize)).vOs.y = 0.0f32; - (*pTriInfos.offset(f as isize)).vOs.z = 0.0f32; - (*pTriInfos.offset(f as isize)).vOt.x = 0.0f32; - (*pTriInfos.offset(f as isize)).vOt.y = 0.0f32; - (*pTriInfos.offset(f as isize)).vOt.z = 0.0f32; - (*pTriInfos.offset(f as isize)).fMagS = 0i32 as f32; - (*pTriInfos.offset(f as isize)).fMagT = 0i32 as f32; - (*pTriInfos.offset(f as isize)).iFlag |= 4i32; - i += 1 - } - f += 1 - } - f = 0; - while f < iNrTrianglesIn { - let v1 = get_position(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); - let v2 = get_position(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); - let v3 = get_position(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); - let t1 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); - let t2 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); - let t3 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); - let t21x: f32 = t2.x - t1.x; - let t21y: f32 = t2.y - t1.y; - let t31x: f32 = t3.x - t1.x; - let t31y: f32 = t3.y - t1.y; - let d1 = v2 - v1; - let d2 = v3 - v1; - let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; - let mut vOs = (t31y * d1) - (t21y * d2); - let mut vOt = (-t31x * d1) + (t21x * d2); - (*pTriInfos.offset(f as isize)).iFlag |= if fSignedAreaSTx2 > 0i32 as f32 { - 8i32 - } else { - 0i32 - }; - if NotZero(fSignedAreaSTx2) { - let fAbsArea: f32 = fSignedAreaSTx2.abs(); - let fLenOs: f32 = vOs.length(); - let fLenOt: f32 = vOt.length(); - let fS: f32 = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { - -1.0f32 - } else { - 1.0f32 - }; - if NotZero(fLenOs) { - (*pTriInfos.offset(f as isize)).vOs = (fS / fLenOs) * vOs - } - if NotZero(fLenOt) { - (*pTriInfos.offset(f as isize)).vOt = (fS / fLenOt) * vOt - } - (*pTriInfos.offset(f as isize)).fMagS = fLenOs / fAbsArea; - (*pTriInfos.offset(f as isize)).fMagT = fLenOt / fAbsArea; - if NotZero((*pTriInfos.offset(f as isize)).fMagS) - && NotZero((*pTriInfos.offset(f as isize)).fMagT) - { - (*pTriInfos.offset(f as isize)).iFlag &= !4i32 - } - } - f += 1 - } - while t < iNrTrianglesIn - 1 { - let iFO_a: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let iFO_b: i32 = (*pTriInfos.offset((t + 1) as isize)).iOrgFaceNumber; - if iFO_a == iFO_b { - let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - let bIsDeg_b: bool = if (*pTriInfos.offset((t + 1) as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - if !(bIsDeg_a || bIsDeg_b) { - let bOrientA: bool = if (*pTriInfos.offset(t as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - let bOrientB: bool = if (*pTriInfos.offset((t + 1) as isize)).iFlag & 8i32 != 0i32 { - true - } else { - false - }; - if bOrientA != bOrientB { - let mut bChooseOrientFirstTri: bool = false; - if (*pTriInfos.offset((t + 1) as isize)).iFlag & 4i32 != 0i32 { - bChooseOrientFirstTri = true - } else if CalcTexArea(geometry, &*piTriListIn.offset((t * 3 + 0) as isize)) - >= CalcTexArea(geometry, &*piTriListIn.offset(((t + 1) * 3 + 0) as isize)) - { - bChooseOrientFirstTri = true - } - let t0 = if bChooseOrientFirstTri { t } else { t + 1 }; - let t1_0 = if bChooseOrientFirstTri { t + 1 } else { t }; - (*pTriInfos.offset(t1_0 as isize)).iFlag &= !8i32; - (*pTriInfos.offset(t1_0 as isize)).iFlag |= - (*pTriInfos.offset(t0 as isize)).iFlag & 8i32 - } - } - t += 2 - } else { - t += 1 - } - } - - let mut pEdges = vec![SEdge::zero(); iNrTrianglesIn * 3]; - BuildNeighborsFast( - pTriInfos, - pEdges.as_mut_ptr(), - piTriListIn, - iNrTrianglesIn as i32, - ); -} - -unsafe fn BuildNeighborsFast( - mut pTriInfos: *mut STriInfo, - mut pEdges: *mut SEdge, - mut piTriListIn: *const i32, - iNrTrianglesIn: i32, -) { - // build array of edges - // could replace with a random seed? - let mut uSeed: u32 = 39871946i32 as u32; - let mut iEntries: i32 = 0i32; - let mut iCurStartIndex: i32 = -1i32; - let mut f: i32 = 0i32; - let mut i: i32 = 0i32; - f = 0i32; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - let i0: i32 = *piTriListIn.offset((f * 3i32 + i) as isize); - let i1: i32 = - *piTriListIn.offset((f * 3i32 + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); - (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i0 = if i0 < i1 { i0 } else { i1 }; - (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i1 = if !(i0 < i1) { i0 } else { i1 }; - (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.f = f; - i += 1 - } - f += 1 - } - QuickSortEdges(pEdges, 0i32, iNrTrianglesIn * 3i32 - 1i32, 0i32, uSeed); - iEntries = iNrTrianglesIn * 3i32; - iCurStartIndex = 0i32; - i = 1i32; - while i < iEntries { - if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 - != (*pEdges.offset(i as isize)).unnamed.i0 - { - let iL: i32 = iCurStartIndex; - let iR: i32 = i - 1i32; - iCurStartIndex = i; - QuickSortEdges(pEdges, iL, iR, 1i32, uSeed); - } - i += 1 - } - iCurStartIndex = 0i32; - i = 1i32; - while i < iEntries { - if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 - != (*pEdges.offset(i as isize)).unnamed.i0 - || (*pEdges.offset(iCurStartIndex as isize)).unnamed.i1 - != (*pEdges.offset(i as isize)).unnamed.i1 - { - let iL_0: i32 = iCurStartIndex; - let iR_0: i32 = i - 1i32; - iCurStartIndex = i; - QuickSortEdges(pEdges, iL_0, iR_0, 2i32, uSeed); - } - i += 1 - } - i = 0i32; - while i < iEntries { - let i0_0: i32 = (*pEdges.offset(i as isize)).unnamed.i0; - let i1_0: i32 = (*pEdges.offset(i as isize)).unnamed.i1; - let f_0: i32 = (*pEdges.offset(i as isize)).unnamed.f; - let mut bUnassigned_A: bool = false; - let mut i0_A: i32 = 0; - let mut i1_A: i32 = 0; - let mut edgenum_A: i32 = 0; - let mut edgenum_B: i32 = 0i32; - GetEdge( - &mut i0_A, - &mut i1_A, - &mut edgenum_A, - &*piTriListIn.offset((f_0 * 3i32) as isize), - i0_0, - i1_0, + AssignRecur( + piTriListIn, + psTriInfos, + neigh_indexR, + pGroup, + group_idx, + piGroupTrianglesBuffer, ); - bUnassigned_A = - if (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] == -1i32 { - true - } else { - false - }; - if bUnassigned_A { - let mut j: i32 = i + 1i32; - let mut t: i32 = 0; - let mut bNotFound: bool = true; - while j < iEntries - && i0_0 == (*pEdges.offset(j as isize)).unnamed.i0 - && i1_0 == (*pEdges.offset(j as isize)).unnamed.i1 - && bNotFound - { - let mut bUnassigned_B: bool = false; - let mut i0_B: i32 = 0; - let mut i1_B: i32 = 0; - t = (*pEdges.offset(j as isize)).unnamed.f; - GetEdge( - &mut i1_B, - &mut i0_B, - &mut edgenum_B, - &*piTriListIn.offset((t * 3i32) as isize), - (*pEdges.offset(j as isize)).unnamed.i0, - (*pEdges.offset(j as isize)).unnamed.i1, - ); - bUnassigned_B = - if (*pTriInfos.offset(t as isize)).FaceNeighbors[edgenum_B as usize] == -1i32 { - true - } else { - false - }; - if i0_A == i0_B && i1_A == i1_B && bUnassigned_B { - bNotFound = false - } else { - j += 1 - } - } - if !bNotFound { - let mut t_0: i32 = (*pEdges.offset(j as isize)).unnamed.f; - (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] = t_0; - (*pTriInfos.offset(t_0 as isize)).FaceNeighbors[edgenum_B as usize] = f_0 - } - } - i += 1 - } -} -unsafe fn GetEdge( - mut i0_out: *mut i32, - mut i1_out: *mut i32, - mut edgenum_out: *mut i32, - mut indices: *const i32, - i0_in: i32, - i1_in: i32, -) { - *edgenum_out = -1i32; - if *indices.offset(0isize) == i0_in || *indices.offset(0isize) == i1_in { - if *indices.offset(1isize) == i0_in || *indices.offset(1isize) == i1_in { - *edgenum_out.offset(0isize) = 0i32; - *i0_out.offset(0isize) = *indices.offset(0isize); - *i1_out.offset(0isize) = *indices.offset(1isize) - } else { - *edgenum_out.offset(0isize) = 2i32; - *i0_out.offset(0isize) = *indices.offset(2isize); - *i1_out.offset(0isize) = *indices.offset(0isize) - } - } else { - *edgenum_out.offset(0isize) = 1i32; - *i0_out.offset(0isize) = *indices.offset(1isize); - *i1_out.offset(0isize) = *indices.offset(2isize) - }; -} -// /////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////// -unsafe fn QuickSortEdges( - mut pSortBuffer: *mut SEdge, - mut iLeft: i32, - mut iRight: i32, - channel: i32, - mut uSeed: u32, -) { - let mut t: u32 = 0; - let mut iL: i32 = 0; - let mut iR: i32 = 0; - let mut n: i32 = 0; - let mut index: i32 = 0; - let mut iMid: i32 = 0; - // early out - let mut sTmp: SEdge = SEdge { - unnamed: unnamed { i0: 0, i1: 0, f: 0 }, - }; - let iElems: i32 = iRight - iLeft + 1i32; - if iElems < 2i32 { - return; - } else { - if iElems == 2i32 { - if (*pSortBuffer.offset(iLeft as isize)).array[channel as usize] - > (*pSortBuffer.offset(iRight as isize)).array[channel as usize] - { - sTmp = *pSortBuffer.offset(iLeft as isize); - *pSortBuffer.offset(iLeft as isize) = *pSortBuffer.offset(iRight as isize); - *pSortBuffer.offset(iRight as isize) = sTmp - } - return; - } - } - - // Random - t = uSeed & 31i32 as u32; - t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as u32).wrapping_sub(t)); - uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as u32); - // Random end - - iL = iLeft; - iR = iRight; - n = iR - iL + 1i32; - index = uSeed.wrapping_rem(n as u32) as i32; - iMid = (*pSortBuffer.offset((index + iL) as isize)).array[channel as usize]; - loop { - while (*pSortBuffer.offset(iL as isize)).array[channel as usize] < iMid { - iL += 1 - } - while (*pSortBuffer.offset(iR as isize)).array[channel as usize] > iMid { - iR -= 1 - } - if iL <= iR { - sTmp = *pSortBuffer.offset(iL as isize); - *pSortBuffer.offset(iL as isize) = *pSortBuffer.offset(iR as isize); - *pSortBuffer.offset(iR as isize) = sTmp; - iL += 1; - iR -= 1 - } - if !(iL <= iR) { - break; - } - } - if iLeft < iR { - QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); - } - if iL < iRight { - QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); - }; -} - -// returns the texture area times 2 -unsafe fn CalcTexArea(geometry: &mut I, mut indices: *const i32) -> f32 { - let t1 = get_tex_coord(geometry, *indices.offset(0isize) as usize); - let t2 = get_tex_coord(geometry, *indices.offset(1isize) as usize); - let t3 = get_tex_coord(geometry, *indices.offset(2isize) as usize); - let t21x: f32 = t2.x - t1.x; - let t21y: f32 = t2.y - t1.y; - let t31x: f32 = t3.x - t1.x; - let t31y: f32 = t3.y - t1.y; - let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; - return if fSignedAreaSTx2 < 0i32 as f32 { - -fSignedAreaSTx2 - } else { - fSignedAreaSTx2 - }; -} - -// degen triangles -unsafe fn DegenPrologue( - mut pTriInfos: *mut STriInfo, - mut piTriList_out: *mut i32, - iNrTrianglesIn: i32, - iTotTris: i32, -) { - let mut iNextGoodTriangleSearchIndex: i32 = -1i32; - let mut bStillFindingGoodOnes: bool = false; - // locate quads with only one good triangle - let mut t: i32 = 0i32; - while t < iTotTris - 1i32 { - let iFO_a: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let iFO_b: i32 = (*pTriInfos.offset((t + 1i32) as isize)).iOrgFaceNumber; - if iFO_a == iFO_b { - let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - let bIsDeg_b: bool = if (*pTriInfos.offset((t + 1i32) as isize)).iFlag & 1i32 != 0i32 { - true - } else { - false - }; - if bIsDeg_a ^ bIsDeg_b != false { - (*pTriInfos.offset(t as isize)).iFlag |= 2i32; - (*pTriInfos.offset((t + 1i32) as isize)).iFlag |= 2i32 - } - t += 2i32 - } else { - t += 1 - } - } - iNextGoodTriangleSearchIndex = 1i32; - t = 0i32; - bStillFindingGoodOnes = true; - while t < iNrTrianglesIn && bStillFindingGoodOnes { - let bIsGood: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 == 0i32 { - true - } else { - false - }; - if bIsGood { - if iNextGoodTriangleSearchIndex < t + 2i32 { - iNextGoodTriangleSearchIndex = t + 2i32 - } - } else { - let mut t0: i32 = 0; - let mut t1: i32 = 0; - let mut bJustADegenerate: bool = true; - while bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris { - let bIsGood_0: bool = - if (*pTriInfos.offset(iNextGoodTriangleSearchIndex as isize)).iFlag & 1i32 - == 0i32 - { - true - } else { - false - }; - if bIsGood_0 { - bJustADegenerate = false - } else { - iNextGoodTriangleSearchIndex += 1 - } - } - t0 = t; - t1 = iNextGoodTriangleSearchIndex; - iNextGoodTriangleSearchIndex += 1; - if !bJustADegenerate { - let mut i: i32 = 0i32; - i = 0i32; - while i < 3i32 { - let index: i32 = *piTriList_out.offset((t0 * 3i32 + i) as isize); - *piTriList_out.offset((t0 * 3i32 + i) as isize) = - *piTriList_out.offset((t1 * 3i32 + i) as isize); - *piTriList_out.offset((t1 * 3i32 + i) as isize) = index; - i += 1 - } - let tri_info: STriInfo = *pTriInfos.offset(t0 as isize); - *pTriInfos.offset(t0 as isize) = *pTriInfos.offset(t1 as isize); - *pTriInfos.offset(t1 as isize) = tri_info - } else { - bStillFindingGoodOnes = false - } - } - if bStillFindingGoodOnes { - t += 1 - } } + true } -unsafe fn GenerateSharedVerticesIndexList( - mut piTriList_in_and_out: *mut i32, - geometry: &mut I, - iNrTrianglesIn: usize, -) { - let mut i = 0; - let mut iChannel: i32 = 0i32; - let mut k = 0; - let mut e = 0; - let mut iMaxCount = 0; - let mut vMin = get_position(geometry, 0); - let mut vMax = vMin; - let mut vDim = Vec3::new(0.0, 0.0, 0.0); - let mut fMin: f32 = 0.; - let mut fMax: f32 = 0.; - i = 1; - while i < iNrTrianglesIn * 3 { - let index: i32 = *piTriList_in_and_out.offset(i as isize); - let vP = get_position(geometry, index as usize); - if vMin.x > vP.x { - vMin.x = vP.x - } else if vMax.x < vP.x { - vMax.x = vP.x - } - if vMin.y > vP.y { - vMin.y = vP.y - } else if vMax.y < vP.y { - vMax.y = vP.y - } - if vMin.z > vP.z { - vMin.z = vP.z - } else if vMax.z < vP.z { - vMax.z = vP.z - } - i += 1 - } - vDim = vMax - vMin; - iChannel = 0i32; - fMin = vMin.x; - fMax = vMax.x; - if vDim.y > vDim.x && vDim.y > vDim.z { - iChannel = 1i32; - fMin = vMin.y; - fMax = vMax.y - } else if vDim.z > vDim.x { - iChannel = 2i32; - fMin = vMin.z; - fMax = vMax.z - } - - let mut piHashTable = vec![0i32; iNrTrianglesIn * 3]; - let mut piHashOffsets = vec![0i32; g_iCells]; - let mut piHashCount = vec![0i32; g_iCells]; - let mut piHashCount2 = vec![0i32; g_iCells]; - - i = 0; - while i < iNrTrianglesIn * 3 { - let index_0: i32 = *piTriList_in_and_out.offset(i as isize); - let vP_0 = get_position(geometry, index_0 as usize); - let fVal: f32 = if iChannel == 0i32 { - vP_0.x - } else if iChannel == 1i32 { - vP_0.y - } else { - vP_0.z - }; - let iCell = FindGridCell(fMin, fMax, fVal); - piHashCount[iCell] += 1; - i += 1 - } - piHashOffsets[0] = 0i32; - k = 1; - while k < g_iCells { - piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1]; - k += 1 - } - i = 0; - while i < iNrTrianglesIn * 3 { - let index_1: i32 = *piTriList_in_and_out.offset(i as isize); - let vP_1 = get_position(geometry, index_1 as usize); - let fVal_0: f32 = if iChannel == 0i32 { - vP_1.x - } else if iChannel == 1i32 { - vP_1.y - } else { - vP_1.z - }; - let iCell_0 = FindGridCell(fMin, fMax, fVal_0); - let mut pTable: *mut i32 = 0 as *mut i32; - pTable = &mut piHashTable[piHashOffsets[iCell_0] as usize] as *mut i32; - *pTable.offset(piHashCount2[iCell_0] as isize) = i as i32; - piHashCount2[iCell_0] += 1; - i += 1 - } - k = 0; - while k < g_iCells { - k += 1 - } - iMaxCount = piHashCount[0] as usize; - k = 1; - while k < g_iCells { - if iMaxCount < piHashCount[k] as usize { - iMaxCount = piHashCount[k] as usize - } - k += 1 - } - let mut pTmpVert = vec![STmpVert::zero(); iMaxCount]; - k = 0; - while k < g_iCells { - // extract table of cell k and amount of entries in it - let mut pTable_0 = &mut piHashTable[piHashOffsets[k] as usize] as *mut i32; - let iEntries = piHashCount[k] as usize; - if !(iEntries < 2) { - e = 0; - while e < iEntries { - let mut i_0: i32 = *pTable_0.offset(e as isize); - let vP_2 = get_position( - geometry, - *piTriList_in_and_out.offset(i_0 as isize) as usize, - ); - pTmpVert[e].vert[0usize] = vP_2.x; - pTmpVert[e].vert[1usize] = vP_2.y; - pTmpVert[e].vert[2usize] = vP_2.z; - pTmpVert[e].index = i_0; - e += 1 - } - MergeVertsFast( - piTriList_in_and_out, - pTmpVert.as_mut_ptr(), - geometry, - 0i32, - (iEntries - 1) as i32, - ); - } - k += 1 - } -} - -unsafe fn MergeVertsFast( - mut piTriList_in_and_out: *mut i32, - mut pTmpVert: *mut STmpVert, - geometry: &mut I, - iL_in: i32, - iR_in: i32, -) { - // make bbox - let mut c: i32 = 0i32; - let mut l: i32 = 0i32; - let mut channel: i32 = 0i32; - let mut fvMin: [f32; 3] = [0.; 3]; - let mut fvMax: [f32; 3] = [0.; 3]; - let mut dx: f32 = 0i32 as f32; - let mut dy: f32 = 0i32 as f32; - let mut dz: f32 = 0i32 as f32; - let mut fSep: f32 = 0i32 as f32; - c = 0i32; - while c < 3i32 { - fvMin[c as usize] = (*pTmpVert.offset(iL_in as isize)).vert[c as usize]; - fvMax[c as usize] = fvMin[c as usize]; - c += 1 - } - l = iL_in + 1i32; - while l <= iR_in { - c = 0i32; - while c < 3i32 { - if fvMin[c as usize] > (*pTmpVert.offset(l as isize)).vert[c as usize] { - fvMin[c as usize] = (*pTmpVert.offset(l as isize)).vert[c as usize] - } else if fvMax[c as usize] < (*pTmpVert.offset(l as isize)).vert[c as usize] { - fvMax[c as usize] = (*pTmpVert.offset(l as isize)).vert[c as usize] - } - c += 1 - } - l += 1 - } - dx = fvMax[0usize] - fvMin[0usize]; - dy = fvMax[1usize] - fvMin[1usize]; - dz = fvMax[2usize] - fvMin[2usize]; - channel = 0i32; - if dy > dx && dy > dz { - channel = 1i32 - } else if dz > dx { - channel = 2i32 - } - fSep = 0.5f32 * (fvMax[channel as usize] + fvMin[channel as usize]); - if fSep >= fvMax[channel as usize] || fSep <= fvMin[channel as usize] { - l = iL_in; - while l <= iR_in { - let mut i: i32 = (*pTmpVert.offset(l as isize)).index; - let index: i32 = *piTriList_in_and_out.offset(i as isize); - let vP = get_position(geometry, index as usize); - let vN = get_normal(geometry, index as usize); - let vT = get_tex_coord(geometry, index as usize); - let mut bNotFound: bool = true; - let mut l2: i32 = iL_in; - let mut i2rec: i32 = -1i32; - while l2 < l && bNotFound { - let i2: i32 = (*pTmpVert.offset(l2 as isize)).index; - let index2: i32 = *piTriList_in_and_out.offset(i2 as isize); - let vP2 = get_position(geometry, index2 as usize); - let vN2 = get_normal(geometry, index2 as usize); - let vT2 = get_tex_coord(geometry, index2 as usize); - i2rec = i2; - if vP.x == vP2.x - && vP.y == vP2.y - && vP.z == vP2.z - && vN.x == vN2.x - && vN.y == vN2.y - && vN.z == vN2.z - && vT.x == vT2.x - && vT.y == vT2.y - && vT.z == vT2.z - { - bNotFound = false - } else { - l2 += 1 - } - } - if !bNotFound { - *piTriList_in_and_out.offset(i as isize) = - *piTriList_in_and_out.offset(i2rec as isize) - } - l += 1 - } - } else { - let mut iL: i32 = iL_in; - let mut iR: i32 = iR_in; - while iL < iR { - let mut bReadyLeftSwap: bool = false; - let mut bReadyRightSwap: bool = false; - while !bReadyLeftSwap && iL < iR { - bReadyLeftSwap = !((*pTmpVert.offset(iL as isize)).vert[channel as usize] < fSep); - if !bReadyLeftSwap { - iL += 1 - } - } - while !bReadyRightSwap && iL < iR { - bReadyRightSwap = (*pTmpVert.offset(iR as isize)).vert[channel as usize] < fSep; - if !bReadyRightSwap { - iR -= 1 - } - } - if bReadyLeftSwap && bReadyRightSwap { - let sTmp: STmpVert = *pTmpVert.offset(iL as isize); - *pTmpVert.offset(iL as isize) = *pTmpVert.offset(iR as isize); - *pTmpVert.offset(iR as isize) = sTmp; - iL += 1; - iR -= 1 - } - } - if iL == iR { - let bReadyRightSwap_0: bool = - (*pTmpVert.offset(iR as isize)).vert[channel as usize] < fSep; - if bReadyRightSwap_0 { - iL += 1 - } else { - iR -= 1 - } - } - if iL_in < iR { - MergeVertsFast(piTriList_in_and_out, pTmpVert, geometry, iL_in, iR); - } - if iL < iR_in { - MergeVertsFast(piTriList_in_and_out, pTmpVert, geometry, iL, iR_in); - } - }; -} - -const g_iCells: usize = 2048; - -// it is IMPORTANT that this function is called to evaluate the hash since -// inlining could potentially reorder instructions and generate different -// results for the same effective input value fVal. -#[inline(never)] -unsafe fn FindGridCell(fMin: f32, fMax: f32, fVal: f32) -> usize { - let fIndex = g_iCells as f32 * ((fVal - fMin) / (fMax - fMin)); - let iIndex = fIndex as isize; - return if iIndex < g_iCells as isize { - if iIndex >= 0 { - iIndex as usize - } else { - 0 - } - } else { - g_iCells - 1 - }; -} - -unsafe fn GenerateInitialVerticesIndexList( - pTriInfos: &mut [STriInfo], - piTriList_out: &mut [i32], - geometry: &mut I, - iNrTrianglesIn: usize, -) -> usize { - let mut iTSpacesOffs: usize = 0; - let mut f = 0; - let mut t: usize = 0; - let mut iDstTriIndex = 0; - f = 0; - while f < geometry.num_faces() { - let verts = geometry.num_vertices_of_face(f); - if !(verts != 3 && verts != 4) { - pTriInfos[iDstTriIndex].iOrgFaceNumber = f as i32; - pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as i32; - if verts == 3 { - let mut pVerts = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts[0] = 0; - pVerts[1] = 1; - pVerts[2] = 2; - piTriList_out[iDstTriIndex * 3 + 0] = face_vert_to_index(f, 0) as i32; - piTriList_out[iDstTriIndex * 3 + 1] = face_vert_to_index(f, 1) as i32; - piTriList_out[iDstTriIndex * 3 + 2] = face_vert_to_index(f, 2) as i32; - iDstTriIndex += 1 - } else { - pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f as i32; - pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs as i32; - let i0 = face_vert_to_index(f, 0); - let i1 = face_vert_to_index(f, 1); - let i2 = face_vert_to_index(f, 2); - let i3 = face_vert_to_index(f, 3); - let T0 = get_tex_coord(geometry, i0); - let T1 = get_tex_coord(geometry, i1); - let T2 = get_tex_coord(geometry, i2); - let T3 = get_tex_coord(geometry, i3); - let distSQ_02: f32 = (T2 - T0).length_squared(); - let distSQ_13: f32 = (T3 - T1).length_squared(); - let mut bQuadDiagIs_02: bool = false; - if distSQ_02 < distSQ_13 { - bQuadDiagIs_02 = true - } else if distSQ_13 < distSQ_02 { - bQuadDiagIs_02 = false - } else { - let P0 = get_position(geometry, i0); - let P1 = get_position(geometry, i1); - let P2 = get_position(geometry, i2); - let P3 = get_position(geometry, i3); - let distSQ_02_0: f32 = (P2 - P0).length_squared(); - let distSQ_13_0: f32 = (P3 - P1).length_squared(); - bQuadDiagIs_02 = if distSQ_13_0 < distSQ_02_0 { - false - } else { - true - } - } - if bQuadDiagIs_02 { - let mut pVerts_A = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_A[0] = 0; - pVerts_A[1] = 1; - pVerts_A[2] = 2; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i2 as i32; - iDstTriIndex += 1; - - let mut pVerts_B = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_B[0] = 0; - pVerts_B[1] = 2; - pVerts_B[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; - iDstTriIndex += 1 - } else { - let mut pVerts_A_0 = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_A_0[0] = 0; - pVerts_A_0[1] = 1; - pVerts_A_0[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; - iDstTriIndex += 1; - - let mut pVerts_B_0 = &mut pTriInfos[iDstTriIndex].vert_num; - pVerts_B_0[0] = 1; - pVerts_B_0[1] = 2; - pVerts_B_0[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i1 as i32; - piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; - iDstTriIndex += 1 - } - } - iTSpacesOffs += verts - } - f += 1 - } - t = 0; - while t < iNrTrianglesIn { - pTriInfos[t].iFlag = 0; - t += 1 - } - return iTSpacesOffs; +fn AddTriToGroup(faces: &mut [i32], pGroup: &mut SGroup, iTriIndex: i32) { + faces[pGroup.iNrFaces as usize + pGroup.pFaceIndices] = iTriIndex; + pGroup.iNrFaces += 1; } diff --git a/crates/bevy_mikktspace/src/generated/degenerate.rs b/crates/bevy_mikktspace/src/generated/degenerate.rs new file mode 100644 index 0000000000000..1ee38ee24dc67 --- /dev/null +++ b/crates/bevy_mikktspace/src/generated/degenerate.rs @@ -0,0 +1,164 @@ +use crate::face_vert_to_index; +use crate::get_position; +use crate::Geometry; + +use super::STSpace; +use super::STriInfo; +use super::TriangleFlags; + +pub(crate) fn DegenPrologue( + pTriInfos: &mut [STriInfo], + piTriList_out: &mut [i32], + iNrTrianglesIn: i32, + iTotTris: i32, +) { + // locate quads with only one good triangle + let mut t = 0; + while t < (iTotTris as usize) - 1 { + let [a, b] = if let [a, b] = &mut pTriInfos[t..=t + 1] { + [a, b] + } else { + unreachable!() + }; + if a.iOrgFaceNumber == b.iOrgFaceNumber { + let bIsDeg_a: bool = a.iFlag.contains(TriangleFlags::DEGENERATE); + let bIsDeg_b: bool = b.iFlag.contains(TriangleFlags::DEGENERATE); + // If exactly one is degenerate, mark both as QUAD_ONE_DEGENERATE_TRI, i.e. that the other triangle + // (If both are degenerate, this doesn't matter ?) + if bIsDeg_a ^ bIsDeg_b { + a.iFlag.insert(TriangleFlags::QUAD_ONE_DEGENERATE_TRI); + b.iFlag.insert(TriangleFlags::QUAD_ONE_DEGENERATE_TRI); + } + t += 2; + } else { + t += 1; + } + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + // That is, a semi-stable partition, e.g. as described at + // https://dlang.org/library/std/algorithm/sorting/partition.html + // TODO: Use `Vec::retain` with a second vec here - not perfect, + // but good enough and safe. + // TODO: Consider using `sort_by_key` on Vec instead (which is stable) - it might be + // technically slower, but it's much easier to reason about + let mut iNextGoodTriangleSearchIndex = 1; + let mut t = 0; + let mut bStillFindingGoodOnes = true; + while t < iNrTrianglesIn as usize && bStillFindingGoodOnes { + let bIsGood: bool = !pTriInfos[t].iFlag.contains(TriangleFlags::DEGENERATE); + if bIsGood { + if iNextGoodTriangleSearchIndex < t + 2 { + iNextGoodTriangleSearchIndex = t + 2; + } + } else { + let mut bJustADegenerate: bool = true; + while bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris as usize { + let bIsGood_0: bool = !pTriInfos[iNextGoodTriangleSearchIndex] + .iFlag + .contains(TriangleFlags::DEGENERATE); + if bIsGood_0 { + bJustADegenerate = false; + } else { + iNextGoodTriangleSearchIndex += 1; + } + } + let t0 = t; + let t1 = iNextGoodTriangleSearchIndex; + iNextGoodTriangleSearchIndex += 1; + debug_assert!(iNextGoodTriangleSearchIndex > (t + 1)); + // Swap t0 and t1 + if !bJustADegenerate { + let (start, end) = piTriList_out.split_at_mut(t1 * 3); + + start[t0 * 3..t0 * 3 + 3].swap_with_slice(&mut end[0..3]); + pTriInfos.swap(t0, t1); + } else { + bStillFindingGoodOnes = false; + } + } + if bStillFindingGoodOnes { + t += 1; + } + } + debug_assert!(iNrTrianglesIn as usize == t); + debug_assert!(bStillFindingGoodOnes); +} + +pub(crate) fn DegenEpilogue( + psTspace: &mut [STSpace], + pTriInfos: &mut [STriInfo], + piTriListIn: &mut [i32], + geometry: &impl Geometry, + iNrTrianglesIn: i32, + iTotTris: i32, +) { + // For all degenerate triangles + for t in iNrTrianglesIn..iTotTris { + let bSkip = pTriInfos[t as usize] + .iFlag + .contains(TriangleFlags::QUAD_ONE_DEGENERATE_TRI); + if !bSkip { + for i in 0..3i32 { + // For all vertices on that triangle + let index1 = piTriListIn[(t * 3 + i) as usize]; + for j in 0..(3 * iNrTrianglesIn) { + let index2 = piTriListIn[j as usize]; + // If the vertex properties are the same as another non-degenerate vertex + if index1 == index2 { + // assert_eq!(j, index1); + let iTri = j / 3i32; + let iVert = j % 3i32; + let iSrcVert = pTriInfos[iTri as usize].vert_num[iVert as usize] as i32; + let iSrcOffs = pTriInfos[iTri as usize].iTSpacesOffs; + let iDstVert = pTriInfos[t as usize].vert_num[i as usize] as i32; + let iDstOffs: i32 = pTriInfos[t as usize].iTSpacesOffs; + // Set the tangent space of this vertex to the tangent space of that vertex + // TODO: This is absurd - doing a linear search through all vertices for each + // degenerate triangle? + psTspace[(iDstOffs + iDstVert) as usize] = + psTspace[(iSrcOffs + iSrcVert) as usize]; + break; + } + } + } + } + } + for t in 0..iNrTrianglesIn { + // Handle quads with a single degenerate triangle by + if pTriInfos[t as usize] + .iFlag + .contains(TriangleFlags::QUAD_ONE_DEGENERATE_TRI) + { + let pV = &mut pTriInfos[t as usize].vert_num; + let iFlag: i32 = 1i32 << pV[0] as i32 | 1i32 << pV[1] as i32 | 1i32 << pV[2] as i32; + let mut iMissingIndex: i32 = 0i32; + if iFlag & 2i32 == 0i32 { + iMissingIndex = 1i32; + } else if iFlag & 4i32 == 0i32 { + iMissingIndex = 2i32; + } else if iFlag & 8i32 == 0i32 { + iMissingIndex = 3i32; + } + let iOrgF = pTriInfos[t as usize].iOrgFaceNumber; + let vDstP = get_position( + geometry, + face_vert_to_index(iOrgF as usize, iMissingIndex as usize), + ); + + for &iVert_0 in pV.iter().take(3) { + let vSrcP = get_position( + geometry, + face_vert_to_index(iOrgF as usize, iVert_0 as usize), + ); + if vSrcP == vDstP { + let iOffs: i32 = pTriInfos[t as usize].iTSpacesOffs; + psTspace[(iOffs + iMissingIndex) as usize] = + psTspace[(iOffs + iVert_0 as i32) as usize]; + break; + } + } + } + } +} diff --git a/crates/bevy_mikktspace/src/generated/setup.rs b/crates/bevy_mikktspace/src/generated/setup.rs new file mode 100644 index 0000000000000..1d826bd833807 --- /dev/null +++ b/crates/bevy_mikktspace/src/generated/setup.rs @@ -0,0 +1,319 @@ +use super::SEdge; +use super::STriInfo; +use super::TriangleFlags; + +use crate::face_vert_to_index; +use crate::ordered_vec::FiniteVec3; +use crate::FaceKind; + +use std::collections::BTreeMap; + +use crate::get_normal; +use crate::get_position; +use crate::get_tex_coord; +use crate::Geometry; + +pub(crate) fn GenerateSharedVerticesIndexList( + // The input vertex index->face/vert mappings + // Identical face/verts will have each vertex index + // point to the same (arbitrary?) face/vert + // TODO: This seems overly complicated - storing vertex properties in a + // side channel seems much easier. + // Hopefully implementation can be changed to just use a btreemap or + // something too. + piTriList_in_and_out: &mut [i32], + geometry: &impl Geometry, +) { + let mut map = BTreeMap::new(); + for vertex_index in piTriList_in_and_out { + let index = *vertex_index as usize; + let vertex_properties = [ + get_position(geometry, index), + get_normal(geometry, index), + get_tex_coord(geometry, index), + ] + // We need to make the vertex properties finite to be able to use them in a btreemap + // Technically, these unwraps aren't ideal, but the original puts absolutely no thought into its handling of + // NaN and infinity, so it's probably *fine*. (I strongly suspect that infinity or NaN would have + // lead to UB somewhere) + .map(|prop| FiniteVec3::new(prop).unwrap()); + + *vertex_index = *(map.entry(vertex_properties).or_insert(*vertex_index)); + } +} + +pub(crate) fn GenerateInitialVerticesIndexList( + pTriInfos: &mut [STriInfo], + piTriList_out: &mut [i32], + geometry: &impl Geometry, + iNrTrianglesIn: usize, +) -> usize { + let mut iTSpacesOffs: usize = 0; + let mut iDstTriIndex = 0; + for f in 0..geometry.num_faces() { + let verts = geometry.num_vertices_of_face(f); + + pTriInfos[iDstTriIndex].iOrgFaceNumber = f as i32; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as i32; + if let FaceKind::Triangle = verts { + let pVerts = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts[0] = 0; + pVerts[1] = 1; + pVerts[2] = 2; + piTriList_out[iDstTriIndex * 3] = face_vert_to_index(f, 0) as i32; + piTriList_out[iDstTriIndex * 3 + 1] = face_vert_to_index(f, 1) as i32; + piTriList_out[iDstTriIndex * 3 + 2] = face_vert_to_index(f, 2) as i32; + iDstTriIndex += 1; + } else { + pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f as i32; + pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs as i32; + let i0 = face_vert_to_index(f, 0); + let i1 = face_vert_to_index(f, 1); + let i2 = face_vert_to_index(f, 2); + let i3 = face_vert_to_index(f, 3); + let T0 = get_tex_coord(geometry, i0); + let T1 = get_tex_coord(geometry, i1); + let T2 = get_tex_coord(geometry, i2); + let T3 = get_tex_coord(geometry, i3); + let distSQ_02: f32 = (T2 - T0).length_squared(); + let distSQ_13: f32 = (T3 - T1).length_squared(); + let bQuadDiagIs_02: bool; + if distSQ_02 < distSQ_13 { + bQuadDiagIs_02 = true; + } else if distSQ_13 < distSQ_02 { + bQuadDiagIs_02 = false; + } else { + let P0 = get_position(geometry, i0); + let P1 = get_position(geometry, i1); + let P2 = get_position(geometry, i2); + let P3 = get_position(geometry, i3); + let distSQ_02_0: f32 = (P2 - P0).length_squared(); + let distSQ_13_0: f32 = (P3 - P1).length_squared(); + bQuadDiagIs_02 = distSQ_13_0 >= distSQ_02_0; + } + if bQuadDiagIs_02 { + let pVerts_A = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_A[0] = 0; + pVerts_A[1] = 1; + pVerts_A[2] = 2; + piTriList_out[iDstTriIndex * 3] = i0 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i2 as i32; + iDstTriIndex += 1; + + let pVerts_B = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_B[0] = 0; + pVerts_B[1] = 2; + pVerts_B[2] = 3; + piTriList_out[iDstTriIndex * 3] = i0 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; + iDstTriIndex += 1; + } else { + let pVerts_A_0 = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_A_0[0] = 0; + pVerts_A_0[1] = 1; + pVerts_A_0[2] = 3; + piTriList_out[iDstTriIndex * 3] = i0 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; + iDstTriIndex += 1; + + let pVerts_B_0 = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_B_0[0] = 1; + pVerts_B_0[1] = 2; + pVerts_B_0[2] = 3; + piTriList_out[iDstTriIndex * 3] = i1 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; + iDstTriIndex += 1; + } + } + iTSpacesOffs += verts.num_vertices(); + assert!(iDstTriIndex <= iNrTrianglesIn); + } + + for triangle in pTriInfos.iter_mut().take(iNrTrianglesIn) { + triangle.iFlag = TriangleFlags::empty(); + } + iTSpacesOffs +} + +pub(crate) fn InitTriInfo( + mut pTriInfos: &mut [STriInfo], + piTriListIn: &[i32], + geometry: &impl Geometry, + iNrTrianglesIn: usize, +) { + for triangle in pTriInfos.iter_mut().take(iNrTrianglesIn) { + // C: assumed bad + triangle.iFlag.insert(TriangleFlags::GROUP_WITH_ANY); + } + + for f in 0..iNrTrianglesIn { + let v1 = get_position(geometry, piTriListIn[f * 3] as usize); + let v2 = get_position(geometry, piTriListIn[f * 3 + 1] as usize); + let v3 = get_position(geometry, piTriListIn[f * 3 + 2] as usize); + let t1 = get_tex_coord(geometry, piTriListIn[f * 3] as usize); + let t2 = get_tex_coord(geometry, piTriListIn[f * 3 + 1] as usize); + let t3 = get_tex_coord(geometry, piTriListIn[f * 3 + 2] as usize); + let t21x: f32 = t2.x - t1.x; + let t21y: f32 = t2.y - t1.y; + let t31x: f32 = t3.x - t1.x; + let t31y: f32 = t3.y - t1.y; + let d1 = v2 - v1; + let d2 = v3 - v1; + let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; + let vOs = (t31y * d1) - (t21y * d2); + let vOt = (-t31x * d1) + (t21x * d2); + if fSignedAreaSTx2 > 0.0 { + pTriInfos[f].iFlag.insert(TriangleFlags::ORIENT_PRESERVING); + } + if fSignedAreaSTx2.is_normal() { + let fAbsArea: f32 = fSignedAreaSTx2.abs(); + let fLenOs: f32 = vOs.length(); + let fLenOt: f32 = vOt.length(); + let fS: f32 = if !pTriInfos[f] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING) + { + -1.0f32 + } else { + 1.0f32 + }; + if fLenOs.is_normal() { + pTriInfos[f].vOs = (fS / fLenOs) * vOs; + } + if fLenOt.is_normal() { + pTriInfos[f].vOt = (fS / fLenOt) * vOt; + } + pTriInfos[f].fMagS = fLenOs / fAbsArea; + pTriInfos[f].fMagT = fLenOt / fAbsArea; + if (pTriInfos[f].fMagS.is_normal()) && (pTriInfos[f].fMagT.is_normal()) { + pTriInfos[f].iFlag.remove(TriangleFlags::GROUP_WITH_ANY); + } + } + } + let mut t = 0; + while t < iNrTrianglesIn - 1 { + let iFO_a: i32 = pTriInfos[t].iOrgFaceNumber; + let iFO_b: i32 = pTriInfos[t + 1].iOrgFaceNumber; + if iFO_a == iFO_b { + let bIsDeg_a: bool = pTriInfos[t].iFlag.contains(TriangleFlags::DEGENERATE); + let bIsDeg_b: bool = pTriInfos[(t + 1)].iFlag.contains(TriangleFlags::DEGENERATE); + if !(bIsDeg_a || bIsDeg_b) { + let bOrientA: bool = pTriInfos[t] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); + let bOrientB: bool = pTriInfos[t + 1] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING); + if bOrientA != bOrientB { + let bChooseOrientFirstTri = pTriInfos[t + 1] + .iFlag + .contains(TriangleFlags::GROUP_WITH_ANY) + || (CalcTexArea(geometry, &piTriListIn[(t * 3)..]) + >= CalcTexArea(geometry, &piTriListIn[((t + 1) * 3)..])); + let t0 = if bChooseOrientFirstTri { t } else { t + 1 }; + let t1_0 = if bChooseOrientFirstTri { t + 1 } else { t }; + pTriInfos[t1_0].iFlag.set( + TriangleFlags::ORIENT_PRESERVING, + pTriInfos[t0] + .iFlag + .contains(TriangleFlags::ORIENT_PRESERVING), + ); + } + } + t += 2; + } else { + t += 1; + } + } + + BuildNeighborsFast(pTriInfos, piTriListIn, iNrTrianglesIn as i32); +} + +pub(crate) fn BuildNeighborsFast( + mut pTriInfos: &mut [STriInfo], + piTriListIn: &[i32], + iNrTrianglesIn: i32, +) { + let mut pEdges = Vec::with_capacity((iNrTrianglesIn * 3) as usize); + // build array of edges + for f in 0..iNrTrianglesIn { + for i in 0..3i32 { + let i0: i32 = piTriListIn[(f * 3 + i) as usize]; + let i1: i32 = piTriListIn[(f * 3 + (i + 1) % 3) as usize]; + // Ensure that the indices have a consistent order by making i0 the smaller + pEdges.push(SEdge { + i0: i0.min(i1), + i1: i0.max(i1), + f, + }); + } + } + pEdges.sort(); + + let iEntries = iNrTrianglesIn * 3i32; + + for i in 0..iEntries { + let edge = pEdges[i as usize]; + let i0_0: i32 = edge.i0; + let i1_0: i32 = edge.i1; + let f_0: i32 = edge.f; + + let (i0_A, i1_A, edgenum_A) = GetEdge(&piTriListIn[(f_0 * 3) as usize..], i0_0, i1_0); + let bUnassigned_A = pTriInfos[f_0 as usize].FaceNeighbors[edgenum_A as usize] == -1i32; + if bUnassigned_A { + let mut j: i32 = i + 1i32; + + while j < iEntries && i0_0 == pEdges[j as usize].i0 && i1_0 == pEdges[j as usize].i1 { + let t = pEdges[j as usize].f; + // C: Flip i1 and i0 + let (i1_B, i0_B, edgenum_B) = GetEdge( + &piTriListIn[(t * 3) as usize..], + pEdges[j as usize].i0, + pEdges[j as usize].i1, + ); + let bUnassigned_B = + pTriInfos[t as usize].FaceNeighbors[edgenum_B as usize] == -1i32; + if i0_A == i0_B && i1_A == i1_B && bUnassigned_B { + let t_0: i32 = pEdges[j as usize].f; + pTriInfos[f_0 as usize].FaceNeighbors[edgenum_A as usize] = t_0; + pTriInfos[t_0 as usize].FaceNeighbors[edgenum_B as usize] = f_0; + break; + } + j += 1; + } + } + } +} + +pub(crate) fn GetEdge(indices: &[i32], i0_in: i32, i1_in: i32) -> (i32, i32, i32) { + let indices_to_find = [i0_in, i1_in]; + match ( + indices_to_find.contains(&indices[0]), + indices_to_find.contains(&indices[1]), + ) { + (true, true) => (indices[0], indices[1], 0), + (true, false) => (indices[2], indices[0], 2), + (false, true) => (indices[1], indices[2], 1), + (false, false) => unreachable!(), + } +} +// returns the texture area times 2 +fn CalcTexArea(geometry: &impl Geometry, indices: &[i32]) -> f32 { + let t1 = get_tex_coord(geometry, indices[0] as usize); + let t2 = get_tex_coord(geometry, indices[1] as usize); + let t3 = get_tex_coord(geometry, indices[2] as usize); + let t21x: f32 = t2.x - t1.x; + let t21y: f32 = t2.y - t1.y; + let t31x: f32 = t3.x - t1.x; + let t31y: f32 = t3.y - t1.y; + let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; + if fSignedAreaSTx2 < 0i32 as f32 { + -fSignedAreaSTx2 + } else { + fSignedAreaSTx2 + } +} diff --git a/crates/bevy_mikktspace/src/lib.rs b/crates/bevy_mikktspace/src/lib.rs index 89d7b05427407..1be40a03832a7 100644 --- a/crates/bevy_mikktspace/src/lib.rs +++ b/crates/bevy_mikktspace/src/lib.rs @@ -1,8 +1,25 @@ -#![allow(clippy::all)] +#![forbid(unsafe_code)] +#![allow(clippy::too_many_arguments)] use glam::{Vec2, Vec3}; mod generated; +pub(crate) mod ordered_vec; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum FaceKind { + Triangle, + Quad, +} + +impl FaceKind { + fn num_vertices(&self) -> usize { + match self { + FaceKind::Triangle => 3, + FaceKind::Quad => 4, + } + } +} /// The interface by which mikktspace interacts with your geometry. pub trait Geometry { @@ -10,7 +27,7 @@ pub trait Geometry { fn num_faces(&self) -> usize; /// Returns the number of vertices of a face. - fn num_vertices_of_face(&self, face: usize) -> usize; + fn num_vertices_of_face(&self, face: usize) -> FaceKind; /// Returns the position of a vertex. fn position(&self, face: usize, vert: usize) -> [f32; 3]; @@ -55,23 +72,22 @@ pub trait Geometry { /// /// Returns `false` if the geometry is unsuitable for tangent generation including, /// but not limited to, lack of vertices. -pub fn generate_tangents(geometry: &mut I) -> bool { - unsafe { generated::genTangSpace(geometry, 180.0) } +pub fn generate_tangents(geometry: &mut impl Geometry) -> bool { + generated::genTangSpace(geometry, 180.0) } -fn get_position(geometry: &mut I, index: usize) -> Vec3 { +fn get_position(geometry: &impl Geometry, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); geometry.position(face, vert).into() } -fn get_tex_coord(geometry: &mut I, index: usize) -> Vec3 { +fn get_tex_coord(geometry: &impl Geometry, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); let tex_coord: Vec2 = geometry.tex_coord(face, vert).into(); - let val = tex_coord.extend(1.0); - val + tex_coord.extend(1.0) } -fn get_normal(geometry: &mut I, index: usize) -> Vec3 { +fn get_normal(geometry: &impl Geometry, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); geometry.normal(face, vert).into() } @@ -81,5 +97,6 @@ fn index_to_face_vert(index: usize) -> (usize, usize) { } fn face_vert_to_index(face: usize, vert: usize) -> usize { + assert!(vert < 4); face << 2 | vert & 0x3 } diff --git a/crates/bevy_mikktspace/src/ordered_vec.rs b/crates/bevy_mikktspace/src/ordered_vec.rs new file mode 100644 index 0000000000000..54c4b5969e139 --- /dev/null +++ b/crates/bevy_mikktspace/src/ordered_vec.rs @@ -0,0 +1,28 @@ +use glam::Vec3; +use ordered_float::NotNan; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct FiniteVec3 { + x: NotNan, + y: NotNan, + z: NotNan, +} + +impl FiniteVec3 { + pub fn new(val: Vec3) -> Result { + if val.is_finite() { + Ok(Self { + // Unwrapping here is fine, because is_finite guarantees that + // the values are not `Nan` + x: NotNan::new(val.x).unwrap(), + y: NotNan::new(val.y).unwrap(), + z: NotNan::new(val.z).unwrap(), + }) + } else { + Err(NotFinite) + } + } +} + +#[derive(Debug)] +pub struct NotFinite; diff --git a/crates/bevy_mikktspace/src/refactored.rs b/crates/bevy_mikktspace/src/refactored.rs new file mode 100644 index 0000000000000..30f762be2b311 --- /dev/null +++ b/crates/bevy_mikktspace/src/refactored.rs @@ -0,0 +1,143 @@ +//! The contents of this file are a combination of transpilation and human +//! modification to Morten S. Mikkelsen's original tangent space algorithm +//! implementation written in C. The original source code can be found at +//! +//! and includes the following licence: +//! +//! Copyright (C) 2011 by Morten S. Mikkelsen +//! +//! This software is provided 'as-is', without any express or implied +//! warranty. In no event will the authors be held liable for any damages +//! arising from the use of this software. +//! +//! Permission is granted to anyone to use this software for any purpose, +//! including commercial applications, and to alter it and redistribute it +//! freely, subject to the following restrictions: +//! +//! 1. The origin of this software must not be misrepresented; you must not +//! claim that you wrote the original software. If you use this software +//! in a product, an acknowledgment in the product documentation would be +//! appreciated but is not required. +//! +//! 2. Altered source versions must be plainly marked as such, and must not be +//! misrepresented as being the original software. +//! +//! 3. This notice may not be removed or altered from any source distribution. + +/// The interface by which mikktspace interacts with your geometry. +pub trait Geometry { + // For simplicity's sake, we intentionall eschew the quad handling of mikktspace. + // You should ensure that the mesh passed here is triangulated + + /// Returns the number of faces. + fn num_faces(&self) -> usize; + + /// Returns the position of a vertex. + fn position(&self, face: usize, vert: usize) -> Vec3; + + /// Returns the normal of a vertex. + fn normal(&self, face: usize, vert: usize) -> Vec3; + + /// Returns the texture coordinate of a vertex. + fn tex_coord(&self, face: usize, vert: usize) -> Vec2; + + /// Sets the generated tangent for a vertex. + /// Leave this function unimplemented if you are implementing + /// `set_tangent_encoded`. + fn set_tangent( + &mut self, + tangent: [f32; 3], + _bi_tangent: [f32; 3], + _f_mag_s: f32, + _f_mag_t: f32, + bi_tangent_preserves_orientation: bool, + face: usize, + vert: usize, + ) { + let sign = if bi_tangent_preserves_orientation { + 1.0 + } else { + -1.0 + }; + self.set_tangent_encoded([tangent[0], tangent[1], tangent[2], sign], face, vert); + } + + /// Sets the generated tangent for a vertex with its bi-tangent encoded as the 'W' (4th) + /// component in the tangent. The 'W' component marks if the bi-tangent is flipped. This + /// is called by the default implementation of `set_tangent`; therefore, this function will + /// not be called by the crate unless `set_tangent` is unimplemented. + fn set_tangent_encoded(&mut self, _tangent: [f32; 4], _face: usize, _vert: usize) {} +} + +use std::{ + cmp::Ordering, + collections::{btree_map::Entry, BTreeMap}, +}; + +use glam::{Vec2, Vec3}; + +pub fn generate_tangents(geometry: &mut impl Geometry) { + let triangle_count = geometry.num_faces(); + if triangle_count == 0 { + // Nothing to do - further steps can now assume at least one face exists. + return; + } + let vertex_indices = find_shared_vertices(geometry); + // Ignore degenerate triangles for now +} + +fn find_shared_vertices(geometry: &impl Geometry) -> Vec { + let mut vertex_indices = BTreeMap::::new(); + let mut vertices = Vec::::new(); + for face in 0..geometry.num_faces() { + for vert in 0..3 { + let info = VertexInfo { + position: geometry.position(face, vert), + normal: geometry.normal(face, vert), + tex_coord: geometry.tex_coord(face, vert), + }; + match vertex_indices.entry(info) { + Entry::Vacant(vacant) => { + let new_idx = vertices.len(); + vacant.insert(new_idx); + vertices.push(new_idx); + } + Entry::Occupied(o) => vertices.push(*o.get()), + } + } + } + vertices +} + +fn build_groups() {} + +fn build_neighbours() {} + +#[derive(PartialEq, Clone, Copy)] +struct VertexInfo { + position: Vec3, + normal: Vec3, + tex_coord: Vec2, +} + +impl Eq for VertexInfo {} + +impl Ord for VertexInfo { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Less) + } +} + +impl PartialOrd for VertexInfo { + fn partial_cmp(&self, other: &Self) -> Option { + match self.position.partial_cmp(&other.position) { + Some(Ordering::Equal) => {} + ord => return ord, + } + match self.normal.partial_cmp(&other.normal) { + Some(Ordering::Equal) => {} + ord => return ord, + } + self.tex_coord.partial_cmp(&other.tex_coord) + } +} diff --git a/crates/bevy_mikktspace/tests/regression_test.rs b/crates/bevy_mikktspace/tests/regression_test.rs index 42177cbc3496d..367667265d928 100644 --- a/crates/bevy_mikktspace/tests/regression_test.rs +++ b/crates/bevy_mikktspace/tests/regression_test.rs @@ -8,7 +8,7 @@ clippy::map_flatten )] -use bevy_mikktspace::{generate_tangents, Geometry}; +use bevy_mikktspace::{generate_tangents, FaceKind, Geometry}; use glam::{Vec2, Vec3}; pub type Face = [u32; 3]; @@ -73,8 +73,9 @@ impl Geometry for Context { self.mesh.faces.len() } - fn num_vertices_of_face(&self, _face: usize) -> usize { - 3 + fn num_vertices_of_face(&self, _face: usize) -> FaceKind { + // We don't currently support Quads + FaceKind::Triangle } fn position(&self, face: usize, vert: usize) -> [f32; 3] { diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 3193ebb5d1a09..4ba3e47ea2b92 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -1,5 +1,6 @@ mod conversions; pub mod skinning; +use bevy_mikktspace::FaceKind; pub use wgpu::PrimitiveTopology; use crate::{ @@ -883,8 +884,8 @@ impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> { self.indices.len() / 3 } - fn num_vertices_of_face(&self, _: usize) -> usize { - 3 + fn num_vertices_of_face(&self, _: usize) -> FaceKind { + FaceKind::Triangle } fn position(&self, face: usize, vert: usize) -> [f32; 3] {