@woosh/meep-engine 2.41.0 → 2.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/core/assert.js +2 -2
  2. package/core/collection/array/array_swap.js +3 -3
  3. package/core/collection/map/AsyncLoadingCache.js +47 -0
  4. package/core/geom/3d/aabb/aabb3_compute_distance_above_plane_max.js +1 -1
  5. package/core/geom/3d/apply_mat4_transform_to_v3_array.js +2 -4
  6. package/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +28 -0
  7. package/core/geom/Quaternion.js +14 -0
  8. package/core/math/statistics/computeSampleSize_Cochran.js +3 -3
  9. package/editor/ecs/component/editors/geom/QuaternionEditor.js +12 -5
  10. package/engine/Engine.js +6 -1
  11. package/engine/EngineBootstrapper.js +2 -1
  12. package/engine/EngineHarness.js +13 -3
  13. package/engine/asset/AssetManager.js +97 -7
  14. package/engine/development/performance/AbstractMetric.js +1 -0
  15. package/engine/development/performance/RingBufferMetric.js +25 -4
  16. package/engine/ecs/EntityBuilder.js +29 -4
  17. package/engine/ecs/transform/Transform.js +23 -3
  18. package/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +17 -1
  19. package/engine/graphics/ecs/decal/v2/Decal.d.ts +11 -0
  20. package/engine/graphics/ecs/decal/v2/Decal.js +50 -0
  21. package/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts +8 -0
  22. package/engine/graphics/ecs/decal/v2/FPDecalSystem.js +201 -0
  23. package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +278 -0
  24. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +8 -1
  25. package/engine/graphics/ecs/mesh-v2/allocate_v3.js +37 -0
  26. package/engine/graphics/ecs/mesh-v2/build_three_object.js +4 -0
  27. package/engine/graphics/geometry/MikkT/AddTriToGroup.js +10 -0
  28. package/engine/graphics/geometry/MikkT/AssignRecur.js +84 -0
  29. package/engine/graphics/geometry/MikkT/AvgTSpace.js +38 -0
  30. package/engine/graphics/geometry/MikkT/Build4RuleGroups.js +96 -0
  31. package/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +137 -0
  32. package/engine/graphics/geometry/MikkT/CalcTexArea.js +31 -0
  33. package/engine/graphics/geometry/MikkT/CompareSubGroups.js +26 -0
  34. package/engine/graphics/geometry/MikkT/DegenEpilogue.js +220 -0
  35. package/engine/graphics/geometry/MikkT/DegenPrologue.js +115 -0
  36. package/engine/graphics/geometry/MikkT/EvalTspace.js +128 -0
  37. package/engine/graphics/geometry/MikkT/GenerateInitialVerticesIndexList.js +48 -0
  38. package/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +184 -0
  39. package/engine/graphics/geometry/MikkT/GenerateTSpaces.js +226 -0
  40. package/engine/graphics/geometry/MikkT/GetEdge.js +45 -0
  41. package/engine/graphics/geometry/MikkT/GetNormal.js +16 -0
  42. package/engine/graphics/geometry/MikkT/GetPosition.js +25 -0
  43. package/engine/graphics/geometry/MikkT/GetTexCoord.js +18 -0
  44. package/engine/graphics/geometry/MikkT/InitTriInfo.js +180 -0
  45. package/engine/graphics/geometry/MikkT/Length.js +10 -0
  46. package/engine/graphics/geometry/MikkT/MakeIndex.js +18 -0
  47. package/engine/graphics/geometry/MikkT/MikkTSpace.js +197 -2068
  48. package/engine/graphics/geometry/MikkT/NormalizeSafe.js +21 -0
  49. package/engine/graphics/geometry/MikkT/NotZero.js +10 -0
  50. package/engine/graphics/geometry/MikkT/QuickSort.js +54 -0
  51. package/engine/graphics/geometry/MikkT/QuickSortEdges.js +71 -0
  52. package/engine/graphics/geometry/MikkT/SSubGroup.js +15 -0
  53. package/engine/graphics/geometry/MikkT/STSpace.js +36 -0
  54. package/engine/graphics/geometry/MikkT/constants/GROUP_WITH_ANY.js +1 -0
  55. package/engine/graphics/geometry/MikkT/constants/INTERNAL_RND_SORT_SEED.js +1 -0
  56. package/engine/graphics/geometry/MikkT/constants/MARK_DEGENERATE.js +1 -0
  57. package/engine/graphics/geometry/MikkT/constants/ORIENT_PRESERVING.js +1 -0
  58. package/engine/graphics/geometry/MikkT/constants/QUAD_ONE_DEGEN_TRI.js +1 -0
  59. package/engine/graphics/geometry/MikkT/m_getNormal.js +16 -0
  60. package/engine/graphics/geometry/MikkT/m_getNumFaces.js +8 -0
  61. package/engine/graphics/geometry/MikkT/m_getNumVerticesOfFace.js +11 -0
  62. package/engine/graphics/geometry/MikkT/m_getPosition.js +20 -0
  63. package/engine/graphics/geometry/MikkT/m_getTexCoord.js +16 -0
  64. package/engine/graphics/geometry/MikkT/m_setTSpace.js +35 -0
  65. package/engine/graphics/geometry/MikkT/m_setTSpaceBasic.js +22 -0
  66. package/engine/graphics/geometry/MikkT/malloc.js +16 -0
  67. package/engine/graphics/geometry/MikkT/v3_scale_dot_sub_normalize.js +52 -0
  68. package/engine/graphics/geometry/buffered/computeGeometryEquality.js +1 -1
  69. package/engine/graphics/geometry/buffered/computeGeometryHash.js +1 -1
  70. package/engine/graphics/impostors/octahedral/ImpostorBaker.js +28 -14
  71. package/engine/graphics/impostors/octahedral/ImpostorDescription.js +6 -0
  72. package/engine/graphics/impostors/octahedral/README.md +1 -0
  73. package/engine/graphics/impostors/octahedral/bake/compute_bounding_sphere.js +25 -22
  74. package/engine/graphics/impostors/octahedral/bake/compute_bounding_sphere_radius_only.js +37 -0
  75. package/engine/graphics/impostors/octahedral/bake/prepare_bake_material.js +30 -1
  76. package/engine/graphics/impostors/octahedral/grid/HemiOctahedralUvEncoder.js +1 -1
  77. package/engine/graphics/impostors/octahedral/prototypeBaker.js +121 -22
  78. package/engine/graphics/impostors/octahedral/shader/BakeShaderStandard.js +46 -7
  79. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.js +349 -0
  80. package/engine/graphics/impostors/octahedral/shader/ImpostorShaderV1.js +74 -0
  81. package/engine/graphics/impostors/octahedral/shader/glsl/v1/common.glsl +209 -0
  82. package/engine/graphics/impostors/octahedral/shader/glsl/v1/flagment.glsl +80 -0
  83. package/engine/graphics/impostors/octahedral/shader/glsl/v1/vertex.glsl +350 -0
  84. package/engine/graphics/micron/render/v1/getTransformedPositionsCached.js +1 -1
  85. package/engine/graphics/render/forward_plus/LightManager.js +17 -7
  86. package/engine/graphics/render/forward_plus/data/TextureBackedMemoryRegion.js +7 -1
  87. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_ACCUMULATION.js +13 -5
  88. package/engine/graphics/render/forward_plus/materials/FP_SHADER_CHUNK_PREAMBLE.js +3 -1
  89. package/engine/graphics/render/forward_plus/model/Decal.js +19 -2
  90. package/engine/graphics/render/forward_plus/plugin/MaterialTransformer.js +14 -2
  91. package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +2 -2
  92. package/engine/graphics/texture/sampler/Sampler2D.js +10 -10
  93. package/engine/graphics/texture/sampler/prototypeSamplerFiltering.js +117 -11
  94. package/engine/graphics/texture/sampler/resize/sampler2d_downsample_mipmap.js +66 -0
  95. package/engine/graphics/texture/sampler/sampler2_d_scale_down_lanczos.js +2 -2
  96. package/engine/intelligence/behavior/util/RotationBehavior.js +69 -0
  97. package/generation/example/filters/SampleGroundMoistureFilter.js +5 -5
  98. package/package.json +1 -1
@@ -1,153 +1,51 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import { EPSILON } from "../../../../core/math/MathUtils.js";
3
- import { array_copy } from "../../../../core/collection/array/copyArray.js";
4
- import { fabsf } from "../../../../core/math/fabsf.js";
5
-
6
- // To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the
7
- // normal map sampler must use the exact inverse of the pixel shader transformation.
8
- // The most efficient transformation we can possibly do in the pixel shader is
9
- // achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN.
10
- // pixel shader (fast transform out)
11
- // vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
12
- // where vNt is the tangent space normal. The normal map sampler must likewise use the
13
- // interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader.
14
- // sampler does (exact inverse of pixel shader):
15
- // float3 row0 = cross(vB, vN);
16
- // float3 row1 = cross(vN, vT);
17
- // float3 row2 = cross(vT, vB);
18
- // float fSign = dot(vT, row0)<0 ? -1 : 1;
19
- // vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );
20
- // where vNout is the sampled normal in some chosen 3D space.
21
- //
22
- // Should you choose to reconstruct the bitangent in the pixel shader instead
23
- // of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
24
- // Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of
25
- // quads as your renderer then problems will occur since the interpolated tangent spaces will differ
26
- // eventhough the vertex level tangent spaces match. This can be solved either by triangulating before
27
- // sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.
28
- // However, this must be used both by the sampler and your tools/rendering pipeline.
29
-
30
-
31
- /**
32
- * @extends {number[]}
33
- */
34
- class SVec3 {
35
- constructor() {
36
- this.x = 0;
37
- this.y = 0;
38
- this.z = 0;
39
- }
40
-
41
- get 0() {
42
- return this.x;
43
- }
44
-
45
- set 0(v) {
46
- this.x = v;
47
- }
48
-
49
- get 1() {
50
- return this.y;
51
- }
52
-
53
-
54
- set 1(v) {
55
- this.y = v;
56
- }
57
-
58
- get 2() {
59
- return this.z;
60
- }
61
-
62
- set 2(v) {
63
- this.z = v;
64
- }
65
-
66
- }
67
-
68
- class SEdge {
69
- constructor() {
70
- /**
71
- * int
72
- * @type {number}
73
- */
74
- this.i0 = 0;
75
- /**
76
- * int
77
- * @type {number}
78
- */
79
- this.i1 = 0;
80
- /**
81
- * int
82
- * @type {number}
83
- */
84
- this.f = 0;
85
- }
86
-
87
- get 0() {
88
- return this.i0;
89
- }
90
-
91
- set 0(v) {
92
- this.i0 = v;
93
- }
94
-
95
- get 1() {
96
- return this.i1;
97
- }
98
-
99
- set 1(v) {
100
- this.i1 = v;
101
- }
102
-
103
- get 2() {
104
- return this.f;
105
- }
106
-
107
- set 2(v) {
108
- this.f = v;
109
- }
110
- }
111
-
112
- class STSpace {
113
- constructor() {
114
- this.vOs = new SVec3();
115
- this.fMagS = 0;
116
- this.vOt = new SVec3();
117
- this.fMagT = 0;
118
- this.iCounter = 0; // this is to average back into quads.
119
- this.bOrient = false;
120
- }
121
-
122
- /**
123
- *
124
- * @param {STSpace} other
125
- */
126
- copy(other) {
127
- this.vOs = other.vOs;
128
- this.fMagS = other.fMagS;
129
- this.vOt = other.vOt;
130
- this.fMagT = other.fMagT;
131
- this.iCounter = other.iCounter;
132
- this.bOrient = other.bOrient;
133
- }
134
- }
135
-
136
- class SSubGroup {
137
- constructor() {
138
- /**
139
- *
140
- * @type {number}
141
- */
142
- this.iNrFaces = 0;
143
-
144
- /**
145
- * *int
146
- * @type {null}
147
- */
148
- this.pTriMembers = null;
149
- }
150
- }
1
+ import { vec3 } from "gl-matrix";
2
+ import { m_setTSpace } from "./m_setTSpace.js";
3
+ import { GetPosition } from "./GetPosition.js";
4
+ import { malloc } from "./malloc.js";
5
+ import { STSpace } from "./STSpace.js";
6
+ import { MARK_DEGENERATE } from "./constants/MARK_DEGENERATE.js";
7
+ import { InitTriInfo } from "./InitTriInfo.js";
8
+ import { GenerateTSpaces } from "./GenerateTSpaces.js";
9
+ import { DegenEpilogue } from "./DegenEpilogue.js";
10
+ import { Build4RuleGroups } from "./Build4RuleGroups.js";
11
+ import { allocate_v3 } from "../../ecs/mesh-v2/allocate_v3.js";
12
+ import { m_getNumFaces } from "./m_getNumFaces.js";
13
+ import { GenerateInitialVerticesIndexList } from "./GenerateInitialVerticesIndexList.js";
14
+ import { DegenPrologue } from "./DegenPrologue.js";
15
+ import { GenerateSharedVerticesIndexList } from "./GenerateSharedVerticesIndexList.js";
16
+ import { DEG_TO_RAD } from "../../../../core/math/DEG_TO_RAD.js";
17
+
18
+
19
+ /*
20
+ To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the
21
+ normal map sampler must use the exact inverse of the pixel shader transformation.
22
+ The most efficient transformation we can possibly do in the pixel shader is
23
+ achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN.
24
+ pixel shader (fast transform out)
25
+ vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
26
+ where vNt is the tangent space normal. The normal map sampler must likewise use the
27
+ interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader.
28
+ sampler does (exact inverse of pixel shader):
29
+ float3 row0 = cross(vB, vN);
30
+ float3 row1 = cross(vN, vT);
31
+ float3 row2 = cross(vT, vB);
32
+ float fSign = dot(vT, row0)<0 ? -1 : 1;
33
+ vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );
34
+ where vNout is the sampled normal in some chosen 3D space.
35
+
36
+ Should you choose to reconstruct the bitangent in the pixel shader instead
37
+ of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
38
+ Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of
39
+ quads as your renderer then problems will occur since the interpolated tangent spaces will differ
40
+ eventhough the vertex level tangent spaces match. This can be solved either by triangulating before
41
+ sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.
42
+ However, this must be used both by the sampler and your tools/rendering pipeline.
43
+
44
+ @see https://github.com/mmikk/MikkTSpace/blob/master/mikktspace.c (the original)
45
+ @see https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/intern/mikktspace/mikktspace.c (looks like best-optimized version)
46
+ @see https://github.com/undefinist/mikktspacehx/blob/master/src/mikktspace/Mikktspace.hx (Haxe port)
47
+ @see https://github.com/gltf-rs/mikktspace/blob/6275cc4f15cff8be29819fb34ae8be3b9129dae1/src/generated.rs (Rust port)
48
+ */
151
49
 
152
50
  class SGroup {
153
51
  constructor() {
@@ -177,8 +75,15 @@ class STriInfo {
177
75
  this.AssignedGroup = [];
178
76
 
179
77
  // normalized first order face derivatives
180
- this.vOs = new SVec3();
181
- this.vOt = new SVec3();
78
+
79
+ /**
80
+ * @type {vec3|Float32Array}
81
+ */
82
+ this.vOs = allocate_v3();
83
+ /**
84
+ * @type {vec3|Float32Array}
85
+ */
86
+ this.vOt = allocate_v3();
182
87
 
183
88
  // original magnitudes
184
89
  this.fMagS = 0;
@@ -200,7 +105,7 @@ class STriInfo {
200
105
  * int[4]
201
106
  * @type {number[]}
202
107
  */
203
- this.vert_num = [];
108
+ this.vert_num = new Uint8Array(4);
204
109
 
205
110
  /**
206
111
  *
@@ -210,130 +115,6 @@ class STriInfo {
210
115
  }
211
116
  }
212
117
 
213
- class SMikkTSpaceInterface {
214
- constructor() {
215
-
216
- }
217
-
218
- /**
219
- * Returns the number of faces (triangles/quads) on the mesh to be processed.
220
- * @param {SMikkTSpaceContext} pContext
221
- * @returns {number}
222
- */
223
- m_getNumFaces(pContext) {
224
- return pContext.geometry_buffer_index.length / 3;
225
- }
226
-
227
- /**
228
- * Returns the number of vertices on face number iFace
229
- * iFace is a number in the range {0, 1, ..., getNumFaces()-1}
230
- * @param {SMikkTSpaceContext} pContext
231
- * @param {number} iFace
232
- * @returns {number}
233
- */
234
- m_getNumVerticesOfFace(pContext, iFace) {
235
- // only support triangles
236
- return 3;
237
- }
238
-
239
- // returns the position/normal/texcoord of the referenced face of vertex number iVert.
240
- // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.
241
-
242
- /**
243
- *
244
- * @param {SMikkTSpaceContext} pContext
245
- * @param {number[]|{0:number,1:number,2:number}} fvPosOut
246
- * @param {number} iFace
247
- * @param {number} iVert
248
- * @returns {void}
249
- */
250
- m_getPosition(pContext, fvPosOut, iFace, iVert) {
251
- // figure out which vertex it is
252
- const vertex_index = pContext.geometry_buffer_index[iFace * 3 + iVert];
253
-
254
- array_copy(pContext.geometry_buffer_vertex_position, vertex_index * 3, fvPosOut, 0, 3);
255
- }
256
-
257
- /**
258
- *
259
- * @param {SMikkTSpaceContext} pContext
260
- * @param {number[]|{0:number,1:number,2:number}} fvNormOut
261
- * @param {number} iFace
262
- * @param {number} iVert
263
- * @returns {void}
264
- */
265
- m_getNormal(pContext, fvNormOut, iFace, iVert) {
266
- // figure out which vertex it is
267
- const vertex_index = pContext.geometry_buffer_index[iFace * 3 + iVert];
268
-
269
- array_copy(pContext.geometry_buffer_vertex_normal, vertex_index * 3, fvNormOut, 0, 3);
270
- }
271
-
272
- /**
273
- *
274
- * @param {SMikkTSpaceContext} pContext
275
- * @param {number[]|{0:number,1:number}} fvTexcOut
276
- * @param {number} iFace
277
- * @param {number} iVert
278
- * @returns {void}
279
- */
280
- m_getTexCoord(pContext, fvTexcOut, iFace, iVert) {
281
- // figure out which vertex it is
282
- const vertex_index = pContext.geometry_buffer_index[iFace * 3 + iVert];
283
-
284
- array_copy(pContext.geometry_buffer_vertex_uv, vertex_index * 2, fvTexcOut, 0, 3);
285
- }
286
-
287
- /**
288
- * either (or both) of the two setTSpace callbacks can be set.
289
- * The call-back m_setTSpaceBasic() is sufficient for basic normal mapping.
290
- *
291
- * This function is used to return the tangent and fSign to the application.
292
- * fvTangent is a unit length vector.
293
- * For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
294
- * bitangent = fSign * cross(vN, tangent);
295
- * Note that the results are returned unindexed. It is possible to generate a new index list
296
- * But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
297
- * DO NOT! use an already existing index list.
298
- *
299
- * @param {SMikkTSpaceContext} pContext
300
- * @param {number[]} fvTangent
301
- * @param {number} fSign
302
- * @param {number} iFace
303
- * @param {number} iVert
304
- * @returns {void}
305
- */
306
- m_setTSpaceBasic(pContext, fvTangent, fSign, iFace, iVert) {
307
- throw new Error('Not supported');
308
- }
309
-
310
- /**
311
- *
312
- * @param {SMikkTSpaceContext} pContext
313
- * @param {number[]} fvTangent
314
- * @param {number[]} fvBiTangent
315
- * @param {number} fMagS
316
- * @param {number} fMagT
317
- * @param {boolean} bIsOrientationPreserving
318
- * @param {number} iFace
319
- * @param {number} iVert
320
- * @returns {void}
321
- */
322
- m_setTSpace(pContext, fvTangent, fvBiTangent, fMagS, fMagT, bIsOrientationPreserving, iFace, iVert) {
323
- // figure out which vertex it is
324
- const vertex_index = pContext.geometry_buffer_index[iFace * 3 + iVert];
325
-
326
- const tangent_destination = pContext.geometry_buffer_vertex_tangent;
327
- const tangent_address = vertex_index * 4;
328
-
329
- // for logic explanation, see https://github.com/gltf-rs/mikktspace/blob/6275cc4f15cff8be29819fb34ae8be3b9129dae1/src/lib.rs#L33
330
- tangent_destination[tangent_address] = fvTangent[0];
331
- tangent_destination[tangent_address + 1] = fvTangent[1];
332
- tangent_destination[tangent_address + 2] = fvTangent[2];
333
- tangent_destination[tangent_address + 3] = bIsOrientationPreserving ? 1 : -1;
334
- }
335
- }
336
-
337
118
  export class SMikkTSpaceContext {
338
119
  constructor() {
339
120
  /**
@@ -367,1844 +148,192 @@ export class SMikkTSpaceContext {
367
148
  * @type {number[]}
368
149
  */
369
150
  this.geometry_buffer_vertex_bitangent = [];
370
-
371
- /**
372
- *
373
- * @type {SMikkTSpaceInterface|null}
374
- */
375
- this.m_pInterface = new SMikkTSpaceInterface();
376
151
  }
377
152
  }
378
153
 
379
- const MARK_DEGENERATE = 1;
380
- const QUAD_ONE_DEGEN_TRI = 2;
381
- const GROUP_WITH_ANY = 4;
382
- const ORIENT_PRESERVING = 8;
154
+ // returns the position/normal/texcoord of the referenced face of vertex number iVert.
155
+ // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.
383
156
 
384
- const INTERNAL_RND_SORT_SEED = 39871946;
385
-
386
- /**
387
- *
388
- * @param {SVec3} v1
389
- * @param {SVec3} v2
390
- * @returns {boolean}
391
- */
392
- function veq(v1, v2) {
393
- return (v1.x === v2.x) && (v1.y === v2.y) && (v1.z === v2.z);
394
- }
395
157
 
396
158
  /**
397
159
  *
398
- * @param {SVec3} v1
399
- * @param {SVec3} v2
400
- * @returns {SVec3}
160
+ * @param {SMikkTSpaceContext} pContext
161
+ * @param {number} fAngularThreshold
162
+ * @return {boolean}
401
163
  */
402
- function vadd(v1, v2) {
403
- const res = new SVec3();
164
+ export function genTangSpace(pContext, fAngularThreshold = 180) {
165
+ // count nr_triangles
166
+ let iNrTSPaces = 0, iNrMaxGroups = 0;
167
+ let iNrActiveGroups = 0;
404
168
 
405
- res.x = v1.x + v2.x;
406
- res.y = v1.y + v2.y;
407
- res.z = v1.z + v2.z;
169
+ const iNrFaces = m_getNumFaces(pContext);
170
+ const fThresCos = Math.cos(fAngularThreshold * DEG_TO_RAD);
408
171
 
409
- return res;
410
- }
411
172
 
412
- /**
413
- *
414
- * @param {SVec3} v1
415
- * @param {SVec3} v2
416
- * @returns {SVec3}
417
- */
418
- function vsub(v1, v2) {
419
- const res = new SVec3();
173
+ // count triangles on supported faces
174
+ // AlexGoldring: since we only support triangles, the number of triangles is going to equal number of faces
175
+ let iNrTrianglesIn = iNrFaces;
420
176
 
421
- res.x = v1.x - v2.x;
422
- res.y = v1.y - v2.y;
423
- res.z = v1.z - v2.z;
177
+ if (iNrTrianglesIn <= 0) {
178
+ return false;
179
+ }
424
180
 
425
- return res;
426
- }
181
+ // allocate memory for an index list
182
+ const piTriListIn = new Int32Array(3 * iNrTrianglesIn);
183
+ const pTriInfos = malloc(STriInfo, iNrTrianglesIn);
427
184
 
428
- /**
429
- *
430
- * @param {number} fS
431
- * @param {SVec3} v
432
- * @returns {SVec3}
433
- */
434
- function vscale(fS, v) {
435
- const res = new SVec3();
185
+ // make an initial triangle --> face index list
186
+ iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
436
187
 
437
- res.x = v.x * fS;
438
- res.y = v.y * fS;
439
- res.z = v.z * fS;
188
+ // make a welded index list of identical positions and attributes (pos, norm, texc)
189
+ //printf("gen welded index list begin\n");
190
+ GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn);
191
+ //printf("gen welded index list end\n");
440
192
 
441
- return res;
442
- }
193
+ // Mark all degenerate triangles
194
+ const iTotTris = iNrTrianglesIn;
195
+ let iDegenTriangles = 0;
443
196
 
444
- /**
445
- *
446
- * @param {SVec3} v
447
- * @returns {number}
448
- */
449
- function LengthSquared(v) {
450
- return v.x * v.x + v.y * v.y + v.z * v.z;
451
- }
197
+ const p0 = vec3.create();
198
+ const p1 = vec3.create();
199
+ const p2 = vec3.create();
452
200
 
453
- /**
454
- *
455
- * @param {SVec3} v
456
- * @returns {number}
457
- */
458
- function Length(v) {
459
- return Math.sqrt(LengthSquared(v));
460
- }
201
+ for (let t = 0; t < iTotTris; t++) {
461
202
 
203
+ const t3 = t * 3;
462
204
 
463
- /**
464
- *
465
- * @param {SVec3} v
466
- * @returns {SVec3}
467
- */
468
- function Normalize(v) {
469
- return vscale(1 / Length(v), v);
470
- }
205
+ const i0 = piTriListIn[t3 ];
206
+ const i1 = piTriListIn[t3 + 1];
207
+ const i2 = piTriListIn[t3 + 2];
471
208
 
472
- /**
473
- *
474
- * @param {SVec3} v1
475
- * @param {SVec3} v2
476
- * @returns {number}
477
- */
478
- function vdot(v1, v2) {
479
- return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
480
- }
209
+ GetPosition(p0, 0, pContext, i0);
210
+ GetPosition(p1, 0, pContext, i1);
211
+ GetPosition(p2, 0, pContext, i2);
481
212
 
482
- /**
483
- *
484
- * @param {number} fX
485
- * @returns {boolean}
486
- */
487
- function NotZero(fX) {
488
- return Math.abs(fX) > EPSILON;
489
- }
213
+ if (
214
+ vec3.exactEquals(p0, p1) ||
215
+ vec3.exactEquals(p0, p2) ||
216
+ vec3.exactEquals(p1, p2)
217
+ ) {
218
+ // degenerate
219
+ pTriInfos[t].iFlag |= MARK_DEGENERATE;
220
+ ++iDegenTriangles;
221
+ }
222
+ }
490
223
 
491
- /**
492
- *
493
- * @param {SVec3} v
494
- * @returns {boolean}
495
- */
496
- function VNotZero(v) {
497
- return NotZero(v.x) || NotZero(v.y) || NotZero(v.z);
498
- }
224
+ iNrTrianglesIn = iTotTris - iDegenTriangles;
499
225
 
500
- /**
501
- *
502
- * @param {number} iFace
503
- * @param {number} iVert
504
- * @returns {number}
505
- */
506
- function MakeIndex(iFace, iVert) {
507
- return (iFace << 2) | (iVert & 0x3);
508
- }
226
+ // mark all triangle pairs that belong to a quad with only one
227
+ // good triangle. These need special treatment in DegenEpilogue().
228
+ // Additionally, move all good triangles to the start of
229
+ // pTriInfos[] and piTriListIn[] without changing order and
230
+ // put the degenerate triangles last.
231
+ DegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);
509
232
 
510
- /**
511
- *
512
- * @param {STSpace} ts_res
513
- * @param {STSpace} pTS0
514
- * @param {STSpace} pTS1
515
- * @returns {void}
516
- */
517
- function AvgTSpace(ts_res, pTS0, pTS1) {
518
233
 
519
- // this if is important. Due to floating point precision
520
- // averaging when ts0==ts1 will cause a slight difference
521
- // which results in tangent space splits later on
522
- if (pTS0.fMagS === pTS1.fMagS && pTS0.fMagT === pTS1.fMagT &&
523
- veq(pTS0.vOs, pTS1.vOs) && veq(pTS0.vOt, pTS1.vOt)) {
524
- ts_res.fMagS = pTS0.fMagS;
525
- ts_res.fMagT = pTS0.fMagT;
526
- ts_res.vOs = pTS0.vOs;
527
- ts_res.vOt = pTS0.vOt;
528
- } else {
529
- ts_res.fMagS = 0.5 * (pTS0.fMagS + pTS1.fMagS);
530
- ts_res.fMagT = 0.5 * (pTS0.fMagT + pTS1.fMagT);
531
- ts_res.vOs = vadd(pTS0.vOs, pTS1.vOs);
532
- ts_res.vOt = vadd(pTS0.vOt, pTS1.vOt);
533
- if (VNotZero(ts_res.vOs)) ts_res.vOs = Normalize(ts_res.vOs);
534
- if (VNotZero(ts_res.vOt)) ts_res.vOt = Normalize(ts_res.vOt);
535
- }
234
+ // evaluate triangle level attributes and neighbor list
235
+ //printf("gen neighbors list begin\n");
236
+ InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
237
+ //printf("gen neighbors list end\n");
536
238
 
537
- }
538
239
 
240
+ // based on the 4 rules, identify groups based on connectivity
241
+ iNrMaxGroups = iNrTrianglesIn * 3;
242
+ const pGroups = malloc(SGroup, iNrMaxGroups);
243
+ const piGroupTrianglesBuffer = new Int32Array(iNrTrianglesIn * 3);
539
244
 
540
- /**
541
- *
542
- * @param {SMikkTSpaceContext} pContext
543
- * @param {number} index
544
- * @returns {SVec3}
545
- */
546
- function GetPosition(pContext, index) {
547
- const res = new SVec3();
245
+ //printf("gen 4rule groups begin\n");
246
+ iNrActiveGroups = Build4RuleGroups(
247
+ pTriInfos,
248
+ pGroups,
249
+ piGroupTrianglesBuffer,
250
+ piTriListIn,
251
+ iNrTrianglesIn
252
+ );
253
+ //printf("gen 4rule groups end\n");
548
254
 
549
- // inlined : IndexToData(&iF, &iI, index);
550
- const iF = index & 0x3;
551
- const iI = index >> 2;
255
+ //
552
256
 
553
- pContext.m_pInterface.m_getPosition(pContext, res, iF, iI);
257
+ /**
258
+ *
259
+ * @type {STSpace[]}
260
+ */
261
+ const psTspace = malloc(STSpace, iNrTSPaces);
554
262
 
555
- return res;
556
- }
263
+ // memset not requried in JS
264
+ // memset(psTspace, 0, sizeof(STSpace)*iNrTSPaces);
557
265
 
558
- /**
559
- *
560
- * @param {SMikkTSpaceContext} pContext
561
- * @param {number} index
562
- * @returns {SVec3}
563
- */
564
- function GetNormal(pContext, index) {
565
- const res = new SVec3();
266
+ for (let t = 0; t < iNrTSPaces; t++) {
267
+ const stSpace = psTspace[t];
566
268
 
567
- // inlined : IndexToData(&iF, &iI, index);
568
- const iF = index & 0x3;
569
- const iI = index >> 2;
269
+ stSpace.vOs[0] = 1.0;
270
+ stSpace.vOs[1] = 0.0;
271
+ stSpace.vOs[2] = 0.0;
570
272
 
571
- pContext.m_pInterface.m_getNormal(pContext, res, iF, iI);
273
+ stSpace.fMagS = 1.0;
572
274
 
573
- return res;
574
- }
275
+ stSpace.vOt[0] = 0.0;
276
+ stSpace.vOt[1] = 1.0;
277
+ stSpace.vOt[2] = 0.0;
575
278
 
576
- /**
577
- * TODO doesn't need to be V3, can save memory
578
- * @param {SMikkTSpaceContext} pContext
579
- * @param {number} index
580
- * @returns {SVec3}
581
- */
582
- function GetTexCoord(pContext, index) {
279
+ stSpace.fMagT = 1.0;
280
+ }
583
281
 
584
- const res = new SVec3();
282
+ // make tspaces, each group is split up into subgroups if necessary
283
+ // based on fAngularThreshold. Finally, a tangent space is made for
284
+ // every resulting subgroup
285
+ //printf("gen tspaces begin\n");
286
+ GenerateTSpaces(
287
+ psTspace,
288
+ pTriInfos,
289
+ pGroups,
290
+ iNrActiveGroups,
291
+ piTriListIn,
292
+ fThresCos,
293
+ pContext
294
+ );
295
+ //printf("gen tspaces end\n");
585
296
 
586
- // inlined : IndexToData(&iF, &iI, index);
587
- const iF = index & 0x3;
588
- const iI = index >> 2;
589
297
 
590
- pContext.m_pInterface.m_getTexCoord(pContext, res, iF, iI);
298
+ // degenerate quads with one good triangle will be fixed by copying a space from
299
+ // the good triangle to the coinciding vertex.
300
+ // all other degenerate triangles will just copy a space from any good triangle
301
+ // with the same welded index in piTriListIn[].
302
+ DegenEpilogue(
303
+ psTspace,
304
+ pTriInfos,
305
+ piTriListIn,
306
+ pContext,
307
+ iNrTrianglesIn,
308
+ iTotTris
309
+ );
591
310
 
592
- res.z = 1;
593
311
 
594
- return res;
595
- }
312
+ for (let f = 0; f < iNrFaces; f++) {
596
313
 
597
- /**
598
- *
599
- * @param {SEdge[]} pSortBuffer
600
- * @param {number} iLeft
601
- * @param {number} iRight
602
- * @param {number} channel
603
- * @param {number} uSeed
604
- * @returns {void}
605
- */
606
- function QuickSortEdges(pSortBuffer, iLeft, iRight, channel, uSeed) {
607
- let t;
608
- let iL, iR, n, index, iMid;
314
+ // set data
315
+ for (let i = 0; i < 3; i++) {
316
+ const index = f * 3 + i
317
+ const pTSpace = psTspace[index];
609
318
 
610
- // early out
611
- let sTmp;
612
- const iElems = iRight - iLeft + 1;
613
- if (iElems < 2) return;
614
- else if (iElems === 2) {
615
- if (pSortBuffer[iLeft][channel] > pSortBuffer[iRight][channel]) {
616
- sTmp = pSortBuffer[iLeft];
617
- pSortBuffer[iLeft] = pSortBuffer[iRight];
618
- pSortBuffer[iRight] = sTmp;
319
+ const tangent = pTSpace.vOs;
320
+ const bitangent = pTSpace.vOt;
321
+
322
+ m_setTSpace(
323
+ pContext,
324
+ tangent,
325
+ bitangent,
326
+ pTSpace.fMagS,
327
+ pTSpace.fMagT,
328
+ pTSpace.bOrient,
329
+ f,
330
+ i
331
+ );
619
332
  }
620
- return;
621
333
  }
622
334
 
623
- // Random
624
- t = uSeed & 31;
625
- t = (uSeed << t) | (uSeed >> (32 - t));
626
- uSeed = uSeed + t + 3;
627
-
628
- uSeed = uSeed >>> 0; // make unsigned
629
- // Random end
630
-
631
- iL = iLeft;
632
- iR = iRight;
633
- n = (iR - iL) + 1;
634
- assert(n >= 0);
635
- index = (uSeed % n);
636
335
 
637
- iMid = pSortBuffer[index + iL][channel];
336
+ return true;
337
+ }
638
338
 
639
- do {
640
- while (pSortBuffer[iL][channel] < iMid)
641
- ++iL;
642
- while (pSortBuffer[iR][channel] > iMid)
643
- --iR;
644
339
 
645
- if (iL <= iR) {
646
- sTmp = pSortBuffer[iL];
647
- pSortBuffer[iL] = pSortBuffer[iR];
648
- pSortBuffer[iR] = sTmp;
649
- ++iL;
650
- --iR;
651
- }
652
- }
653
- while (iL <= iR);
654
-
655
- if (iLeft < iR)
656
- QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed);
657
- if (iL < iRight)
658
- QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed);
659
- }
660
-
661
- /**
662
- * resolve ordering and edge number
663
- * @param {SEdge} out
664
- * @param {number[]} indices
665
- * @param {number} indices_offset
666
- * @param {number} i0_in
667
- * @param {number} i1_in
668
- * @returns {void}
669
- */
670
- function GetEdge(out, indices, indices_offset, i0_in, i1_in) {
671
-
672
- // test if first index is on the edge
673
- const index_0 = indices[indices_offset + 0];
674
- const index_1 = indices[indices_offset + 1];
675
- const index_2 = indices[indices_offset + 2];
676
-
677
- if (index_0 === i0_in || index_0 === i1_in) {
678
- // test if second index is on the edge
679
- if (index_1 === i0_in || index_1 === i1_in) {
680
- out.f = 0; // first edge
681
- out.i0 = index_0;
682
- out.i1 = index_1;
683
- } else {
684
- out.f = 2; // third edge
685
- out.i0 = index_2;
686
- out.i1 = index_0;
687
- }
688
- } else {
689
- // only second and third index is on the edge
690
- out.f = 1; // second edge
691
- out.i0 = index_1;
692
- out.i1 = index_2;
693
- }
694
- }
695
-
696
- /**
697
- * returns the texture area times 2
698
- * @param {SMikkTSpaceContext } pContext
699
- * @param {number[]} indices
700
- * @param {number} indices_offset
701
- * @returns {number}
702
- */
703
- function CalcTexArea(pContext, indices, indices_offset) {
704
-
705
- const t1 = GetTexCoord(pContext, indices[indices_offset + 0]);
706
- const t2 = GetTexCoord(pContext, indices[indices_offset + 1]);
707
- const t3 = GetTexCoord(pContext, indices[indices_offset + 2]);
708
-
709
- const t21x = t2.x - t1.x;
710
- const t21y = t2.y - t1.y;
711
- const t31x = t3.x - t1.x;
712
- const t31y = t3.y - t1.y;
713
-
714
- const fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
715
-
716
- return fSignedAreaSTx2 < 0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2;
717
- }
718
-
719
- /**
720
- * Mimics C malloc somewhat, allocates an array of class instances of fixed size
721
- * @template T
722
- * @param {constructor<T>} Klass
723
- * @param {number} count
724
- * @returns {T[]}
725
- */
726
- function malloc(Klass, count) {
727
- const r = new Array(count);
728
-
729
- for (let i = 0; i < count; i++) {
730
- r[i] = new Klass();
731
- }
732
-
733
- return r;
734
- }
735
-
736
- /**
737
- *
738
- * @param {number[]} face_indices
739
- * @param {number} iFaces
740
- * @param {number[]} piTriListIn
741
- * @param {STriInfo[]} pTriInfos
742
- * @param {SMikkTSpaceContext} pContext
743
- * @param {number} iVertexRepresentitive
744
- * @returns {STSpace}
745
- */
746
- function EvalTspace(face_indices, iFaces, piTriListIn, pTriInfos, pContext, iVertexRepresentitive) {
747
- const res = new STSpace();
748
- let fAngleSum = 0;
749
- let face = 0;
750
- res.vOs.x = 0.0;
751
- res.vOs.y = 0.0;
752
- res.vOs.z = 0.0;
753
- res.vOt.x = 0.0;
754
- res.vOt.y = 0.0;
755
- res.vOt.z = 0.0;
756
- res.fMagS = 0;
757
- res.fMagT = 0;
758
-
759
- for (face = 0; face < iFaces; face++) {
760
- const f = face_indices[face];
761
-
762
- // only valid triangles get to add their contribution
763
- if ((pTriInfos[f].iFlag & GROUP_WITH_ANY) === 0) {
764
- /**
765
- * @type {SVec3}
766
- */
767
- let n, vOs, vOt, p0, p1, p2, v1, v2;
768
- /**
769
- * @type {number}
770
- */
771
- let fCos, fAngle, fMagS, fMagT;
772
- let i = -1, index = -1, i0 = -1, i1 = -1, i2 = -1;
773
-
774
- if (piTriListIn[3 * f + 0] === iVertexRepresentitive) i = 0;
775
- else if (piTriListIn[3 * f + 1] === iVertexRepresentitive) i = 1;
776
- else if (piTriListIn[3 * f + 2] === iVertexRepresentitive) i = 2;
777
- assert(i >= 0 && i < 3);
778
-
779
- // project
780
- index = piTriListIn[3 * f + i];
781
- n = GetNormal(pContext, index);
782
- vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n, pTriInfos[f].vOs), n));
783
- vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n, pTriInfos[f].vOt), n));
784
- if (VNotZero(vOs)) vOs = Normalize(vOs);
785
- if (VNotZero(vOt)) vOt = Normalize(vOt);
786
-
787
- i2 = piTriListIn[3 * f + (i < 2 ? (i + 1) : 0)];
788
- i1 = piTriListIn[3 * f + i];
789
- i0 = piTriListIn[3 * f + (i > 0 ? (i - 1) : 2)];
790
-
791
- p0 = GetPosition(pContext, i0);
792
- p1 = GetPosition(pContext, i1);
793
- p2 = GetPosition(pContext, i2);
794
- v1 = vsub(p0, p1);
795
- v2 = vsub(p2, p1);
796
-
797
- // project
798
- v1 = vsub(v1, vscale(vdot(n, v1), n));
799
- if (VNotZero(v1)) v1 = Normalize(v1);
800
- v2 = vsub(v2, vscale(vdot(n, v2), n));
801
- if (VNotZero(v2)) v2 = Normalize(v2);
802
-
803
- // weight contribution by the angle
804
- // between the two edge vectors
805
- fCos = vdot(v1, v2);
806
- fCos = fCos > 1 ? 1 : (fCos < (-1) ? (-1) : fCos);
807
- fAngle = Math.acos(fCos);
808
- fMagS = pTriInfos[f].fMagS;
809
- fMagT = pTriInfos[f].fMagT;
810
-
811
- res.vOs = vadd(res.vOs, vscale(fAngle, vOs));
812
- res.vOt = vadd(res.vOt, vscale(fAngle, vOt));
813
- res.fMagS += (fAngle * fMagS);
814
- res.fMagT += (fAngle * fMagT);
815
- fAngleSum += fAngle;
816
- }
817
- }
818
-
819
- // normalize
820
- if (VNotZero(res.vOs)) res.vOs = Normalize(res.vOs);
821
- if (VNotZero(res.vOt)) res.vOt = Normalize(res.vOt);
822
- if (fAngleSum > 0) {
823
- res.fMagS /= fAngleSum;
824
- res.fMagT /= fAngleSum;
825
- }
826
-
827
- return res;
828
- }
829
-
830
- /**
831
- *
832
- * @param {SSubGroup} pg1
833
- * @param {SSubGroup } pg2
834
- * @returns {boolean}
835
- */
836
- function CompareSubGroups(pg1, pg2) {
837
- let bStillSame = true;
838
- let i = 0;
839
- if (pg1.iNrFaces !== pg2.iNrFaces) return false;
840
- while (i < pg1.iNrFaces && bStillSame) {
841
- bStillSame = pg1.pTriMembers[i] === pg2.pTriMembers[i];
842
- if (bStillSame) ++i;
843
- }
844
- return bStillSame;
845
- }
846
-
847
-
848
- /**
849
- *
850
- * @param {number[]|ArrayLike<number>|Uint32Array|Int32Array} pSortBuffer
851
- * @param {number} iLeft
852
- * @param {number} iRight
853
- * @param {number} uSeed
854
- * @returns {void}
855
- */
856
- function QuickSort(pSortBuffer, iLeft, iRight, uSeed) {
857
- let iL, iR, n, index, iMid, iTmp;
858
-
859
- // Random
860
- let t = uSeed & 31;
861
- t = (uSeed << t) | (uSeed >> (32 - t));
862
- uSeed = uSeed + t + 3;
863
- uSeed = uSeed >>> 0; // make unsigned
864
- // Random end
865
-
866
- iL = iLeft;
867
- iR = iRight;
868
- n = (iR - iL) + 1;
869
- assert(n >= 0);
870
- index = (uSeed % n);
871
-
872
- iMid = pSortBuffer[index + iL];
873
-
874
-
875
- do {
876
- while (pSortBuffer[iL] < iMid)
877
- ++iL;
878
- while (pSortBuffer[iR] > iMid)
879
- --iR;
880
-
881
- if (iL <= iR) {
882
- iTmp = pSortBuffer[iL];
883
- pSortBuffer[iL] = pSortBuffer[iR];
884
- pSortBuffer[iR] = iTmp;
885
- ++iL;
886
- --iR;
887
- }
888
- }
889
- while (iL <= iR);
890
-
891
- if (iLeft < iR)
892
- QuickSort(pSortBuffer, iLeft, iR, uSeed);
893
- if (iL < iRight)
894
- QuickSort(pSortBuffer, iL, iRight, uSeed);
895
- }
896
-
897
- /**
898
- *
899
- * @param {STriInfo[]} pTriInfos
900
- * @param {SEdge[]} pEdges
901
- * @param {number[]} piTriListIn
902
- * @param {number} iNrTrianglesIn
903
- * @returns {void}
904
- */
905
- function BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn) {
906
- // build array of edges
907
- const uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed?
908
- let iEntries = 0, iCurStartIndex = -1, f = 0, i = 0;
909
- for (f = 0; f < iNrTrianglesIn; f++)
910
- for (i = 0; i < 3; i++) {
911
- const i0 = piTriListIn[f * 3 + i];
912
- const i1 = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)];
913
- pEdges[f * 3 + i].i0 = i0 < i1 ? i0 : i1; // put minimum index in i0
914
- pEdges[f * 3 + i].i1 = !(i0 < i1) ? i0 : i1; // put maximum index in i1
915
- pEdges[f * 3 + i].f = f; // record face number
916
- }
917
-
918
- // sort over all edges by i0, this is the pricy one.
919
- QuickSortEdges(pEdges, 0, iNrTrianglesIn * 3 - 1, 0, uSeed); // sort channel 0 which is i0
920
-
921
- // sub sort over i1, should be fast.
922
- // could replace this with a 64 bit int sort over (i0,i1)
923
- // with i0 as msb in the quicksort call above.
924
- iEntries = iNrTrianglesIn * 3;
925
- iCurStartIndex = 0;
926
- for (i = 1; i < iEntries; i++) {
927
- if (pEdges[iCurStartIndex].i0 !== pEdges[i].i0) {
928
- const iL = iCurStartIndex;
929
- const iR = i - 1;
930
- //const int iElems = i-iL;
931
- iCurStartIndex = i;
932
- QuickSortEdges(pEdges, iL, iR, 1, uSeed); // sort channel 1 which is i1
933
- }
934
- }
935
-
936
- // sub sort over f, which should be fast.
937
- // this step is to remain compliant with BuildNeighborsSlow() when
938
- // more than 2 triangles use the same edge (such as a butterfly topology).
939
- iCurStartIndex = 0;
940
- for (i = 1; i < iEntries; i++) {
941
- if (pEdges[iCurStartIndex].i0 !== pEdges[i].i0 || pEdges[iCurStartIndex].i1 !== pEdges[i].i1) {
942
- const iL = iCurStartIndex;
943
- const iR = i - 1;
944
- //const int iElems = i-iL;
945
- iCurStartIndex = i;
946
- QuickSortEdges(pEdges, iL, iR, 2, uSeed); // sort channel 2 which is f
947
- }
948
- }
949
-
950
- const temp_edge_A = new SEdge();
951
- const temp_edge_B = new SEdge();
952
-
953
- // pair up, adjacent triangles
954
- for (i = 0; i < iEntries; i++) {
955
- const i0 = pEdges[i].i0;
956
- const i1 = pEdges[i].i1;
957
- const f = pEdges[i].f;
958
- let bUnassigned_A = false;
959
-
960
- let i0_A, i1_A;
961
- let edgenum_A, edgenum_B = 0; // 0,1 or 2
962
-
963
- GetEdge(temp_edge_A, piTriListIn, f * 3, i0, i1); // resolve index ordering and edge_num
964
- i0_A = temp_edge_A.i0;
965
- i1_A = temp_edge_A.i1;
966
- edgenum_A = temp_edge_A.f;
967
-
968
- bUnassigned_A = pTriInfos[f].FaceNeighbors[edgenum_A] === -1;
969
-
970
- if (bUnassigned_A) {
971
- // get true index ordering
972
- let j = i + 1, t;
973
- let bNotFound = true;
974
- while (j < iEntries && i0 === pEdges[j].i0 && i1 === pEdges[j].i1 && bNotFound) {
975
- let bUnassigned_B = false;
976
- let i0_B, i1_B;
977
- t = pEdges[j].f;
978
- // flip i0_B and i1_B
979
- GetEdge(temp_edge_B, piTriListIn, t * 3, pEdges[j].i0, pEdges[j].i1); // resolve index ordering and edge_num
980
-
981
- i0_B = temp_edge_B.i0;
982
- i1_B = temp_edge_B.i1;
983
- edgenum_B = temp_edge_B.f;
984
-
985
- //assert(!(i0_A==i1_B && i1_A==i0_B));
986
- bUnassigned_B = pTriInfos[t].FaceNeighbors[edgenum_B] === -1;
987
- if (i0_A === i0_B && i1_A === i1_B && bUnassigned_B)
988
- bNotFound = false;
989
- else
990
- ++j;
991
- }
992
-
993
- if (!bNotFound) {
994
- let t = pEdges[j].f;
995
- pTriInfos[f].FaceNeighbors[edgenum_A] = t;
996
- //assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1);
997
- pTriInfos[t].FaceNeighbors[edgenum_B] = f;
998
- }
999
- }
1000
- }
1001
- }
1002
-
1003
- /**
1004
- *
1005
- * @param {STriInfo[]} pTriInfos
1006
- * @param {number} piTriListIn
1007
- * @param {number} iNrTrianglesIn
1008
- * @returns {void}
1009
- */
1010
- function BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn) {
1011
- let f = 0, i = 0;
1012
- for (f = 0; f < iNrTrianglesIn; f++) {
1013
- for (i = 0; i < 3; i++) {
1014
- // if unassigned
1015
- if (pTriInfos[f].FaceNeighbors[i] === -1) {
1016
- const i0_A = piTriListIn[f * 3 + i];
1017
- const i1_A = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)];
1018
-
1019
- // search for a neighbor
1020
- let bFound = false;
1021
- let t = 0, j = 0;
1022
- while (!bFound && t < iNrTrianglesIn) {
1023
- if (t !== f) {
1024
- j = 0;
1025
- while (!bFound && j < 3) {
1026
- // in rev order
1027
- const i1_B = piTriListIn[t * 3 + j];
1028
- const i0_B = piTriListIn[t * 3 + (j < 2 ? (j + 1) : 0)];
1029
- //assert(!(i0_A==i1_B && i1_A==i0_B));
1030
- if (i0_A === i0_B && i1_A === i1_B)
1031
- bFound = true;
1032
- else
1033
- ++j;
1034
- }
1035
- }
1036
-
1037
- if (!bFound) ++t;
1038
- }
1039
-
1040
- // assign neighbors
1041
- if (bFound) {
1042
- pTriInfos[f].FaceNeighbors[i] = t;
1043
- //assert(pTriInfos[t].FaceNeighbors[j]==-1);
1044
- pTriInfos[t].FaceNeighbors[j] = f;
1045
- }
1046
- }
1047
- }
1048
- }
1049
- }
1050
-
1051
- /**
1052
- *
1053
- * @param {STriInfo[]} pTriInfos
1054
- * @param {number[]|Int32Array} piTriListIn
1055
- * @param {SMikkTSpaceContext} pContext
1056
- * @param {number} iNrTrianglesIn
1057
- * @returns {void}
1058
- */
1059
- function InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn) {
1060
- let f = 0, i = 0, t = 0;
1061
- // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function.
1062
-
1063
- // generate neighbor info list
1064
- for (f = 0; f < iNrTrianglesIn; f++)
1065
- for (i = 0; i < 3; i++) {
1066
- pTriInfos[f].FaceNeighbors[i] = -1;
1067
- pTriInfos[f].AssignedGroup[i] = null;
1068
-
1069
- pTriInfos[f].vOs.x = 0.0;
1070
- pTriInfos[f].vOs.y = 0.0;
1071
- pTriInfos[f].vOs.z = 0.0;
1072
- pTriInfos[f].vOt.x = 0.0;
1073
- pTriInfos[f].vOt.y = 0.0;
1074
- pTriInfos[f].vOt.z = 0.0;
1075
- pTriInfos[f].fMagS = 0;
1076
- pTriInfos[f].fMagT = 0;
1077
-
1078
- // assumed bad
1079
- pTriInfos[f].iFlag |= GROUP_WITH_ANY;
1080
- }
1081
-
1082
- // evaluate first order derivatives
1083
- for (f = 0; f < iNrTrianglesIn; f++) {
1084
- // initial values
1085
- const v1 = GetPosition(pContext, piTriListIn[f * 3 + 0]);
1086
- const v2 = GetPosition(pContext, piTriListIn[f * 3 + 1]);
1087
- const v3 = GetPosition(pContext, piTriListIn[f * 3 + 2]);
1088
- const t1 = GetTexCoord(pContext, piTriListIn[f * 3 + 0]);
1089
- const t2 = GetTexCoord(pContext, piTriListIn[f * 3 + 1]);
1090
- const t3 = GetTexCoord(pContext, piTriListIn[f * 3 + 2]);
1091
-
1092
- const t21x = t2.x - t1.x;
1093
- const t21y = t2.y - t1.y;
1094
- const t31x = t3.x - t1.x;
1095
- const t31y = t3.y - t1.y;
1096
- const d1 = vsub(v2, v1);
1097
- const d2 = vsub(v3, v1);
1098
-
1099
- const fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
1100
- //assert(fSignedAreaSTx2!=0);
1101
- let vOs = vsub(vscale(t31y, d1), vscale(t21y, d2)); // eq 18
1102
- let vOt = vadd(vscale(-t31x, d1), vscale(t21x, d2)); // eq 19
1103
-
1104
- pTriInfos[f].iFlag |= (fSignedAreaSTx2 > 0 ? ORIENT_PRESERVING : 0);
1105
-
1106
- if (NotZero(fSignedAreaSTx2)) {
1107
- const fAbsArea = fabsf(fSignedAreaSTx2);
1108
- const fLenOs = Length(vOs);
1109
- const fLenOt = Length(vOt);
1110
- const fS = (pTriInfos[f].iFlag & ORIENT_PRESERVING) === 0 ? (-1.0) : 1.0;
1111
- if (NotZero(fLenOs)) pTriInfos[f].vOs = vscale(fS / fLenOs, vOs);
1112
- if (NotZero(fLenOt)) pTriInfos[f].vOt = vscale(fS / fLenOt, vOt);
1113
-
1114
- // evaluate magnitudes prior to normalization of vOs and vOt
1115
- pTriInfos[f].fMagS = fLenOs / fAbsArea;
1116
- pTriInfos[f].fMagT = fLenOt / fAbsArea;
1117
-
1118
- // if this is a good triangle
1119
- if (NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT))
1120
- pTriInfos[f].iFlag &= (~GROUP_WITH_ANY);
1121
- }
1122
- }
1123
-
1124
- // force otherwise healthy quads to a fixed orientation
1125
- while (t < (iNrTrianglesIn - 1)) {
1126
- const iFO_a = pTriInfos[t].iOrgFaceNumber;
1127
- const iFO_b = pTriInfos[t + 1].iOrgFaceNumber;
1128
- if (iFO_a === iFO_b) // this is a quad
1129
- {
1130
- const bIsDeg_a = (pTriInfos[t].iFlag & MARK_DEGENERATE) !== 0;
1131
- const bIsDeg_b = (pTriInfos[t + 1].iFlag & MARK_DEGENERATE) !== 0;
1132
-
1133
- // bad triangles should already have been removed by
1134
- // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false
1135
- if ((bIsDeg_a || bIsDeg_b) === false) {
1136
- const bOrientA = (pTriInfos[t].iFlag & ORIENT_PRESERVING) !== 0;
1137
- const bOrientB = (pTriInfos[t + 1].iFlag & ORIENT_PRESERVING) !== 0;
1138
- // if this happens the quad has extremely bad mapping!!
1139
- if (bOrientA !== bOrientB) {
1140
- //printf("found quad with bad mapping\n");
1141
- let bChooseOrientFirstTri = false;
1142
- if ((pTriInfos[t + 1].iFlag & GROUP_WITH_ANY) !== 0) bChooseOrientFirstTri = true;
1143
- else if (CalcTexArea(pContext, piTriListIn, t * 3 + 0) >= CalcTexArea(pContext, piTriListIn, (t + 1) * 3 + 0))
1144
- bChooseOrientFirstTri = true;
1145
-
1146
- // force match
1147
- {
1148
- const t0 = bChooseOrientFirstTri ? t : (t + 1);
1149
- const t1 = bChooseOrientFirstTri ? (t + 1) : t;
1150
- pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first
1151
- pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag & ORIENT_PRESERVING); // copy bit
1152
- }
1153
- }
1154
- }
1155
- t += 2;
1156
- } else
1157
- ++t;
1158
- }
1159
-
1160
- // match up edge pairs
1161
- {
1162
- const pEdges = malloc(SEdge, iNrTrianglesIn * 3);
1163
- BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn);
1164
- }
1165
- }
1166
-
1167
-
1168
- /**
1169
- *
1170
- * @param {SMikkTSpaceContext} pContext
1171
- * @param {number} fAngularThreshold
1172
- * @return {boolean}
1173
- */
1174
- export function genTangSpace(pContext, fAngularThreshold = 180) {
1175
- // count nr_triangles
1176
- let iNrTrianglesIn = 0, f = 0, t = 0, i = 0;
1177
- let iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0;
1178
- let iNrActiveGroups = 0, index = 0;
1179
- const iNrFaces = pContext.m_pInterface.m_getNumFaces(pContext);
1180
- let bRes = false;
1181
- const fThresCos = Math.cos((fAngularThreshold * Math.PI) / 180.0);
1182
-
1183
- // verify all call-backs have been set
1184
- if (pContext.m_pInterface.m_getNumFaces === null ||
1185
- pContext.m_pInterface.m_getNumVerticesOfFace === null ||
1186
- pContext.m_pInterface.m_getPosition === null ||
1187
- pContext.m_pInterface.m_getNormal === null ||
1188
- pContext.m_pInterface.m_getTexCoord === null)
1189
- return false;
1190
-
1191
- // count triangles on supported faces
1192
- for (f = 0; f < iNrFaces; f++) {
1193
- const verts = pContext.m_pInterface.m_getNumVerticesOfFace(pContext, f);
1194
- if (verts === 3) ++iNrTrianglesIn;
1195
- else if (verts === 4) iNrTrianglesIn += 2;
1196
- }
1197
- if (iNrTrianglesIn <= 0) return false;
1198
-
1199
- // allocate memory for an index list
1200
- const piTriListIn = new Int32Array(3 * iNrTrianglesIn);
1201
- const pTriInfos = malloc(STriInfo, iNrTrianglesIn);
1202
-
1203
- // make an initial triangle -. face index list
1204
- iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
1205
-
1206
- // make a welded index list of identical positions and attributes (pos, norm, texc)
1207
- //printf("gen welded index list begin\n");
1208
- GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn);
1209
- //printf("gen welded index list end\n");
1210
-
1211
- // Mark all degenerate triangles
1212
- iTotTris = iNrTrianglesIn;
1213
- iDegenTriangles = 0;
1214
- for (t = 0; t < iTotTris; t++) {
1215
- const i0 = piTriListIn[t * 3 + 0];
1216
- const i1 = piTriListIn[t * 3 + 1];
1217
- const i2 = piTriListIn[t * 3 + 2];
1218
- const p0 = GetPosition(pContext, i0);
1219
- const p1 = GetPosition(pContext, i1);
1220
- const p2 = GetPosition(pContext, i2);
1221
- if (veq(p0, p1) || veq(p0, p2) || veq(p1, p2)) // degenerate
1222
- {
1223
- pTriInfos[t].iFlag |= MARK_DEGENERATE;
1224
- ++iDegenTriangles;
1225
- }
1226
- }
1227
- iNrTrianglesIn = iTotTris - iDegenTriangles;
1228
-
1229
- // mark all triangle pairs that belong to a quad with only one
1230
- // good triangle. These need special treatment in DegenEpilogue().
1231
- // Additionally, move all good triangles to the start of
1232
- // pTriInfos[] and piTriListIn[] without changing order and
1233
- // put the degenerate triangles last.
1234
- DegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);
1235
-
1236
-
1237
- // evaluate triangle level attributes and neighbor list
1238
- //printf("gen neighbors list begin\n");
1239
- InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
1240
- //printf("gen neighbors list end\n");
1241
-
1242
-
1243
- // based on the 4 rules, identify groups based on connectivity
1244
- iNrMaxGroups = iNrTrianglesIn * 3;
1245
- const pGroups = malloc(SGroup, iNrMaxGroups);
1246
- const piGroupTrianglesBuffer = new Int32Array(iNrTrianglesIn * 3);
1247
-
1248
- //printf("gen 4rule groups begin\n");
1249
- iNrActiveGroups =
1250
- Build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn);
1251
- //printf("gen 4rule groups end\n");
1252
-
1253
- //
1254
-
1255
- /**
1256
- *
1257
- * @type {STSpace[]}
1258
- */
1259
- const psTspace = malloc(STSpace, iNrTSPaces);
1260
-
1261
- // memset not requried in JS
1262
- // memset(psTspace, 0, sizeof(STSpace)*iNrTSPaces);
1263
-
1264
- for (t = 0; t < iNrTSPaces; t++) {
1265
- psTspace[t].vOs.x = 1.0;
1266
- psTspace[t].vOs.y = 0.0;
1267
- psTspace[t].vOs.z = 0.0;
1268
- psTspace[t].fMagS = 1.0;
1269
- psTspace[t].vOt.x = 0.0;
1270
- psTspace[t].vOt.y = 1.0;
1271
- psTspace[t].vOt.z = 0.0;
1272
- psTspace[t].fMagT = 1.0;
1273
- }
1274
-
1275
- // make tspaces, each group is split up into subgroups if necessary
1276
- // based on fAngularThreshold. Finally a tangent space is made for
1277
- // every resulting subgroup
1278
- //printf("gen tspaces begin\n");
1279
- bRes = GenerateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext);
1280
- //printf("gen tspaces end\n");
1281
-
1282
- if (!bRes) // if an allocation in GenerateTSpaces() failed
1283
- {
1284
- // clean up and return false
1285
- return false;
1286
- }
1287
-
1288
-
1289
- // degenerate quads with one good triangle will be fixed by copying a space from
1290
- // the good triangle to the coinciding vertex.
1291
- // all other degenerate triangles will just copy a space from any good triangle
1292
- // with the same welded index in piTriListIn[].
1293
- DegenEpilogue(psTspace, pTriInfos, piTriListIn, pContext, iNrTrianglesIn, iTotTris);
1294
-
1295
- index = 0;
1296
- for (f = 0; f < iNrFaces; f++) {
1297
- const verts = pContext.m_pInterface.m_getNumVerticesOfFace(pContext, f);
1298
- if (verts !== 3 && verts !== 4) continue;
1299
-
1300
-
1301
- // I've decided to let degenerate triangles and group-with-anythings
1302
- // vary between left/right hand coordinate systems at the vertices.
1303
- // All healthy triangles on the other hand are built to always be either or.
1304
-
1305
- /*// force the coordinate system orientation to be uniform for every face.
1306
- // (this is already the case for good triangles but not for
1307
- // degenerate ones and those with bGroupWithAnything==true)
1308
- bool bOrient = psTspace[index].bOrient;
1309
- if (psTspace[index].iCounter == 0) // tspace was not derived from a group
1310
- {
1311
- // look for a space created in GenerateTSpaces() by iCounter>0
1312
- bool bNotFound = true;
1313
- int i=1;
1314
- while (i<verts && bNotFound)
1315
- {
1316
- if (psTspace[index+i].iCounter > 0) bNotFound=false;
1317
- else ++i;
1318
- }
1319
- if (!bNotFound) bOrient = psTspace[index+i].bOrient;
1320
- }*/
1321
-
1322
- // set data
1323
- for (i = 0; i < verts; i++) {
1324
- const pTSpace = psTspace[index];
1325
- const tang = [pTSpace.vOs.x, pTSpace.vOs.y, pTSpace.vOs.z];
1326
- const bitang = [pTSpace.vOt.x, pTSpace.vOt.y, pTSpace.vOt.z];
1327
-
1328
- pContext.m_pInterface.m_setTSpace(pContext, tang, bitang, pTSpace.fMagS, pTSpace.fMagT, pTSpace.bOrient, f, i);
1329
-
1330
- ++index;
1331
- }
1332
- }
1333
-
1334
-
1335
- return true;
1336
- }
1337
-
1338
- class STmpVert {
1339
- constructor() {
1340
- this.vert = [0, 0, 0];
1341
- this.index = 0;
1342
- }
1343
- }
1344
-
1345
- /**
1346
- *
1347
- * @type {number}
1348
- */
1349
- const g_iCells = 2048;
1350
-
1351
-
1352
- /**
1353
- *
1354
- * @param {number} fMin
1355
- * @param {number} fMax
1356
- * @param {number} fVal
1357
- * @return {number}
1358
- */
1359
- function FindGridCell(fMin, fMax, fVal) {
1360
- const fIndex = g_iCells * ((fVal - fMin) / (fMax - fMin));
1361
- const iIndex = fIndex >> 0;
1362
- return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1);
1363
- }
1364
-
1365
- /**
1366
- *
1367
- * @param {number[]|Int32Array} piTriList_in_and_out
1368
- * @param {SMikkTSpaceContext} pContext
1369
- * @param {number} iNrTrianglesIn
1370
- * @returns {void}
1371
- */
1372
- function GenerateSharedVerticesIndexList(piTriList_in_and_out, pContext, iNrTrianglesIn) {
1373
-
1374
- // Generate bounding box
1375
- let piHashCount = null, piHashOffsets = null, piHashCount2 = null;
1376
- let pTmpVert = null;
1377
- let i = 0, iChannel = 0, k = 0, e = 0;
1378
- let iMaxCount = 0;
1379
- let vMin = GetPosition(pContext, 0), vMax = vMin, vDim;
1380
- let fMin, fMax;
1381
- for (i = 1; i < (iNrTrianglesIn * 3); i++) {
1382
- const index = piTriList_in_and_out[i];
1383
-
1384
- const vP = GetPosition(pContext, index);
1385
- if (vMin.x > vP.x) vMin.x = vP.x;
1386
- else if (vMax.x < vP.x) vMax.x = vP.x;
1387
- if (vMin.y > vP.y) vMin.y = vP.y;
1388
- else if (vMax.y < vP.y) vMax.y = vP.y;
1389
- if (vMin.z > vP.z) vMin.z = vP.z;
1390
- else if (vMax.z < vP.z) vMax.z = vP.z;
1391
- }
1392
-
1393
- vDim = vsub(vMax, vMin);
1394
- iChannel = 0;
1395
- fMin = vMin.x;
1396
- fMax = vMax.x;
1397
- if (vDim.y > vDim.x && vDim.y > vDim.z) {
1398
- iChannel = 1;
1399
- fMin = vMin.y;
1400
- fMax = vMax.y;
1401
- } else if (vDim.z > vDim.x) {
1402
- iChannel = 2;
1403
- fMin = vMin.z;
1404
- fMax = vMax.z;
1405
- }
1406
-
1407
- // make allocations
1408
- const piHashTable = new Int32Array(iNrTrianglesIn * 3);
1409
- piHashCount = new Int32Array(g_iCells);
1410
- piHashOffsets = new Int32Array(g_iCells);
1411
- piHashCount2 = new Int32Array(g_iCells);
1412
-
1413
- // memset is not needed in JS, as arrays are initialized with 0 per spec
1414
- // memset(piHashCount, 0, sizeof(int)*g_iCells);
1415
- // memset(piHashCount2, 0, sizeof(int)*g_iCells);
1416
-
1417
- // count amount of elements in each cell unit
1418
- for (i = 0; i < (iNrTrianglesIn * 3); i++) {
1419
- const index = piTriList_in_and_out[i];
1420
- const vP = GetPosition(pContext, index);
1421
- const fVal = iChannel === 0 ? vP.x : (iChannel === 1 ? vP.y : vP.z);
1422
- const iCell = FindGridCell(fMin, fMax, fVal);
1423
- ++piHashCount[iCell];
1424
- }
1425
-
1426
- // evaluate start index of each cell.
1427
- piHashOffsets[0] = 0;
1428
- for (k = 1; k < g_iCells; k++)
1429
- piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1];
1430
-
1431
- // insert vertices
1432
- for (i = 0; i < (iNrTrianglesIn * 3); i++) {
1433
- const index = piTriList_in_and_out[i];
1434
- const vP = GetPosition(pContext, index);
1435
- const fVal = iChannel === 0 ? vP.x : (iChannel === 1 ? vP.y : vP.z);
1436
- const iCell = FindGridCell(fMin, fMax, fVal);
1437
-
1438
- assert(piHashCount2[iCell] < piHashCount[iCell]);
1439
- const table_offset = piHashOffsets[iCell] + piHashCount2[iCell];
1440
- piHashTable[table_offset] = i; // vertex i has been inserted.
1441
- ++piHashCount2[iCell];
1442
- }
1443
- for (k = 0; k < g_iCells; k++)
1444
- assert(piHashCount2[k] === piHashCount[k]); // verify the count
1445
-
1446
- // find maximum amount of entries in any hash entry
1447
- iMaxCount = piHashCount[0];
1448
- for (k = 1; k < g_iCells; k++)
1449
- if (iMaxCount < piHashCount[k])
1450
- iMaxCount = piHashCount[k];
1451
- pTmpVert = malloc(STmpVert, iMaxCount);
1452
-
1453
-
1454
- // complete the merge
1455
- for (k = 0; k < g_iCells; k++) {
1456
- // extract table of cell k and amount of entries in it
1457
- const pTable = piHashTable[piHashOffsets[k]];
1458
- const iEntries = piHashCount[k];
1459
- if (iEntries < 2) continue;
1460
-
1461
- for (e = 0; e < iEntries; e++) {
1462
- const i = pTable[e];
1463
- const vP = GetPosition(pContext, piTriList_in_and_out[i]);
1464
- pTmpVert[e].vert[0] = vP.x;
1465
- pTmpVert[e].vert[1] = vP.y;
1466
- pTmpVert[e].vert[2] = vP.z;
1467
- pTmpVert[e].index = i;
1468
- }
1469
- MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, 0, iEntries - 1);
1470
- }
1471
-
1472
- }
1473
-
1474
- /**
1475
- *
1476
- * @param {number[]} piTriList_in_and_out
1477
- * @param {STmpVert[]} pTmpVert
1478
- * @param {SMikkTSpaceContext} pContext
1479
- * @param {number} iL_in
1480
- * @param {number} iR_in
1481
- * @returns {void}
1482
- */
1483
- function MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL_in, iR_in) {
1484
- // make bbox
1485
- let c = 0, l = 0, channel = 0;
1486
- let fvMin = [], fvMax = [];
1487
- let dx = 0, dy = 0, dz = 0, fSep = 0;
1488
- for (c = 0; c < 3; c++) {
1489
- fvMin[c] = pTmpVert[iL_in].vert[c];
1490
- fvMax[c] = fvMin[c];
1491
- }
1492
- for (l = (iL_in + 1); l <= iR_in; l++) {
1493
- for (c = 0; c < 3; c++) {
1494
- if (fvMin[c] > pTmpVert[l].vert[c]) fvMin[c] = pTmpVert[l].vert[c];
1495
- if (fvMax[c] < pTmpVert[l].vert[c]) fvMax[c] = pTmpVert[l].vert[c];
1496
- }
1497
- }
1498
-
1499
- dx = fvMax[0] - fvMin[0];
1500
- dy = fvMax[1] - fvMin[1];
1501
- dz = fvMax[2] - fvMin[2];
1502
-
1503
- channel = 0;
1504
- if (dy > dx && dy > dz) channel = 1;
1505
- else if (dz > dx) channel = 2;
1506
-
1507
- fSep = 0.5 * (fvMax[channel] + fvMin[channel]);
1508
-
1509
- // stop if all vertices are NaNs
1510
- if (!Number.isFinite(fSep))
1511
- return;
1512
-
1513
- // terminate recursion when the separation/average value
1514
- // is no longer strictly between fMin and fMax values.
1515
- if (fSep >= fvMax[channel] || fSep <= fvMin[channel]) {
1516
- // complete the weld
1517
- for (l = iL_in; l <= iR_in; l++) {
1518
- let i = pTmpVert[l].index;
1519
- const index = piTriList_in_and_out[i];
1520
- const vP = GetPosition(pContext, index);
1521
- const vN = GetNormal(pContext, index);
1522
- const vT = GetTexCoord(pContext, index);
1523
-
1524
- let bNotFound = true;
1525
- let l2 = iL_in, i2rec = -1;
1526
- while (l2 < l && bNotFound) {
1527
- const i2 = pTmpVert[l2].index;
1528
- const index2 = piTriList_in_and_out[i2];
1529
- const vP2 = GetPosition(pContext, index2);
1530
- const vN2 = GetNormal(pContext, index2);
1531
- const vT2 = GetTexCoord(pContext, index2);
1532
- i2rec = i2;
1533
-
1534
- //if (vP==vP2 && vN==vN2 && vT==vT2)
1535
- if (vP.x === vP2.x && vP.y === vP2.y && vP.z == vP2.z &&
1536
- vN.x === vN2.x && vN.y === vN2.y && vN.z === vN2.z &&
1537
- vT.x === vT2.x && vT.y === vT2.y && vT.z === vT2.z)
1538
- bNotFound = false;
1539
- else
1540
- ++l2;
1541
- }
1542
-
1543
- // merge if previously found
1544
- if (!bNotFound)
1545
- piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
1546
- }
1547
- } else {
1548
- let iL = iL_in, iR = iR_in;
1549
- assert((iR_in - iL_in) > 0); // at least 2 entries
1550
-
1551
- // separate (by fSep) all points between iL_in and iR_in in pTmpVert[]
1552
- while (iL < iR) {
1553
- let bReadyLeftSwap = false, bReadyRightSwap = false;
1554
- while ((!bReadyLeftSwap) && iL < iR) {
1555
- assert(iL >= iL_in && iL <= iR_in);
1556
- bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep);
1557
- if (!bReadyLeftSwap) ++iL;
1558
- }
1559
- while ((!bReadyRightSwap) && iL < iR) {
1560
- assert(iR >= iL_in && iR <= iR_in);
1561
- bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep;
1562
- if (!bReadyRightSwap) --iR;
1563
- }
1564
- assert((iL < iR) || !(bReadyLeftSwap && bReadyRightSwap));
1565
-
1566
- if (bReadyLeftSwap && bReadyRightSwap) {
1567
- const sTmp = pTmpVert[iL];
1568
- assert(iL < iR);
1569
- pTmpVert[iL] = pTmpVert[iR];
1570
- pTmpVert[iR] = sTmp;
1571
- ++iL;
1572
- --iR;
1573
- }
1574
- }
1575
-
1576
- assert(iL === (iR + 1) || (iL === iR));
1577
- if (iL === iR) {
1578
- const bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep;
1579
- if (bReadyRightSwap) ++iL;
1580
- else --iR;
1581
- }
1582
-
1583
- // only need to weld when there is more than 1 instance of the (x,y,z)
1584
- if (iL_in < iR)
1585
- MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL_in, iR); // weld all left of fSep
1586
- if (iL < iR_in)
1587
- MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL, iR_in); // weld all right of (or equal to) fSep
1588
- }
1589
- }
1590
-
1591
- /**
1592
- *
1593
- * @param {STriInfo[]} pTriInfos
1594
- * @param {number[]|Int32Array} piTriList_out
1595
- * @param {SMikkTSpaceContext } pContext
1596
- * @param {number} iNrTrianglesIn
1597
- * @return {number}
1598
- */
1599
- function GenerateInitialVerticesIndexList(pTriInfos, piTriList_out, pContext, iNrTrianglesIn) {
1600
- let iTSpacesOffs = 0, f = 0, t = 0;
1601
- let iDstTriIndex = 0;
1602
- for (f = 0; f < pContext.m_pInterface.m_getNumFaces(pContext); f++) {
1603
- const verts = pContext.m_pInterface.m_getNumVerticesOfFace(pContext, f);
1604
- if (verts !== 3 && verts !== 4) continue;
1605
-
1606
- pTriInfos[iDstTriIndex].iOrgFaceNumber = f;
1607
- pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs;
1608
-
1609
- if (verts === 3) {
1610
- const pVerts = pTriInfos[iDstTriIndex].vert_num;
1611
- pVerts[0] = 0;
1612
- pVerts[1] = 1;
1613
- pVerts[2] = 2;
1614
- piTriList_out[iDstTriIndex * 3 + 0] = MakeIndex(f, 0);
1615
- piTriList_out[iDstTriIndex * 3 + 1] = MakeIndex(f, 1);
1616
- piTriList_out[iDstTriIndex * 3 + 2] = MakeIndex(f, 2);
1617
- ++iDstTriIndex; // next
1618
- } else {
1619
- {
1620
- pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f;
1621
- pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs;
1622
- }
1623
-
1624
- {
1625
- // need an order independent way to evaluate
1626
- // tspace on quads. This is done by splitting
1627
- // along the shortest diagonal.
1628
- const i0 = MakeIndex(f, 0);
1629
- const i1 = MakeIndex(f, 1);
1630
- const i2 = MakeIndex(f, 2);
1631
- const i3 = MakeIndex(f, 3);
1632
- const T0 = GetTexCoord(pContext, i0);
1633
- const T1 = GetTexCoord(pContext, i1);
1634
- const T2 = GetTexCoord(pContext, i2);
1635
- const T3 = GetTexCoord(pContext, i3);
1636
- const distSQ_02 = LengthSquared(vsub(T2, T0));
1637
- const distSQ_13 = LengthSquared(vsub(T3, T1));
1638
- let bQuadDiagIs_02 = false;
1639
- if (distSQ_02 < distSQ_13)
1640
- bQuadDiagIs_02 = true;
1641
- else if (distSQ_13 < distSQ_02)
1642
- bQuadDiagIs_02 = false;
1643
- else {
1644
- const P0 = GetPosition(pContext, i0);
1645
- const P1 = GetPosition(pContext, i1);
1646
- const P2 = GetPosition(pContext, i2);
1647
- const P3 = GetPosition(pContext, i3);
1648
- const distSQ_02 = LengthSquared(vsub(P2, P0));
1649
- const distSQ_13 = LengthSquared(vsub(P3, P1));
1650
-
1651
- bQuadDiagIs_02 = distSQ_13 >= distSQ_02;
1652
- }
1653
-
1654
- if (bQuadDiagIs_02) {
1655
- {
1656
- const pVerts_A = pTriInfos[iDstTriIndex].vert_num;
1657
- pVerts_A[0] = 0;
1658
- pVerts_A[1] = 1;
1659
- pVerts_A[2] = 2;
1660
- }
1661
- piTriList_out[iDstTriIndex * 3 + 0] = i0;
1662
- piTriList_out[iDstTriIndex * 3 + 1] = i1;
1663
- piTriList_out[iDstTriIndex * 3 + 2] = i2;
1664
- ++iDstTriIndex; // next
1665
- {
1666
- const pVerts_B = pTriInfos[iDstTriIndex].vert_num;
1667
- pVerts_B[0] = 0;
1668
- pVerts_B[1] = 2;
1669
- pVerts_B[2] = 3;
1670
- }
1671
- piTriList_out[iDstTriIndex * 3 + 0] = i0;
1672
- piTriList_out[iDstTriIndex * 3 + 1] = i2;
1673
- piTriList_out[iDstTriIndex * 3 + 2] = i3;
1674
- ++iDstTriIndex; // next
1675
- } else {
1676
- {
1677
- const pVerts_A = pTriInfos[iDstTriIndex].vert_num;
1678
- pVerts_A[0] = 0;
1679
- pVerts_A[1] = 1;
1680
- pVerts_A[2] = 3;
1681
- }
1682
- piTriList_out[iDstTriIndex * 3 + 0] = i0;
1683
- piTriList_out[iDstTriIndex * 3 + 1] = i1;
1684
- piTriList_out[iDstTriIndex * 3 + 2] = i3;
1685
- ++iDstTriIndex; // next
1686
- {
1687
- const pVerts_B = pTriInfos[iDstTriIndex].vert_num;
1688
- pVerts_B[0] = 1;
1689
- pVerts_B[1] = 2;
1690
- pVerts_B[2] = 3;
1691
- }
1692
- piTriList_out[iDstTriIndex * 3 + 0] = i1;
1693
- piTriList_out[iDstTriIndex * 3 + 1] = i2;
1694
- piTriList_out[iDstTriIndex * 3 + 2] = i3;
1695
- ++iDstTriIndex; // next
1696
- }
1697
- }
1698
- }
1699
-
1700
- iTSpacesOffs += verts;
1701
- assert(iDstTriIndex <= iNrTrianglesIn);
1702
- }
1703
-
1704
- for (t = 0; t < iNrTrianglesIn; t++)
1705
- pTriInfos[t].iFlag = 0;
1706
-
1707
- // return total amount of tspaces
1708
- return iTSpacesOffs;
1709
- }
1710
-
1711
- /**
1712
- *
1713
- * @param {STriInfo[]} pTriInfos
1714
- * @param {SGroup[]} pGroups
1715
- * @param {number[]|Int32Array} piGroupTrianglesBuffer
1716
- * @param {number[]|Int32Array} piTriListIn
1717
- * @param {number} iNrTrianglesIn
1718
- * @return {number}
1719
- */
1720
- function Build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn) {
1721
- const iNrMaxGroups = iNrTrianglesIn * 3;
1722
- let iNrActiveGroups = 0;
1723
- let iOffset = 0, f = 0, i = 0;
1724
-
1725
- for (; f < iNrTrianglesIn; f++) {
1726
- const tri_info_f = pTriInfos[f];
1727
-
1728
- for (i = 0; i < 3; i++) {
1729
- // if not assigned to a group
1730
- if ((tri_info_f.iFlag & GROUP_WITH_ANY) === 0 && tri_info_f.AssignedGroup[i] == null) {
1731
- let neigh_indexL, neigh_indexR;
1732
- const vert_index = piTriListIn[f * 3 + i];
1733
-
1734
- assert(iNrActiveGroups < iNrMaxGroups);
1735
-
1736
- const assigned_group = pGroups[iNrActiveGroups];
1737
- tri_info_f.AssignedGroup[i] = assigned_group;
1738
-
1739
- assigned_group.iVertexRepresentitive = vert_index;
1740
-
1741
- const bOrPre = (tri_info_f.iFlag & ORIENT_PRESERVING) !== 0;
1742
-
1743
- assigned_group.bOrientPreservering = bOrPre;
1744
- assigned_group.iNrFaces = 0;
1745
- assigned_group.pFaceIndices = new Int32Array(piGroupTrianglesBuffer.buffer, iOffset * 4);
1746
- ++iNrActiveGroups;
1747
-
1748
- AddTriToGroup(assigned_group, f);
1749
-
1750
- neigh_indexL = tri_info_f.FaceNeighbors[i];
1751
- neigh_indexR = tri_info_f.FaceNeighbors[i > 0 ? (i - 1) : 2];
1752
-
1753
- if (neigh_indexL >= 0) // neighbor
1754
- {
1755
- const bAnswer = AssignRecur(
1756
- piTriListIn,
1757
- pTriInfos,
1758
- neigh_indexL,
1759
- assigned_group
1760
- );
1761
-
1762
- const bOrPre2 = (pTriInfos[neigh_indexL].iFlag & ORIENT_PRESERVING) !== 0;
1763
- const bDiff = bOrPre !== bOrPre2;
1764
- assert(bAnswer || bDiff);
1765
- }
1766
- if (neigh_indexR >= 0) // neighbor
1767
- {
1768
- const bAnswer = AssignRecur(
1769
- piTriListIn,
1770
- pTriInfos,
1771
- neigh_indexR,
1772
- assigned_group
1773
- );
1774
-
1775
- const bOrPre2 = (pTriInfos[neigh_indexR].iFlag & ORIENT_PRESERVING) !== 0;
1776
- const bDiff = bOrPre !== bOrPre2;
1777
- assert(bAnswer || bDiff);
1778
- }
1779
-
1780
- // update offset
1781
- iOffset += assigned_group.iNrFaces;
1782
- // since the groups are disjoint a triangle can never
1783
- // belong to more than 3 groups. Subsequently something
1784
- // is completely screwed if this assertion ever hits.
1785
- assert(iOffset <= iNrMaxGroups);
1786
- }
1787
- }
1788
- }
1789
-
1790
- return iNrActiveGroups;
1791
- }
1792
-
1793
- /**
1794
- *
1795
- * @param {SGroup} pGroup
1796
- * @param {number} iTriIndex
1797
- * @returns {void}
1798
- */
1799
- function AddTriToGroup(pGroup, iTriIndex) {
1800
- pGroup.pFaceIndices[pGroup.iNrFaces] = iTriIndex;
1801
- pGroup.iNrFaces++;
1802
- }
1803
-
1804
- /**
1805
- *
1806
- * @param {number[]} piTriListIn
1807
- * @param {STriInfo[]} psTriInfos
1808
- * @param {number} iMyTriIndex
1809
- * @param {SGroup} pGroup
1810
- * @return {boolean}
1811
- */
1812
- function AssignRecur(piTriListIn, psTriInfos, iMyTriIndex, pGroup) {
1813
-
1814
- // track down vertex
1815
- const iVertRep = pGroup.iVertexRepresentitive;
1816
- const pVerts = piTriListIn[iMyTriIndex * 3];
1817
-
1818
- let i = -1;
1819
- if (pVerts[0] === iVertRep) i = 0;
1820
- else if (pVerts[1] === iVertRep) i = 1;
1821
- else if (pVerts[2] === iVertRep) i = 2;
1822
-
1823
- assert(i >= 0 && i < 3);
1824
-
1825
- const pMyTriInfo = psTriInfos[iMyTriIndex];
1826
-
1827
- // early out
1828
- if (pMyTriInfo.AssignedGroup[i] === pGroup) {
1829
- return true;
1830
- } else if (pMyTriInfo.AssignedGroup[i] !== null) {
1831
- return false;
1832
- }
1833
-
1834
- if ((pMyTriInfo.iFlag & GROUP_WITH_ANY) !== 0) {
1835
- // first to group with a group-with-anything triangle
1836
- // determines it's orientation.
1837
- // This is the only existing order dependency in the code!!
1838
- if (
1839
- pMyTriInfo.AssignedGroup[0] === null &&
1840
- pMyTriInfo.AssignedGroup[1] === null &&
1841
- pMyTriInfo.AssignedGroup[2] === null
1842
- ) {
1843
- pMyTriInfo.iFlag &= (~ORIENT_PRESERVING);
1844
- pMyTriInfo.iFlag |= (pGroup.bOrientPreservering ? ORIENT_PRESERVING : 0);
1845
- }
1846
- }
1847
- {
1848
- const bOrient = (pMyTriInfo.iFlag & ORIENT_PRESERVING) !== 0;
1849
- if (bOrient !== pGroup.bOrientPreservering) return false;
1850
- }
1851
-
1852
- AddTriToGroup(pGroup, iMyTriIndex);
1853
- pMyTriInfo.AssignedGroup[i] = pGroup;
1854
-
1855
- {
1856
- const neigh_indexL = pMyTriInfo.FaceNeighbors[i];
1857
- const neigh_indexR = pMyTriInfo.FaceNeighbors[i > 0 ? (i - 1) : 2];
1858
- if (neigh_indexL >= 0)
1859
- AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup);
1860
- if (neigh_indexR >= 0)
1861
- AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup);
1862
- }
1863
-
1864
-
1865
- return true;
1866
- }
1867
-
1868
- /**
1869
- *
1870
- * @param {STSpace[]} psTspace
1871
- * @param {STriInfo[]} pTriInfos
1872
- * @param {SGroup[]} pGroups
1873
- * @param {number} iNrActiveGroups
1874
- * @param {number[]|Int32Array} piTriListIn
1875
- * @param {number} fThresCos
1876
- * @param {SMikkTSpaceContext} pContext
1877
- * @returns {boolean}
1878
- */
1879
- function GenerateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext) {
1880
-
1881
- let iMaxNrFaces = 0, iUniqueTspaces = 0, g = 0, i = 0;
1882
-
1883
- for (g = 0; g < iNrActiveGroups; g++) {
1884
- if (iMaxNrFaces < pGroups[g].iNrFaces) {
1885
- iMaxNrFaces = pGroups[g].iNrFaces;
1886
- }
1887
- }
1888
-
1889
- if (iMaxNrFaces === 0) {
1890
- return true;
1891
- }
1892
-
1893
-
1894
- // make initial allocations
1895
- /**
1896
- * @type {STSpace[]}
1897
- */
1898
- let pSubGroupTspace = malloc(STSpace, iMaxNrFaces);
1899
- /**
1900
- *
1901
- * @type {SSubGroup[]}
1902
- */
1903
- let pUniSubGroups = malloc(SSubGroup, iMaxNrFaces);
1904
- /**
1905
- *
1906
- * @type {Int32Array|null}
1907
- */
1908
- let pTmpMembers = new Int32Array(iMaxNrFaces);
1909
-
1910
- iUniqueTspaces = 0;
1911
-
1912
- for (g = 0; g < iNrActiveGroups; g++) {
1913
- const pGroup = pGroups[g];
1914
- let iUniqueSubGroups = 0, s = 0;
1915
-
1916
- for (i = 0; i < pGroup.iNrFaces; i++) // triangles
1917
- {
1918
- const f = pGroup.pFaceIndices[i]; // triangle number
1919
- let index = -1, iVertIndex = -1, iOF_1 = -1, iMembers = 0, j = 0, l = 0;
1920
- /**
1921
- * @type {SSubGroup}
1922
- */
1923
- const tmp_group = new SSubGroup();
1924
- let bFound = false;
1925
- /**
1926
- * @type {SVec3}
1927
- */
1928
- let n, vOs, vOt;
1929
-
1930
- if (pTriInfos[f].AssignedGroup[0] === pGroup) index = 0;
1931
- else if (pTriInfos[f].AssignedGroup[1] === pGroup) index = 1;
1932
- else if (pTriInfos[f].AssignedGroup[2] === pGroup) index = 2;
1933
- assert(index >= 0 && index < 3);
1934
-
1935
- iVertIndex = piTriListIn[f * 3 + index];
1936
- assert(iVertIndex === pGroup.iVertexRepresentitive);
1937
-
1938
- // is normalized already
1939
- n = GetNormal(pContext, iVertIndex);
1940
-
1941
- // project
1942
- vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n, pTriInfos[f].vOs), n));
1943
- vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n, pTriInfos[f].vOt), n));
1944
- if (VNotZero(vOs)) vOs = Normalize(vOs);
1945
- if (VNotZero(vOt)) vOt = Normalize(vOt);
1946
-
1947
- // original face number
1948
- iOF_1 = pTriInfos[f].iOrgFaceNumber;
1949
-
1950
- iMembers = 0;
1951
- for (j = 0; j < pGroup.iNrFaces; j++) {
1952
- const t = pGroup.pFaceIndices[j]; // triangle number
1953
- const iOF_2 = pTriInfos[t].iOrgFaceNumber;
1954
-
1955
- // project
1956
- let vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n, pTriInfos[t].vOs), n));
1957
- let vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n, pTriInfos[t].vOt), n));
1958
- if (VNotZero(vOs2)) vOs2 = Normalize(vOs2);
1959
- if (VNotZero(vOt2)) vOt2 = Normalize(vOt2);
1960
-
1961
- {
1962
- const bAny = ((pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY) !== 0;
1963
- // make sure triangles which belong to the same quad are joined.
1964
- const bSameOrgFace = iOF_1 === iOF_2;
1965
-
1966
- const fCosS = vdot(vOs, vOs2);
1967
- const fCosT = vdot(vOt, vOt2);
1968
-
1969
- assert(f !== t || bSameOrgFace); // sanity check
1970
- if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos))
1971
- pTmpMembers[iMembers++] = t;
1972
- }
1973
- }
1974
-
1975
- // sort pTmpMembers
1976
- tmp_group.iNrFaces = iMembers;
1977
- tmp_group.pTriMembers = pTmpMembers;
1978
- if (iMembers > 1) {
1979
- const uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed?
1980
- QuickSort(pTmpMembers, 0, iMembers - 1, uSeed);
1981
- }
1982
-
1983
- // look for an existing match
1984
- bFound = false;
1985
- l = 0;
1986
- while (l < iUniqueSubGroups && !bFound) {
1987
- bFound = CompareSubGroups(tmp_group, pUniSubGroups[l]);
1988
- if (!bFound) ++l;
1989
- }
1990
-
1991
- // assign tangent space index
1992
- assert(bFound || l === iUniqueSubGroups);
1993
- //piTempTangIndices[f*3+index] = iUniqueTspaces+l;
1994
-
1995
- // if no match was found we allocate a new subgroup
1996
- if (!bFound) {
1997
- // insert new subgroup
1998
- const pIndices = new Int32Array(iMembers);
1999
-
2000
- pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers;
2001
- pUniSubGroups[iUniqueSubGroups].pTriMembers = pIndices;
2002
-
2003
- array_copy(tmp_group.pTriMembers, 0, pIndices, 0, iMembers);
2004
-
2005
- pSubGroupTspace[iUniqueSubGroups] = EvalTspace(
2006
- tmp_group.pTriMembers,
2007
- iMembers,
2008
- piTriListIn,
2009
- pTriInfos,
2010
- pContext,
2011
- pGroup.iVertexRepresentitive
2012
- );
2013
-
2014
- ++iUniqueSubGroups;
2015
- }
2016
-
2017
- // output tspace
2018
- {
2019
- const iOffs = pTriInfos[f].iTSpacesOffs;
2020
- const iVert = pTriInfos[f].vert_num[index];
2021
- /**
2022
- * @type {STSpace}
2023
- */
2024
- const pTS_out = psTspace[iOffs + iVert];
2025
- assert(pTS_out.iCounter < 2);
2026
- assert(((pTriInfos[f].iFlag & ORIENT_PRESERVING) !== 0) === pGroup.bOrientPreservering);
2027
- if (pTS_out.iCounter === 1) {
2028
- AvgTSpace(pTS_out, pTS_out, pSubGroupTspace[l]);
2029
- pTS_out.iCounter = 2; // update counter
2030
- pTS_out.bOrient = pGroup.bOrientPreservering;
2031
- } else {
2032
- assert(pTS_out.iCounter === 0);
2033
- pTS_out.copy(pSubGroupTspace[l]);
2034
- pTS_out.iCounter = 1; // update counter
2035
- pTS_out.bOrient = pGroup.bOrientPreservering;
2036
- }
2037
- }
2038
- }
2039
-
2040
- // clean up and offset iUniqueTspaces
2041
-
2042
- iUniqueTspaces += iUniqueSubGroups;
2043
- }
2044
-
2045
- return true;
2046
- }
2047
-
2048
-
2049
- /**
2050
- *
2051
- * @param {STriInfo[]} pTriInfos
2052
- * @param {number[]|Int32Array} piTriList_out
2053
- * @param {number} iNrTrianglesIn
2054
- * @param {number} iTotTris
2055
- * @returns {void}
2056
- */
2057
- function DegenPrologue(pTriInfos, piTriList_out, iNrTrianglesIn, iTotTris) {
2058
- let iNextGoodTriangleSearchIndex = -1;
2059
- let bStillFindingGoodOnes = false;
2060
-
2061
- // locate quads with only one good triangle
2062
- let t = 0;
2063
- while (t < (iTotTris - 1)) {
2064
- const iFO_a = pTriInfos[t].iOrgFaceNumber;
2065
- const iFO_b = pTriInfos[t + 1].iOrgFaceNumber;
2066
- if (iFO_a === iFO_b) // this is a quad
2067
- {
2068
- const bIsDeg_a = (pTriInfos[t].iFlag & MARK_DEGENERATE) !== 0;
2069
- const bIsDeg_b = (pTriInfos[t + 1].iFlag & MARK_DEGENERATE) !== 0;
2070
- if ((bIsDeg_a ^ bIsDeg_b) !== 0) {
2071
- pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI;
2072
- pTriInfos[t + 1].iFlag |= QUAD_ONE_DEGEN_TRI;
2073
- }
2074
- t += 2;
2075
- } else
2076
- ++t;
2077
- }
2078
-
2079
- // reorder list so all degen triangles are moved to the back
2080
- // without reordering the good triangles
2081
- iNextGoodTriangleSearchIndex = 1;
2082
- t = 0;
2083
- bStillFindingGoodOnes = true;
2084
- while (t < iNrTrianglesIn && bStillFindingGoodOnes) {
2085
- const bIsGood = (pTriInfos[t].iFlag & MARK_DEGENERATE) === 0;
2086
- if (bIsGood) {
2087
- if (iNextGoodTriangleSearchIndex < (t + 2))
2088
- iNextGoodTriangleSearchIndex = t + 2;
2089
- } else {
2090
- let t0, t1;
2091
- // search for the first good triangle.
2092
- let bJustADegenerate = true;
2093
- while (bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris) {
2094
- const bIsGood = (pTriInfos[iNextGoodTriangleSearchIndex].iFlag & MARK_DEGENERATE) === 0;
2095
- if (bIsGood) bJustADegenerate = false;
2096
- else ++iNextGoodTriangleSearchIndex;
2097
- }
2098
-
2099
- t0 = t;
2100
- t1 = iNextGoodTriangleSearchIndex;
2101
- ++iNextGoodTriangleSearchIndex;
2102
- assert(iNextGoodTriangleSearchIndex > (t + 1));
2103
-
2104
- // swap triangle t0 and t1
2105
- if (!bJustADegenerate) {
2106
- let i = 0;
2107
- for (; i < 3; i++) {
2108
- const index = piTriList_out[t0 * 3 + i];
2109
- piTriList_out[t0 * 3 + i] = piTriList_out[t1 * 3 + i];
2110
- piTriList_out[t1 * 3 + i] = index;
2111
- }
2112
- {
2113
- const tri_info = pTriInfos[t0];
2114
- pTriInfos[t0] = pTriInfos[t1];
2115
- pTriInfos[t1] = tri_info;
2116
- }
2117
- } else
2118
- bStillFindingGoodOnes = false; // this is not supposed to happen
2119
- }
2120
-
2121
- if (bStillFindingGoodOnes) ++t;
2122
- }
2123
-
2124
- assert(bStillFindingGoodOnes); // code will still work.
2125
- assert(iNrTrianglesIn === t);
2126
- }
2127
-
2128
- /**
2129
- *
2130
- * @param {STSpace[]} psTspace
2131
- * @param {STriInfo[]} pTriInfos
2132
- * @param {number[]|Int32Array} piTriListIn
2133
- * @param {SMikkTSpaceContext} pContext
2134
- * @param {number} iNrTrianglesIn
2135
- * @param {number} iTotTris
2136
- * @returns {void}
2137
- */
2138
- function DegenEpilogue(psTspace, pTriInfos, piTriListIn, pContext, iNrTrianglesIn, iTotTris) {
2139
- let t = 0, i = 0;
2140
-
2141
- // deal with degenerate triangles
2142
- // punishment for degenerate triangles is O(N^2)
2143
- for (t = iNrTrianglesIn; t < iTotTris; t++) {
2144
- // degenerate triangles on a quad with one good triangle are skipped
2145
- // here but processed in the next loop
2146
- const bSkip = (pTriInfos[t].iFlag & QUAD_ONE_DEGEN_TRI) !== 0;
2147
-
2148
- if (!bSkip) {
2149
- for (i = 0; i < 3; i++) {
2150
- const index1 = piTriListIn[t * 3 + i];
2151
- // search through the good triangles
2152
- let bNotFound = true;
2153
- let j = 0;
2154
- while (bNotFound && j < (3 * iNrTrianglesIn)) {
2155
- const index2 = piTriListIn[j];
2156
- if (index1 === index2) bNotFound = false;
2157
- else ++j;
2158
- }
2159
-
2160
- if (!bNotFound) {
2161
- const iTri = j / 3;
2162
- const iVert = j % 3;
2163
- const iSrcVert = pTriInfos[iTri].vert_num[iVert];
2164
- const iSrcOffs = pTriInfos[iTri].iTSpacesOffs;
2165
- const iDstVert = pTriInfos[t].vert_num[i];
2166
- const iDstOffs = pTriInfos[t].iTSpacesOffs;
2167
-
2168
- // copy tspace
2169
- psTspace[iDstOffs + iDstVert] = psTspace[iSrcOffs + iSrcVert];
2170
- }
2171
- }
2172
- }
2173
- }
2174
-
2175
- // deal with degenerate quads with one good triangle
2176
- for (t = 0; t < iNrTrianglesIn; t++) {
2177
- // this triangle belongs to a quad where the
2178
- // other triangle is degenerate
2179
- if ((pTriInfos[t].iFlag & QUAD_ONE_DEGEN_TRI) !== 0) {
2180
- /**
2181
- * @type {SVec3}
2182
- */
2183
- let vDstP;
2184
- let iOrgF = -1, i = 0;
2185
- let bNotFound;
2186
- const pV = pTriInfos[t].vert_num;
2187
- const iFlag = (1 << pV[0]) | (1 << pV[1]) | (1 << pV[2]);
2188
- let iMissingIndex = 0;
2189
- if ((iFlag & 2) === 0) iMissingIndex = 1;
2190
- else if ((iFlag & 4) === 0) iMissingIndex = 2;
2191
- else if ((iFlag & 8) === 0) iMissingIndex = 3;
2192
-
2193
- iOrgF = pTriInfos[t].iOrgFaceNumber;
2194
- vDstP = GetPosition(pContext, MakeIndex(iOrgF, iMissingIndex));
2195
- bNotFound = true;
2196
- i = 0;
2197
- while (bNotFound && i < 3) {
2198
- const iVert = pV[i];
2199
- const vSrcP = GetPosition(pContext, MakeIndex(iOrgF, iVert));
2200
- if (veq(vSrcP, vDstP) === true) {
2201
- const iOffs = pTriInfos[t].iTSpacesOffs;
2202
- psTspace[iOffs + iMissingIndex] = psTspace[iOffs + iVert];
2203
- bNotFound = false;
2204
- } else
2205
- ++i;
2206
- }
2207
- assert(!bNotFound);
2208
- }
2209
- }
2210
- }