UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

340 lines (279 loc) • 10.5 kB
import { vec3 } from "gl-matrix"; import { v3_allocate } from "../../../../core/geom/vec3/v3_allocate.js"; import { DEG_TO_RAD } from "../../../../core/math/DEG_TO_RAD.js"; import { Build4RuleGroups } from "./Build4RuleGroups.js"; import { MARK_DEGENERATE } from "./constants/MARK_DEGENERATE.js"; import { DegenEpilogue } from "./DegenEpilogue.js"; import { DegenPrologue } from "./DegenPrologue.js"; import { GenerateInitialVerticesIndexList } from "./GenerateInitialVerticesIndexList.js"; import { GenerateSharedVerticesIndexList } from "./GenerateSharedVerticesIndexList.js"; import { GenerateTSpaces } from "./GenerateTSpaces.js"; import { GetPosition } from "./GetPosition.js"; import { InitTriInfo } from "./InitTriInfo.js"; import { m_getNumFaces } from "./m_getNumFaces.js"; import { m_setTSpace } from "./m_setTSpace.js"; import { malloc } from "./malloc.js"; import { STSpace } from "./STSpace.js"; /* 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. @see https://github.com/mmikk/MikkTSpace/blob/master/mikktspace.c (the original) @see https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/intern/mikktspace/mikktspace.c (looks like best-optimized version) @see https://github.com/undefinist/mikktspacehx/blob/master/src/mikktspace/Mikktspace.hx (Haxe port) @see https://github.com/gltf-rs/mikktspace/blob/6275cc4f15cff8be29819fb34ae8be3b9129dae1/src/generated.rs (Rust port) */ class SGroup { constructor() { this.iNrFaces = 0; /** * *int * @type {null} */ this.pFaceIndices = null; this.iVertexRepresentitive = 0; this.bOrientPreservering = false; } } class STriInfo { constructor() { /** * int[3] * @type {number[]} */ this.FaceNeighbors = []; /** * SGroup[3] * @type {SGroup[]} */ this.AssignedGroup = []; // normalized first order face derivatives /** * @type {vec3|Float32Array} */ this.vOs = v3_allocate(); /** * @type {vec3|Float32Array} */ this.vOt = v3_allocate(); // original magnitudes this.fMagS = 0; this.fMagT = 0; /** * * @type {number} */ this.iOrgFaceNumber = 0; /** * * @type {number} */ this.iFlag = 0; /** * int[4] * @type {number[]} */ this.vert_num = new Uint8Array(4); /** * * @type {number} */ this.iTSpacesOffs = 0; } } export class SMikkTSpaceContext { constructor() { /** * * @type {number[]} */ this.geometry_buffer_index = []; /** * * @type {number[]} */ this.geometry_buffer_vertex_normal = []; /** * * @type {number[]} */ this.geometry_buffer_vertex_position = []; /** * * @type {number[]} */ this.geometry_buffer_vertex_uv = []; /** * * @type {number[]} */ this.geometry_buffer_vertex_tangent = []; /** * * @type {number[]} */ this.geometry_buffer_vertex_bitangent = []; } } // returns the position/normal/texcoord of the referenced face of vertex number iVert. // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. /** * * @param {SMikkTSpaceContext} pContext * @param {number} [fAngularThreshold] * @return {boolean} */ export function genTangSpace(pContext, fAngularThreshold = 180) { // count nr_triangles let iNrTSPaces = 0, iNrMaxGroups = 0; let iNrActiveGroups = 0; const iNrFaces = m_getNumFaces(pContext); const fThresCos = Math.cos(fAngularThreshold * DEG_TO_RAD); // count triangles on supported faces // AlexGoldring: since we only support triangles, the number of triangles is going to equal number of faces let iNrTrianglesIn = iNrFaces; if (iNrTrianglesIn <= 0) { return false; } // allocate memory for an index list const piTriListIn = new Int32Array(3 * iNrTrianglesIn); const pTriInfos = malloc(STriInfo, iNrTrianglesIn); // make an initial triangle --> face index list iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); // make a welded index list of identical positions and attributes (pos, norm, texc) //printf("gen welded index list begin\n"); GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); //printf("gen welded index list end\n"); // Mark all degenerate triangles const iTotTris = iNrTrianglesIn; let iDegenTriangles = 0; const p0 = vec3.create(); const p1 = vec3.create(); const p2 = vec3.create(); for (let t = 0; t < iTotTris; t++) { const t3 = t * 3; const i0 = piTriListIn[t3 ]; const i1 = piTriListIn[t3 + 1]; const i2 = piTriListIn[t3 + 2]; GetPosition(p0, 0, pContext, i0); GetPosition(p1, 0, pContext, i1); GetPosition(p2, 0, pContext, i2); if ( vec3.exactEquals(p0, p1) || vec3.exactEquals(p0, p2) || vec3.exactEquals(p1, p2) ) { // degenerate pTriInfos[t].iFlag |= MARK_DEGENERATE; ++iDegenTriangles; } } iNrTrianglesIn = iTotTris - iDegenTriangles; // mark all triangle pairs that belong to a quad with only one // good triangle. These need special treatment in DegenEpilogue(). // Additionally, move all good triangles to the start of // pTriInfos[] and piTriListIn[] without changing order and // put the degenerate triangles last. DegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris); // evaluate triangle level attributes and neighbor list //printf("gen neighbors list begin\n"); InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); //printf("gen neighbors list end\n"); // based on the 4 rules, identify groups based on connectivity iNrMaxGroups = iNrTrianglesIn * 3; const pGroups = malloc(SGroup, iNrMaxGroups); const piGroupTrianglesBuffer = new Int32Array(iNrTrianglesIn * 3); //printf("gen 4rule groups begin\n"); iNrActiveGroups = Build4RuleGroups( pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn ); //printf("gen 4rule groups end\n"); // /** * * @type {STSpace[]} */ const psTspace = malloc(STSpace, iNrTSPaces); // memset not requried in JS // memset(psTspace, 0, sizeof(STSpace)*iNrTSPaces); for (let t = 0; t < iNrTSPaces; t++) { const stSpace = psTspace[t]; stSpace.vOs[0] = 1.0; stSpace.vOs[1] = 0.0; stSpace.vOs[2] = 0.0; stSpace.fMagS = 1.0; stSpace.vOt[0] = 0.0; stSpace.vOt[1] = 1.0; stSpace.vOt[2] = 0.0; stSpace.fMagT = 1.0; } // make tspaces, each group is split up into subgroups if necessary // based on fAngularThreshold. Finally, a tangent space is made for // every resulting subgroup //printf("gen tspaces begin\n"); GenerateTSpaces( psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext ); //printf("gen tspaces end\n"); // degenerate quads with one good triangle will be fixed by copying a space from // the good triangle to the coinciding vertex. // all other degenerate triangles will just copy a space from any good triangle // with the same welded index in piTriListIn[]. DegenEpilogue( psTspace, pTriInfos, piTriListIn, pContext, iNrTrianglesIn, iTotTris ); for (let f = 0; f < iNrFaces; f++) { // set data for (let i = 0; i < 3; i++) { const index = f * 3 + i const pTSpace = psTspace[index]; const tangent = pTSpace.vOs; const bitangent = pTSpace.vOt; m_setTSpace( pContext, tangent, bitangent, pTSpace.fMagS, pTSpace.fMagT, pTSpace.bOrient, f, i ); } } return true; }