@woosh/meep-engine 2.162.0 → 2.163.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.
package/README.md CHANGED
@@ -82,7 +82,7 @@ export default defineConfig({
82
82
  ```
83
83
 
84
84
  ### Reliability
85
- * **Tests:** 7,244 tests with 90%+ core coverage.
85
+ * **Tests:** 7,554 tests with 90%+ core coverage.
86
86
 
87
87
  * **Stability:** Exhaustively tested corner cases over a decade of development.
88
88
 
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "2.162.0",
9
+ "version": "2.163.1",
10
10
  "main": "build/meep.module.js",
11
11
  "module": "build/meep.module.js",
12
12
  "exports": {
@@ -3,8 +3,8 @@
3
3
  * Assumes linear RGB space
4
4
  * Assumes D65 standard illuminant
5
5
  * @see https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
6
- * @param {vec3} out
7
- * @param {vec3} input
6
+ * @param {number[]|Float32Array|Float64Array} out
7
+ * @param {number[]|Float32Array|Float64Array} input
8
8
  */
9
- export function rgb_to_xyz(out: vec3, input: vec3): void;
9
+ export function rgb_to_xyz(out: number[] | Float32Array | Float64Array, input: number[] | Float32Array | Float64Array): void;
10
10
  //# sourceMappingURL=rgb_to_xyz.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rgb_to_xyz.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/xyz/rgb_to_xyz.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,yDASC"}
1
+ {"version":3,"file":"rgb_to_xyz.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/xyz/rgb_to_xyz.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,gCAHW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,SAClC,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,QAW5C"}
@@ -3,8 +3,8 @@
3
3
  * Assumes linear RGB space
4
4
  * Assumes D65 standard illuminant
5
5
  * @see https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
6
- * @param {vec3} out
7
- * @param {vec3} input
6
+ * @param {number[]|Float32Array|Float64Array} out
7
+ * @param {number[]|Float32Array|Float64Array} input
8
8
  */
9
9
  export function rgb_to_xyz(out, input) {
10
10
 
@@ -2,8 +2,8 @@
2
2
  * CIE color model to linear RGB
3
3
  * Assumes D65 standard illuminant
4
4
  * @see https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
5
- * @param {vec3|number[]|Float32Array} out
6
- * @param {vec3|number[]|Float32Array} input
5
+ * @param {number[]|Float32Array|Float64Array} out
6
+ * @param {number[]|Float32Array|Float64Array} input
7
7
  */
8
- export function xyz_to_rgb(out: vec3 | number[] | Float32Array, input: vec3 | number[] | Float32Array): void;
8
+ export function xyz_to_rgb(out: number[] | Float32Array | Float64Array, input: number[] | Float32Array | Float64Array): void;
9
9
  //# sourceMappingURL=xyz_to_rgb.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"xyz_to_rgb.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/xyz/xyz_to_rgb.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,gCAHW,OAAK,MAAM,EAAE,GAAC,YAAY,SAC1B,OAAK,MAAM,EAAE,GAAC,YAAY,QAUpC"}
1
+ {"version":3,"file":"xyz_to_rgb.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/xyz/xyz_to_rgb.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,gCAHW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,SAClC,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,QAU5C"}
@@ -2,8 +2,8 @@
2
2
  * CIE color model to linear RGB
3
3
  * Assumes D65 standard illuminant
4
4
  * @see https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
5
- * @param {vec3|number[]|Float32Array} out
6
- * @param {vec3|number[]|Float32Array} input
5
+ * @param {number[]|Float32Array|Float64Array} out
6
+ * @param {number[]|Float32Array|Float64Array} input
7
7
  */
8
8
  export function xyz_to_rgb(out, input) {
9
9
  const x = input[0];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Scale a 3d vector by a scalar, writing the result into `output`.
3
+ *
4
+ * @param {number[]|Float32Array} output
5
+ * @param {number} output_offset
6
+ * @param {number[]|Float32Array} input
7
+ * @param {number} input_offset
8
+ * @param {number} scalar
9
+ */
10
+ export function v3_array_scale(output: number[] | Float32Array, output_offset: number, input: number[] | Float32Array, input_offset: number, scalar: number): void;
11
+ //# sourceMappingURL=v3_array_scale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_array_scale.d.ts","sourceRoot":"","sources":["../../../../../src/core/geom/vec3/v3_array_scale.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,uCANW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,SACN,MAAM,EAAE,GAAC,YAAY,gBACrB,MAAM,UACN,MAAM,QAUhB"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Scale a 3d vector by a scalar, writing the result into `output`.
3
+ *
4
+ * @param {number[]|Float32Array} output
5
+ * @param {number} output_offset
6
+ * @param {number[]|Float32Array} input
7
+ * @param {number} input_offset
8
+ * @param {number} scalar
9
+ */
10
+ export function v3_array_scale(
11
+ output, output_offset,
12
+ input, input_offset,
13
+ scalar,
14
+ ) {
15
+ output[output_offset] = input[input_offset] * scalar;
16
+ output[output_offset + 1] = input[input_offset + 1] * scalar;
17
+ output[output_offset + 2] = input[input_offset + 2] * scalar;
18
+ }
@@ -270,11 +270,11 @@ export class EngineHarness {
270
270
  */
271
271
  static async buildBasics({
272
272
  engine,
273
- focus = new Vector3(10, 0, 10),
273
+ focus = new Vector3(0, 0, 0),
274
274
  heightMap,
275
275
  heightRange,
276
276
  pitch = 0.7,
277
- yaw = -1.2,
277
+ yaw = 0,
278
278
  distance = 10,
279
279
  terrainSize = new Vector2(10, 10),
280
280
  terrainResolution = 10,
@@ -287,7 +287,7 @@ export class EngineHarness {
287
287
  cameraController = true,
288
288
  cameraAutoClip = true,
289
289
  shadowmapResolution,
290
- showFps = true
290
+ showFps = false
291
291
  }) {
292
292
 
293
293
  if (showFps) {
@@ -1,202 +1,218 @@
1
- import { BufferGeometry, Vector3 } from "three";
2
- import { assert } from "../../../../../../core/assert.js";
3
- import { v3_angle_cos_between } from "../../../../../../core/geom/vec3/v3_angle_cos_between.js";
4
- import { v3_length } from "../../../../../../core/geom/vec3/v3_length.js";
5
- import { CapType } from "../CapType.js";
6
- import { append_compute_cap_geometry_size, make_cap } from "./make_cap.js";
7
- import { make_ring_faces } from "./make_ring_faces.js";
8
- import { make_ring_vertices } from "./make_ring_vertices.js";
9
- import { StreamGeometryBuilder } from "./StreamGeometryBuilder.js";
10
-
11
-
12
- const v4_array = new Float32Array(4);
13
-
14
- /**
15
- * @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
16
- * @see https://github.com/hofk/THREEg.js/blob/488f1128a25321a76888aa1fa19db64750318444/THREEg.js#L3483
17
- * @param {Float32Array|number[]} in_positions
18
- * @param {Vector3[]} in_normals
19
- * @param {Vector3[]} in_binormals
20
- * @param {Vector3[]} in_tangents
21
- * @param {number[]} shape
22
- * @param {number[]|Float32Array} shape_normal
23
- * @param {number} shape_length
24
- * @param {number[]|Float32Array} shape_transform
25
- * @param {boolean} [closed]
26
- * @param {CapType} [cap_type]
27
- * @returns {BufferGeometry}
28
- */
29
- export function makeTubeGeometry(
30
- in_positions, in_normals, in_binormals, in_tangents,
31
- shape, shape_normal, shape_length, shape_transform, closed = false, cap_type = CapType.Round
32
- ) {
33
- assert.enum(cap_type, CapType, 'cap_type');
34
- assert.isBoolean(closed, 'closed');
35
-
36
- assert.isNumber(shape_length, 'shape_length');
37
- assert.isArrayLike(shape, 'shape');
38
-
39
- const out = new StreamGeometryBuilder();
40
-
41
- // helper variables
42
-
43
- const point_count = in_positions.length / 3;
44
- const tubular_segments = point_count - 1;
45
-
46
-
47
- const geometry_size = {
48
- vertex_count: (tubular_segments + 1) * (shape_length + 1),
49
- polygon_count: tubular_segments * shape_length * 2
50
- };
51
-
52
- if (!closed) {
53
- append_compute_cap_geometry_size(2, geometry_size, shape_length, cap_type);
54
- }
55
-
56
- out.allocate(
57
- geometry_size.vertex_count,
58
- geometry_size.polygon_count
59
- );
60
-
61
- // create buffer data
62
-
63
- if (!closed) {
64
- // start cap
65
- make_cap(
66
- out, 0,
67
- in_positions, in_normals, in_binormals, in_tangents,
68
- shape, shape_normal, shape_length, shape_transform, 1, cap_type
69
- );
70
- }
71
-
72
- const index_offset = out.cursor_vertices;
73
-
74
- for (let i = 0; i < tubular_segments; i++) {
75
-
76
- generateSegment(i);
77
-
78
- }
79
-
80
- // if the geometry is not closed, generate the last row of vertices and normals
81
- // at the regular position on the given path
82
- //
83
- // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)
84
-
85
- generateSegment((closed === false) ? tubular_segments : 0);
86
-
87
- // finally create faces
88
- make_ring_faces(out, index_offset, tubular_segments, shape_length);
89
-
90
- if (!closed) {
91
- // end cap
92
- make_cap(
93
- out, point_count - 1,
94
- in_positions, in_normals, in_binormals, in_tangents,
95
- shape, shape_normal, shape_length, shape_transform, -1, cap_type
96
- );
97
- }
98
-
99
- /**
100
- *
101
- * @param {number} i
102
- */
103
- function generateSegment(i) {
104
-
105
- // we use getPointAt to sample evenly distributed points from the given path
106
-
107
- const i3 = i * 3;
108
-
109
- const Px = in_positions[i3];
110
- const Py = in_positions[i3 + 1];
111
- const Pz = in_positions[i3 + 2];
112
-
113
- // retrieve corresponding normal and binormal
114
-
115
- const N = in_normals[i];
116
- const B = in_binormals[i];
117
-
118
- // generate normals and vertices for the current segment
119
- compute_bend_normal(v4_array, i, tubular_segments, in_positions);
120
-
121
- make_ring_vertices(
122
- out,
123
- Px, Py, Pz,
124
- N, B, in_tangents[i],
125
- i / tubular_segments, v4_array,
126
- shape, shape_normal, shape_length, shape_transform
127
- );
128
-
129
- }
130
-
131
-
132
- return out.build();
133
- }
134
-
135
-
136
- /**
137
- *
138
- * @param {number[]|Float32Array} out
139
- * @param {number} index
140
- * @param {number} index_count
141
- * @param {number[]|Float32Array} positions
142
- */
143
- function compute_bend_normal(
144
- out,
145
- index,
146
- index_count,
147
- positions
148
- ) {
149
- if (index <= 0 || index >= index_count - 1) {
150
- // end points, no bending
151
-
152
- out[0] = 0;
153
- out[1] = 1;
154
- out[2] = 0;
155
- out[3] = 0;
156
-
157
- return;
158
- }
159
-
160
- const index_next = index + 1;
161
- const index_prev = index - 1;
162
-
163
- const address_current = index * 3;
164
- const address_next = index_next * 3;
165
- const address_prev = index_prev * 3;
166
-
167
- const i0_x = positions[address_prev];
168
- const i0_y = positions[address_prev + 1];
169
- const i0_z = positions[address_prev + 2];
170
-
171
- const i1_x = positions[address_current];
172
- const i1_y = positions[address_current + 1];
173
- const i1_z = positions[address_current + 2];
174
-
175
- const i2_x = positions[address_next];
176
- const i2_y = positions[address_next + 1];
177
- const i2_z = positions[address_next + 2];
178
-
179
- const d0_x = i0_x - i1_x;
180
- const d0_y = i0_y - i1_y;
181
- const d0_z = i0_z - i1_z;
182
-
183
- const d1_x = i1_x - i2_x;
184
- const d1_y = i1_y - i2_y;
185
- const d1_z = i1_z - i2_z;
186
-
187
- // compute rotation axis
188
- const cross_x = d0_y * d1_z - d0_z * d1_y;
189
- const cross_y = d0_z * d1_x - d0_x * d1_z;
190
- const cross_z = d0_x * d1_y - d0_y * d1_x;
191
-
192
- const angle = v3_angle_cos_between(d0_x, d0_y, d0_z, d1_x, d1_y, d1_z);
193
-
194
- const length_inv = 1 / v3_length(cross_x, cross_y, cross_z);
195
-
196
- out[0] = cross_x * length_inv;
197
- out[1] = cross_y * length_inv;
198
- out[2] = cross_z * length_inv;
199
-
200
- // bend amount
201
- out[3] = (1 - Math.abs(angle)) * 0.5;
202
- }
1
+ import { BufferGeometry, Vector3 } from "three";
2
+ import { assert } from "../../../../../../core/assert.js";
3
+ import { v3_angle_cos_between } from "../../../../../../core/geom/vec3/v3_angle_cos_between.js";
4
+ import { v3_length } from "../../../../../../core/geom/vec3/v3_length.js";
5
+ import { CapType } from "../CapType.js";
6
+ import { append_compute_cap_geometry_size, make_cap } from "./make_cap.js";
7
+ import { make_ring_faces } from "./make_ring_faces.js";
8
+ import { make_ring_vertices } from "./make_ring_vertices.js";
9
+ import { StreamGeometryBuilder } from "./StreamGeometryBuilder.js";
10
+
11
+
12
+ const v4_array = new Float32Array(4);
13
+
14
+ /**
15
+ * @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
16
+ * @see https://github.com/hofk/THREEg.js/blob/488f1128a25321a76888aa1fa19db64750318444/THREEg.js#L3483
17
+ * @param {Float32Array|number[]} in_positions
18
+ * @param {Vector3[]} in_normals
19
+ * @param {Vector3[]} in_binormals
20
+ * @param {Vector3[]} in_tangents
21
+ * @param {number[]} shape
22
+ * @param {number[]|Float32Array} shape_normal
23
+ * @param {number} shape_length
24
+ * @param {number[]|Float32Array} shape_transform
25
+ * @param {boolean} [closed]
26
+ * @param {CapType} [cap_type]
27
+ * @returns {BufferGeometry}
28
+ */
29
+ export function makeTubeGeometry(
30
+ in_positions, in_normals, in_binormals, in_tangents,
31
+ shape, shape_normal, shape_length, shape_transform, closed = false, cap_type = CapType.Round
32
+ ) {
33
+ assert.enum(cap_type, CapType, 'cap_type');
34
+ assert.isBoolean(closed, 'closed');
35
+
36
+ assert.isNumber(shape_length, 'shape_length');
37
+ assert.isArrayLike(shape, 'shape');
38
+
39
+ const out = new StreamGeometryBuilder();
40
+
41
+ // helper variables
42
+
43
+ const point_count = in_positions.length / 3;
44
+ const tubular_segments = point_count - 1;
45
+
46
+
47
+ const geometry_size = {
48
+ vertex_count: (tubular_segments + 1) * (shape_length + 1),
49
+ polygon_count: tubular_segments * shape_length * 2
50
+ };
51
+
52
+ if (!closed) {
53
+ append_compute_cap_geometry_size(2, geometry_size, shape_length, cap_type);
54
+ }
55
+
56
+ out.allocate(
57
+ geometry_size.vertex_count,
58
+ geometry_size.polygon_count
59
+ );
60
+
61
+ // create buffer data
62
+
63
+ if (!closed) {
64
+ // start cap
65
+ make_cap(
66
+ out, 0,
67
+ in_positions, in_normals, in_binormals, in_tangents,
68
+ shape, shape_normal, shape_length, shape_transform, 1, cap_type
69
+ );
70
+ }
71
+
72
+ const index_offset = out.cursor_vertices;
73
+
74
+ for (let i = 0; i < tubular_segments; i++) {
75
+
76
+ generateSegment(i);
77
+
78
+ }
79
+
80
+ // if the geometry is not closed, generate the last row of vertices and normals
81
+ // at the regular position on the given path
82
+ //
83
+ // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)
84
+
85
+ generateSegment((closed === false) ? tubular_segments : 0);
86
+
87
+ // finally create faces
88
+ make_ring_faces(out, index_offset, tubular_segments, shape_length);
89
+
90
+ if (!closed) {
91
+ // end cap
92
+ make_cap(
93
+ out, point_count - 1,
94
+ in_positions, in_normals, in_binormals, in_tangents,
95
+ shape, shape_normal, shape_length, shape_transform, -1, cap_type
96
+ );
97
+ }
98
+
99
+ /**
100
+ *
101
+ * @param {number} i
102
+ */
103
+ function generateSegment(i) {
104
+
105
+ // we use getPointAt to sample evenly distributed points from the given path
106
+
107
+ const i3 = i * 3;
108
+
109
+ const Px = in_positions[i3];
110
+ const Py = in_positions[i3 + 1];
111
+ const Pz = in_positions[i3 + 2];
112
+
113
+ // retrieve corresponding normal and binormal
114
+
115
+ const N = in_normals[i];
116
+ const B = in_binormals[i];
117
+
118
+ // generate normals and vertices for the current segment
119
+ compute_bend_normal(v4_array, i, tubular_segments, in_positions);
120
+
121
+ make_ring_vertices(
122
+ out,
123
+ Px, Py, Pz,
124
+ N, B, in_tangents[i],
125
+ i / tubular_segments, v4_array,
126
+ shape, shape_normal, shape_length, shape_transform
127
+ );
128
+
129
+ }
130
+
131
+
132
+ return out.build();
133
+ }
134
+
135
+
136
+ /**
137
+ *
138
+ * @param {number[]|Float32Array} out
139
+ * @param {number} index
140
+ * @param {number} index_count
141
+ * @param {number[]|Float32Array} positions
142
+ */
143
+ function compute_bend_normal(
144
+ out,
145
+ index,
146
+ index_count,
147
+ positions
148
+ ) {
149
+ if (index <= 0 || index >= index_count - 1) {
150
+ // end points, no bending
151
+
152
+ out[0] = 0;
153
+ out[1] = 1;
154
+ out[2] = 0;
155
+ out[3] = 0;
156
+
157
+ return;
158
+ }
159
+
160
+ const index_next = index + 1;
161
+ const index_prev = index - 1;
162
+
163
+ const address_current = index * 3;
164
+ const address_next = index_next * 3;
165
+ const address_prev = index_prev * 3;
166
+
167
+ const i0_x = positions[address_prev];
168
+ const i0_y = positions[address_prev + 1];
169
+ const i0_z = positions[address_prev + 2];
170
+
171
+ const i1_x = positions[address_current];
172
+ const i1_y = positions[address_current + 1];
173
+ const i1_z = positions[address_current + 2];
174
+
175
+ const i2_x = positions[address_next];
176
+ const i2_y = positions[address_next + 1];
177
+ const i2_z = positions[address_next + 2];
178
+
179
+ const d0_x = i0_x - i1_x;
180
+ const d0_y = i0_y - i1_y;
181
+ const d0_z = i0_z - i1_z;
182
+
183
+ const d1_x = i1_x - i2_x;
184
+ const d1_y = i1_y - i2_y;
185
+ const d1_z = i1_z - i2_z;
186
+
187
+ // compute rotation axis
188
+ const cross_x = d0_y * d1_z - d0_z * d1_y;
189
+ const cross_y = d0_z * d1_x - d0_x * d1_z;
190
+ const cross_z = d0_x * d1_y - d0_y * d1_x;
191
+
192
+ const angle = v3_angle_cos_between(d0_x, d0_y, d0_z, d1_x, d1_y, d1_z);
193
+
194
+ const cross_length = v3_length(cross_x, cross_y, cross_z);
195
+
196
+ if (cross_length < 1e-10) {
197
+ // Collinear (or duplicate) neighbours: zero curvature, so no bend.
198
+ // The rotation axis is undefined here and computing it would be
199
+ // 0 / 0 = NaN, poisoning this ring's vertices (visible as shattered
200
+ // geometry on short / straight resampled segments, e.g. the animated
201
+ // marching dashes). Emit the same no-bend value the path ends use.
202
+ out[0] = 0;
203
+ out[1] = 1;
204
+ out[2] = 0;
205
+ out[3] = 0;
206
+
207
+ return;
208
+ }
209
+
210
+ const length_inv = 1 / cross_length;
211
+
212
+ out[0] = cross_x * length_inv;
213
+ out[1] = cross_y * length_inv;
214
+ out[2] = cross_z * length_inv;
215
+
216
+ // bend amount
217
+ out[3] = (1 - Math.abs(angle)) * 0.5;
218
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"make_cap.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/ecs/path/tube/build/make_cap.js"],"names":[],"mappings":"AA+PA;;;;;;;;;;;;;;GAcG;AACH,4DAbW,MAAM,gBACN,YAAY,GAAC,MAAM,EAAE,cACrB,OAAO,EAAE,gBACT,OAAO,EAAE,eAET,OAAO,EAAE,SACT,MAAM,EAAE,gBACR,MAAM,EAAE,GAAC,YAAY,gBACrB,MAAM,mBACN,MAAM,EAAE,GAAC,YAAY,aACrB,MAAM,QACN,OAAO,QA6BjB;AAED;;;;;;GAMG;AACH,wDALW,MAAM,OACN;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,YAAY,EAAC,MAAM,CAAA;CAAC,mBAC3C,MAAM,QACN,OAAO,QAiBjB;wBAjUuB,OAAO;wBAMP,eAAe"}
1
+ {"version":3,"file":"make_cap.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/ecs/path/tube/build/make_cap.js"],"names":[],"mappings":"AAwQA;;;;;;;;;;;;;;GAcG;AACH,4DAbW,MAAM,gBACN,YAAY,GAAC,MAAM,EAAE,cACrB,OAAO,EAAE,gBACT,OAAO,EAAE,eAET,OAAO,EAAE,SACT,MAAM,EAAE,gBACR,MAAM,EAAE,GAAC,YAAY,gBACrB,MAAM,mBACN,MAAM,EAAE,GAAC,YAAY,aACrB,MAAM,QACN,OAAO,QA6BjB;AAED;;;;;;GAMG;AACH,wDALW,MAAM,OACN;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,YAAY,EAAC,MAAM,CAAA;CAAC,mBAC3C,MAAM,QACN,OAAO,QAiBjB;wBA1UuB,OAAO;wBAMP,eAAe"}
@@ -106,8 +106,17 @@ function make_cap_round(
106
106
  tangent.multiplyScalar(-direction);
107
107
  }
108
108
 
109
- const normal = direction > 0 ? N : N.clone().negate();
110
- const binormal = direction > 0 ? B : B.clone().negate();
109
+ // Use the path frame's N/B directly for BOTH ends. The profile orientation
110
+ // must match the tube body it caps, otherwise the cap's cross-section is
111
+ // mis-aligned where it meets the body. Negating N/B for the end cap (as was
112
+ // done previously) point-reflects the profile in the N-B plane (a 180 degree
113
+ // turn about the tangent): invisible for a centrally-symmetric profile (a
114
+ // circle) but it twists the cap for any asymmetric one. The end cap's face
115
+ // winding is already corrected by the `j = cap_segment_count - i` ring-order
116
+ // reversal below (it advances the end-cap rings in the same direction vs `i`
117
+ // as the start cap), so no profile negation is needed.
118
+ const normal = N;
119
+ const binormal = B;
111
120
 
112
121
  const angular_step_i = (Math.PI / 2) / cap_segment_count;
113
122
 
@@ -1 +1 @@
1
- {"version":3,"file":"make_sky_hosek.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/path_tracer/make_sky_hosek.js"],"names":[],"mappings":"AAQA,2LAmCC"}
1
+ {"version":3,"file":"make_sky_hosek.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/path_tracer/make_sky_hosek.js"],"names":[],"mappings":"AASA,2LAmCC"}