//! Everything in this module is pending to be refactored, turned into idiomatic-rust, and moved to
//! other modules.

//! 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
//! <https://archive.blender.org/wiki/index.php/Dev:Shading/Tangent_Space_Normal_Maps>
//! 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.

#![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
)]

use std::ptr::null_mut;

use glam::Vec3;

use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry};

#[derive(Copy, Clone)]
pub struct STSpace {
    pub vOs: Vec3,
    pub fMagS: f32,
    pub vOt: Vec3,
    pub fMagT: f32,
    pub iCounter: i32,
    pub bOrient: bool,
}

impl STSpace {
    pub fn zero() -> Self {
        Self {
            vOs: Default::default(),
            fMagS: 0.0,
            vOt: Default::default(),
            fMagT: 0.0,
            iCounter: 0,
            bOrient: false,
        }
    }
}

// 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

#[derive(Copy, Clone)]
pub struct STriInfo {
    pub FaceNeighbors: [i32; 3],
    pub AssignedGroup: [*mut SGroup; 3],
    pub vOs: Vec3,
    pub vOt: Vec3,
    pub fMagS: f32,
    pub fMagT: f32,
    pub iOrgFaceNumber: i32,
    pub iFlag: i32,
    pub iTSpacesOffs: i32,
    pub vert_num: [u8; 4],
}

impl STriInfo {
    fn zero() -> Self {
        Self {
            FaceNeighbors: [0, 0, 0],
            AssignedGroup: [null_mut(), null_mut(), null_mut()],
            vOs: Default::default(),
            vOt: Default::default(),
            fMagS: 0.0,
            fMagT: 0.0,
            iOrgFaceNumber: 0,
            iFlag: 0,
            iTSpacesOffs: 0,
            vert_num: [0, 0, 0, 0],
        }
    }
}

#[derive(Copy, Clone)]
pub struct SGroup {
    pub iNrFaces: i32,
    pub pFaceIndices: *mut i32,
    pub iVertexRepresentitive: i32,
    pub bOrientPreservering: bool,
}

impl SGroup {
    fn zero() -> Self {
        Self {
            iNrFaces: 0,
            pFaceIndices: null_mut(),
            iVertexRepresentitive: 0,
            bOrientPreservering: false,
        }
    }
}

#[derive(Clone)]
pub struct SSubGroup {
    pub iNrFaces: i32,
    pub pTriMembers: Vec<i32>,
}

impl SSubGroup {
    fn zero() -> Self {
        Self {
            iNrFaces: 0,
            pTriMembers: Vec::new(),
        }
    }
}

#[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 {
    pub i0: i32,
    pub i1: i32,
    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,
        }
    }
}

pub unsafe fn genTangSpace<I: Geometry>(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 verts = geometry.num_vertices_of_face(f);
        if verts == 3 {
            iNrTrianglesIn += 1
        } else if verts == 4 {
            iNrTrianglesIn += 2
        }
        f += 1
    }
    if iNrTrianglesIn <= 0 {
        return false;
    }

    let mut piTriListIn = vec![0i32; 3 * iNrTrianglesIn];
    let mut pTriInfos = vec![STriInfo::zero(); iNrTrianglesIn];

    iNrTSPaces = 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];
        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(),
        iNrTrianglesIn as i32,
        iTotTris as i32,
    );
    InitTriInfo(
        pTriInfos.as_mut_ptr(),
        piTriListIn.as_ptr(),
        geometry,
        iNrTrianglesIn,
    );
    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(),
        iNrTrianglesIn as i32,
    );

    let mut psTspace = vec![
        STSpace {
            vOs: Vec3::new(1.0, 0.0, 0.0),
            fMagS: 1.0,
            vOt: Vec3::new(0.0, 1.0, 0.0),
            fMagT: 1.0,
            ..STSpace::zero()
        };
        iNrTSPaces
    ];

    bRes = GenerateTSpaces(
        &mut psTspace,
        pTriInfos.as_ptr(),
        pGroups.as_ptr(),
        iNrActiveGroups,
        piTriListIn.as_ptr(),
        fThresCos,
        geometry,
    );
    if !bRes {
        return false;
    }
    DegenEpilogue(
        psTspace.as_mut_ptr(),
        pTriInfos.as_mut_ptr(),
        piTriListIn.as_mut_ptr(),
        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<I: Geometry>(
    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),
            );
            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
                }
            }
        }
        t += 1
    }
}

unsafe fn GenerateTSpaces<I: Geometry>(
    psTspace: &mut [STSpace],
    mut pTriInfos: *const STriInfo,
    mut pGroups: *const SGroup,
    iNrActiveGroups: i32,
    mut piTriListIn: *const i32,
    fThresCos: f32,
    geometry: &mut I,
) -> 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 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;
        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;
            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 bSameOrgFace: bool = iOF_1 == iOF_2;
                let fCosS: f32 = vOs.dot(vOs2);
                let fCosT: f32 = vOt.dot(vOt2);
                if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos {
                    let fresh0 = iMembers;
                    iMembers = iMembers + 1;
                    pTmpMembers[fresh0] = t
                }
                j += 1
            }
            if iMembers > 1 {
                let mut uSeed: u32 = 39871946i32 as u32;
                QuickSort(pTmpMembers.as_mut_ptr(), 0i32, (iMembers - 1) as i32, uSeed);
            }
            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
                }
            }
            if !bFound {
                pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as i32;
                pUniSubGroups[iUniqueSubGroups].pTriMembers = tmp_group.pTriMembers.clone();

                pSubGroupTspace[iUniqueSubGroups] = EvalTspace(
                    tmp_group.pTriMembers.as_mut_ptr(),
                    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;
            if (*pTS_out).iCounter == 1i32 {
                *pTS_out = AvgTSpace(pTS_out, &mut pSubGroupTspace[l]);
                (*pTS_out).iCounter = 2i32;
                (*pTS_out).bOrient = (*pGroup).bOrientPreservering
            } else {
                *pTS_out = pSubGroupTspace[l];
                (*pTS_out).iCounter = 1i32;
                (*pTS_out).bOrient = (*pGroup).bOrientPreservering
            }
            i += 1
        }
        iUniqueTspaces += iUniqueSubGroups;
        g += 1
    }
    return true;
}
unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpace {
    let mut ts_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,
    };
    if (*pTS0).fMagS == (*pTS1).fMagS
        && (*pTS0).fMagT == (*pTS1).fMagT
        && (*pTS0).vOs == (*pTS1).vOs
        && (*pTS0).vOt == (*pTS1).vOt
    {
        ts_res.fMagS = (*pTS0).fMagS;
        ts_res.fMagT = (*pTS0).fMagT;
        ts_res.vOs = (*pTS0).vOs;
        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)
        }
    }
    return ts_res;
}

unsafe fn Normalize(v: Vec3) -> Vec3 {
    return (1.0 / v.length()) * v;
}

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<I: Geometry>(
    mut face_indices: *mut i32,
    iFaces: i32,
    mut piTriListIn: *const i32,
    mut pTriInfos: *const STriInfo,
    geometry: &mut I,
    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 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
            } else {
                fCos
            };
            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);
            res.fMagS += fAngle * fMagS;
            res.fMagT += fAngle * fMagT;
            fAngleSum += fAngle
        }
        face += 1
    }
    if VNotZero(res.vOs) {
        res.vOs = Normalize(res.vOs)
    }
    if VNotZero(res.vOt) {
        res.vOt = Normalize(res.vOt)
    }
    if fAngleSum > 0i32 as f32 {
        res.fMagS /= fAngleSum;
        res.fMagT /= fAngleSum
    }
    return 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,
    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 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;
                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];
                if neigh_indexL >= 0i32 {
                    let bAnswer: bool = AssignRecur(
                        piTriListIn,
                        pTriInfos,
                        neigh_indexL,
                        (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize],
                    );
                    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 };
                }
                if neigh_indexR >= 0i32 {
                    let bAnswer_0: bool = AssignRecur(
                        piTriListIn,
                        pTriInfos,
                        neigh_indexR,
                        (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize],
                    );
                    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 };
                }
                iOffset += (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces
            }
            i += 1
        }
        f += 1
    }
    return iNrActiveGroups;
}
// ///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe fn AssignRecur(
    mut piTriListIn: *const i32,
    mut psTriInfos: *mut STriInfo,
    iMyTriIndex: i32,
    mut pGroup: *mut SGroup,
) -> bool {
    let mut pMyTriInfo: *mut STriInfo =
        &mut *psTriInfos.offset(iMyTriIndex as isize) as *mut STriInfo;
    // 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 {
        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
            }
        }
    }
    let bOrient: bool = if (*pMyTriInfo).iFlag & 8i32 != 0i32 {
        true
    } else {
        false
    };
    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];
    if neigh_indexL >= 0i32 {
        AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup);
    }
    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<I: Geometry>(
    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,
        );
        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<I: Geometry>(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
        }
    }
}
unsafe fn GenerateSharedVerticesIndexList<I: Geometry>(
    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);
        piHashTable[(piHashOffsets[iCell_0] + piHashCount2[iCell_0]) as usize] = 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 pTable_0 = piHashTable.as_mut_ptr().offset(piHashOffsets[k] as isize);
        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<I: Geometry>(
    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<I: Geometry>(
    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;
}
