zincjs 1.19.3 → 1.20.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zincjs",
3
- "version": "1.19.3",
3
+ "version": "1.20.0",
4
4
  "description": "ZincJS (Web-based-Zinc-Visualisation)",
5
5
  "main": "build/zinc.js",
6
6
  "directories": {
@@ -15,6 +15,7 @@ const defaultTextureSettings = {
15
15
  "position": [0, 0, 0],
16
16
  "scale": [1, 1, 1],
17
17
  "flipY": false,
18
+ "flipZ": false,
18
19
  "reference_point": "corner"
19
20
  }
20
21
  ],
@@ -38,7 +39,6 @@ const defaultTextureSettings = {
38
39
  };
39
40
 
40
41
  const defaultOptions = {
41
- hideWhitePixel: false,
42
42
  hideBlackPixel: true,
43
43
  keepScalePosition: true,
44
44
  filterByValue: true,
@@ -126,197 +126,81 @@ function readNIFTI(data) {
126
126
  }
127
127
 
128
128
 
129
- function createSources(niftiHeader, niftiImage, maskHeader, maskImage, options) {
130
- if (niftiHeader?.dims && niftiHeader.dims[0] === 3) {
131
- const width = niftiHeader.dims[1];
132
- const height = niftiHeader.dims[2];
133
- const depth = niftiHeader.dims[3];
134
- const { typedData, dataType } = getTypedData(niftiHeader, niftiImage);
135
- const sliceSize = width * height;
136
- const length = sliceSize * depth * 4;
137
- const fullArray = new Uint8Array(length);
138
- let maskData = undefined;
139
- if (maskHeader && maskImage) {
140
- const maskWidth = maskHeader.dims[1];
141
- const maskHeight = maskHeader.dims[2];
142
- const maskDepth = maskHeader.dims[3];
143
- if (maskWidth === width && maskHeight === height && maskDepth === depth) {
144
- const maskedTypedData = getTypedData(maskHeader, maskImage);
145
- maskData = maskedTypedData.typedData;
146
- }
147
- }
148
- let slope = niftiHeader.scl_slope || 1;
149
- let intercept = niftiHeader.scl_inter || 0;
129
+ function convertNiftiToUint8Array(niftiHeader, niftiImage) {
130
+ // 1. Parse the raw array using the correct native typed array view
131
+ let rawData;
132
+ switch (niftiHeader.datatypeCode) {
133
+ case nifti.NIFTI1.TYPE_UINT8: rawData = new Uint8Array(niftiImage); break;
134
+ case nifti.NIFTI1.TYPE_INT8: rawData = new Int8Array(niftiImage); break;
135
+ case nifti.NIFTI1.TYPE_UINT16: rawData = new Uint16Array(niftiImage); break;
136
+ case nifti.NIFTI1.TYPE_INT16: rawData = new Int16Array(niftiImage); break;
137
+ case nifti.NIFTI1.TYPE_UINT32: rawData = new Uint32Array(niftiImage); break;
138
+ case nifti.NIFTI1.TYPE_INT32: rawData = new Int32Array(niftiImage); break;
139
+ case nifti.NIFTI1.TYPE_FLOAT32: rawData = new Float32Array(niftiImage); break;
140
+ case nifti.NIFTI1.TYPE_FLOAT64: rawData = new Float64Array(niftiImage); break;
141
+ default: throw new Error("Unsupported NIfTI data type");
142
+ }
150
143
 
151
- let min = Infinity;
152
- let max = -Infinity;
144
+ const slope = niftiHeader.scl_slope || 1.0;
145
+ const intercept = niftiHeader.scl_inter || 0.0;
153
146
 
154
- const dataLength = typedData.length;
155
- for (let i = 0; i < dataLength; i++) {
156
- let val = (typedData[i] * slope) + intercept;
157
- if (val < min) min = val;
158
- if (val > max) max = val;
159
- }
160
- let range = max - min;
147
+ let min = Infinity;
148
+ let max = -Infinity;
161
149
 
162
- for (let slice = 0; slice < depth; slice++) {
163
- const sliceOffset = sliceSize * slice;
164
- for (let row = 0; row < height; row++) {
165
- const rowOffset = row * width;
166
- for (let col = 0; col < width; col++) {
167
- const offset = sliceOffset + rowOffset + col;
168
- let trueVal = typedData[offset] * slope + intercept;
169
- let value = 0;
170
- if (range !== 0) {
171
- // Map [min, max] to [0, 255]
172
- value = ((trueVal - min) / range) * 255;
173
- }
174
- fullArray[offset * 4] = value;
175
- fullArray[offset * 4 + 1] = value;
176
- fullArray[offset * 4 + 2] = value;
177
- fullArray[offset * 4 + 3] = 255;
178
- if (maskData) {
179
- const maskedValue = maskData[offset];
180
- if (options.hideBlackPixel) {
181
- //if (maskedValue === 0 && 20 > value) {
182
- if (maskedValue === 0) {
183
- fullArray[offset * 4 + 3] = 0;
184
- }
185
- }
186
- } else if (options.filterByValue) {
187
- if (options.hideWhitePixel && value === 255) {
188
- fullArray[offset * 4 + 3] = 0;
189
- }
190
- if (options.hideBlackPixel && 2 >= value) {
191
- fullArray[offset * 4] = 240;
192
- fullArray[offset * 4 + 1] = 240;
193
- fullArray[offset * 4 + 2] = 240;
194
- fullArray[offset * 4 + 3] = 1.0;
195
- }
196
- }
197
- }
198
- }
199
- }
150
+ const len = rawData.length;
151
+ for (let i = 0; i < len; i++) {
152
+ let val = rawData[i];
153
+ if (val < min) min = val;
154
+ if (val > max) max = val;
155
+ }
200
156
 
201
- return {
202
- data: fullArray,
203
- width,
204
- height,
205
- depth,
206
- };
157
+ const uint8Data = new Uint8Array(len);
158
+ const range = max - min;
159
+
160
+ for (let i = 0; i < len; i++) {
161
+ let physicalVal = rawData[i];
162
+ let normalised = (physicalVal - min) / range;
163
+ normalised = Math.max(0.0, Math.min(1.0, normalised));
164
+ uint8Data[i] = Math.round(normalised * 255.0);
207
165
  }
208
- return undefined;
166
+
167
+ return uint8Data;
209
168
  }
210
- /*
211
- function createSources(niftiHeader, niftiImage, maskHeader, maskImage, options) {
169
+
170
+ function createSources(niftiHeader, niftiImage, maskHeader, maskImage) {
212
171
  if (niftiHeader?.dims && niftiHeader.dims[0] === 3) {
213
172
  const width = niftiHeader.dims[1];
214
173
  const height = niftiHeader.dims[2];
215
174
  const depth = niftiHeader.dims[3];
216
- const { typedData, dataType } = getTypedData(niftiHeader, niftiImage);
217
- const sliceSize = width * height;
218
- const length = sliceSize * depth * 4;
219
- const fullArray = new Uint8Array(length);
175
+ let isRGB = false;
176
+ if (niftiHeader.intent_code === 1007 || niftiHeader.intent_code === 1008 ||
177
+ niftiHeader.datatypeCode === 128 || niftiHeader.datatypeCode === 2304) {
178
+ isRGB = true;
179
+ }
180
+ const typedData = convertNiftiToUint8Array(niftiHeader, niftiImage);
220
181
  let maskData = undefined;
221
182
  if (maskHeader && maskImage) {
222
183
  const maskWidth = maskHeader.dims[1];
223
184
  const maskHeight = maskHeader.dims[2];
224
185
  const maskDepth = maskHeader.dims[3];
225
186
  if (maskWidth === width && maskHeight === height && maskDepth === depth) {
226
- const maskedTypedData = getTypedData(maskHeader, maskImage);
227
- maskData = maskedTypedData.typedData;
228
- }
229
- }
230
- let scale = 1;
231
- let valueOffset = 0.0;
232
- if (dataType === "float") {
233
- //It should be like
234
- // scl_slope = 0, intensity will be stored in value between 0, 1
235
- // Otherwise the following
236
- //y = scl_slope * x + scl_inter
237
- if (niftiHeader.scl_slope === 0) {
238
- scale = 255;
239
- } else {
240
- valueOffset = niftiHeader.scl_inter;
241
- }
242
- } else if (dataType === "uint") {
243
- scale = 255;
244
- } else if (dataType === "int16") {
245
- //scale = 1 / 255;
246
- if (niftiHeader.scl_slope === 0) {
247
- scale = 255;
248
- } else {
249
- valueOffset = niftiHeader.scl_inter;
250
- }
251
- }
252
- for (let slice = 0; slice < depth; slice++) {
253
- const sliceOffset = sliceSize * slice;
254
- for (let row = 0; row < height; row++) {
255
- const rowOffset = row * width;
256
- for (let col = 0; col < width; col++) {
257
- const offset = sliceOffset + rowOffset + col;
258
- let value = typedData[offset] * scale + valueOffset;
259
- if (value < 0) value = 0;
260
- fullArray[offset * 4] = value;
261
- fullArray[offset * 4 + 1] = value;
262
- fullArray[offset * 4 + 2] = value;
263
- fullArray[offset * 4 + 3] = 255;
264
- if (maskData) {
265
- const maskedValue = maskData[offset];
266
- if (options.hideBlackPixel) {
267
- //if (maskedValue === 0 && 20 > value) {
268
- if (maskedValue === 0) {
269
- fullArray[offset * 4 + 3] = 0;
270
- }
271
- }
272
- } else if (options.filterByValue) {
273
- if (options.hideWhitePixel && value === 255) {
274
- fullArray[offset * 4 + 3] = 0;
275
- }
276
- if (options.hideBlackPixel && 2 >= value) {
277
- fullArray[offset * 4] = 240;
278
- fullArray[offset * 4 + 1] = 240;
279
- fullArray[offset * 4 + 2] = 240;
280
- fullArray[offset * 4 + 3] = 1.0;
281
- }
282
- }
283
- }
187
+ const maskedTypedData = convertNiftiToUint8Array(maskHeader, maskImage);
188
+ maskData = maskedTypedData;
284
189
  }
285
190
  }
191
+
286
192
  return {
287
- data: fullArray,
193
+ data: typedData,
194
+ maskData: maskData,
288
195
  width,
289
196
  height,
290
197
  depth,
198
+ isRGB,
291
199
  };
292
200
  }
293
201
  return undefined;
294
202
  }
295
203
 
296
- */
297
-
298
- function getTypedData(niftiHeader, niftiImage) {
299
- if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_UINT8) {
300
- return { typedData: new Uint8Array(niftiImage), dataType: "uint" };
301
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_INT16) {
302
- return { typedData: new Int16Array(niftiImage), dataType: "int16" };
303
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_INT32) {
304
- return { typedData: new Int32Array(niftiImage), dataType: "int32" };
305
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_FLOAT32) {
306
- return { typedData: new Float32Array(niftiImage), dataType: "float" };
307
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_FLOAT64) {
308
- return { typedData: new Float64Array(niftiImage), dataType: "float" };
309
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_INT8) {
310
- return { typedData: new Int8Array(niftiImage), dataType: "int" };
311
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_UINT16) {
312
- return { typedData: new Uint16Array(niftiImage), dataType: "int" };
313
- } else if (niftiHeader.datatypeCode === nifti.NIFTI1.TYPE_UINT32) {
314
- return { typedData: new Uint32Array(niftiImage), dataType: "int" };
315
- } else {
316
- return;
317
- }
318
- }
319
-
320
204
  function getSFormTransformation(header, options) {
321
205
  if (header?.affine && header.dims) {
322
206
  const affine = header.affine;
@@ -348,26 +232,41 @@ function getTransformationFromHeader(header, options) {
348
232
  return {position: undefined, scale: undefined}
349
233
  }
350
234
 
235
+ function createDataTexture(data, width, height, depth, isRGB) {
236
+ const dataTexture = new THREE.DataTexture2DArray(
237
+ data, width, height, depth);
238
+ dataTexture.anisotropy = 4;
239
+ if (!isRGB) {
240
+ dataTexture.format = THREE.RedFormat;
241
+ }
242
+ dataTexture.minFilter = THREE.NearestFilter;
243
+ dataTexture.magFilter = THREE.NearestFilter;
244
+ dataTexture.needsUpdate = true;
245
+ return dataTexture;
246
+ }
247
+
351
248
  function createTextureArray(sources) {
352
249
  if (sources?.data) {
353
250
  const tArray = new TextureArray();
354
- tArray.impl = new THREE.DataTexture2DArray(
251
+ tArray.impl = new createDataTexture(
355
252
  sources.data, sources.width, sources.height, sources.depth);
356
- tArray.impl.anisotropy = 4;
357
253
  tArray.size = {
358
254
  width: sources.width,
359
255
  height: sources.height,
360
256
  depth: sources.depth,
361
257
  };
362
258
  tArray.isLoading = false;
363
- tArray.impl.needsUpdate = true;
364
259
  return tArray;
365
260
  }
366
261
  return undefined;
367
262
  }
368
263
 
369
- function createTexturePrimitives(niftiHeader, sources, useHeaderInfo, textureSettings, options) {
264
+ function createTexturePrimitives(niftiHeader, sources, useHeaderInfo, textureSettings, optionsIn) {
370
265
  if (sources?.data) {
266
+ const options = {...defaultOptions};
267
+ if (optionsIn) {
268
+ Object.assign(options, optionsIn);
269
+ }
371
270
  const newTexture = new TextureSlides();
372
271
  const tArray = createTextureArray(sources);
373
272
  if (tArray) {
@@ -384,36 +283,23 @@ function createTexturePrimitives(niftiHeader, sources, useHeaderInfo, textureSet
384
283
  settings.locations[0].scale = scale;
385
284
  settings.locations[0].position = position;
386
285
  }
387
- /*
388
- if (niftiHeader.qoffset_x) {
389
- settings.locations[0].position[0] = niftiHeader.qoffset_x;
390
- }
391
- if (niftiHeader.qoffset_y) {
392
- settings.locations[0].position[1] = niftiHeader.qoffset_y;
393
- }
394
- if (niftiHeader.qoffset_y) {
395
- settings.locations[0].position[2] = niftiHeader.qoffset_z;
396
- }
397
- if (niftiHeader.dims) {
398
- settings.locations[0].scale[0] = niftiHeader.dims[1];
399
- settings.locations[0].scale[1] = niftiHeader.dims[2];
400
- settings.locations[0].scale[2] = niftiHeader.dims[3];
401
- }
402
- */
403
286
  }
404
287
  newTexture.initialise(settings, undefined);
405
288
  newTexture.showEdges(0x999999);
289
+ if (sources.maskData && !options.timeEnabled) {
290
+ const maskTexture = createDataTexture(sources.maskData,
291
+ sources.width, sources.height, sources.depth, false);
292
+ newTexture.setMask(maskTexture);
293
+ }
294
+ newTexture.setNumberOfChannels(sources.isRGB ? 3 : 1);
295
+
406
296
  return newTexture;
407
297
  }
408
298
  }
409
299
  return undefined;
410
300
  }
411
301
 
412
- async function getImagesFromURL(url, maskURL, optionsIn) {
413
- const options = {...defaultOptions};
414
- if (optionsIn) {
415
- Object.assign(options, optionsIn);
416
- }
302
+ async function getImagesFromURL(url, maskURL) {
417
303
 
418
304
  // try {
419
305
  let maskHeader = undefined, maskImage = undefined;
@@ -427,13 +313,13 @@ async function getImagesFromURL(url, maskURL, optionsIn) {
427
313
  const response = await fetch(url);
428
314
  const buffer = await response.arrayBuffer();
429
315
  const {niftiHeader, niftiImage} = readNIFTI(buffer);
430
- const sources = createSources(niftiHeader, niftiImage, maskHeader, maskImage, options);
431
-
316
+ const sources = createSources(niftiHeader, niftiImage, maskHeader, maskImage);
432
317
  return {sources, niftiHeader};
433
318
  }
434
319
 
435
320
 
436
- async function createPrimitivesFromNIFTI(url, useHeaderInfo, maskURL, textureSettings, optionsIn) {
321
+ async function createPrimitivesFromNIFTI(
322
+ url, useHeaderInfo, maskURL, textureSettings, optionsIn) {
437
323
  let timeEnabled = false;
438
324
  let textureP = undefined;
439
325
  let firstURL = url;
@@ -454,6 +340,7 @@ async function createPrimitivesFromNIFTI(url, useHeaderInfo, maskURL, textureSet
454
340
  }
455
341
  textureP.timeEnabled = true;
456
342
  }
343
+
457
344
  return textureP;
458
345
  }
459
346
 
@@ -21,8 +21,12 @@ const TextureSlides = function (textureIn) {
21
21
  this.morph.userData = this;
22
22
  let edgesLine = undefined;
23
23
  let flipY = true;
24
+ let flipZ = false;
24
25
  let brightness = 0.0;
25
26
  let contrast = 1.0;
27
+ let nChannels = 1;
28
+ let maskTexture = undefined;
29
+ let maskEnabled = false;
26
30
  let discardAlpha = true;
27
31
  let lt0 = 0;
28
32
  let lt1 = 0;
@@ -124,6 +128,10 @@ const TextureSlides = function (textureIn) {
124
128
  uniforms.discardAlpha.value = discardAlpha;
125
129
  uniforms.depth.value = this.texture.size.depth;
126
130
  uniforms.flipY.value = flipY;
131
+ uniforms.flipZ.value = flipZ;
132
+ uniforms.mask.value = maskTexture;
133
+ uniforms.maskEnabled.value = maskEnabled;
134
+ uniforms.nChannels.value = nChannels;
127
135
  const options = {
128
136
  fs: shader.fs,
129
137
  vs: shader.vs,
@@ -330,6 +338,9 @@ const TextureSlides = function (textureIn) {
330
338
  if ("flipY" in locations[0]) {
331
339
  flipY = locations[0].flipY;
332
340
  }
341
+ if ("flipZ" in locations[0]) {
342
+ flipZ = locations[0].flipZ;
343
+ }
333
344
  }
334
345
  this.createSlides(textureData.settings.slides);
335
346
  if (finishCallback != undefined && (typeof finishCallback == 'function')) {
@@ -351,36 +362,34 @@ const TextureSlides = function (textureIn) {
351
362
  edgesLine.visible = true;
352
363
  }
353
364
 
354
- this.isAlphaPixelDiscarded = () => {
355
- return discardAlpha;
356
- }
357
365
 
358
- this.discardAlphaPixel = (flag) => {
359
- discardAlpha = flag;
366
+ this.setUniformsValue = (name, val) => {
360
367
  this.morph.children.forEach((mesh) => {
361
368
  const material = mesh.material;
362
369
  if (material.type === "ShaderMaterial") {
363
370
  const uniforms = material.uniforms;
364
- uniforms.discardAlpha.value = discardAlpha;
371
+ uniforms[name].value = val;
365
372
  material.needsUpdate = true;
366
373
  }
367
374
  });
368
375
  }
369
376
 
377
+ this.isAlphaPixelDiscarded = () => {
378
+ return discardAlpha;
379
+ }
380
+
381
+ this.discardAlphaPixel = (flag) => {
382
+ discardAlpha = flag;
383
+ this.setUniformsValue("discardAlpha", discardAlpha);
384
+ }
385
+
370
386
  this.getBrightness = () => {
371
387
  return brightness;
372
388
  }
373
389
 
374
390
  this.setBrightness = (brightnessIn) => {
375
391
  brightness = brightnessIn;
376
- this.morph.children.forEach((mesh) => {
377
- const material = mesh.material;
378
- if (material.type === "ShaderMaterial") {
379
- const uniforms = material.uniforms;
380
- uniforms.brightness.value = brightness;
381
- material.needsUpdate = true;
382
- }
383
- });
392
+ this.setUniformsValue("brightness", brightness);
384
393
  }
385
394
 
386
395
  this.getContrast = () => {
@@ -390,17 +399,29 @@ const TextureSlides = function (textureIn) {
390
399
  this.setContrast = (contrastIn) => {
391
400
  if (contrast >= 0 ) {
392
401
  contrast = contrastIn;
393
- this.morph.children.forEach((mesh) => {
394
- const material = mesh.material;
395
- if (material.type === "ShaderMaterial") {
396
- const uniforms = material.uniforms;
397
- uniforms.contrast.value = contrast;
398
- material.needsUpdate = true;
399
- }
400
- });
402
+ this.setUniformsValue("contrast", contrast);
401
403
  }
402
404
  }
403
405
 
406
+ this.getNumberOfChannels = () => {
407
+ return nChannels;
408
+ }
409
+
410
+ this.setNumberOfChannels = (numbersIn) => {
411
+ nChannels = numbersIn;
412
+ this.setUniformsValue("nChannels", nChannels);
413
+ }
414
+
415
+ this.getMask = () => {
416
+ return maskTexture;
417
+ }
418
+
419
+ this.setMask = (maskTextureIn) => {
420
+ maskTexture = maskTextureIn;
421
+ this.setUniformsValue("mask", maskTexture);
422
+ this.setUniformsValue("maskEnabled", maskTexture ? true : false);
423
+ }
424
+
404
425
  this.hideEdges = () => {
405
426
  if (edgesLine) {
406
427
  edgesLine.visible = false;
@@ -20,6 +20,7 @@ const TextureSlides = function (textureIn) {
20
20
  this.morph.userData = this;
21
21
  let edgesLine = undefined;
22
22
  let flipY = true;
23
+ let flipZ = false;
23
24
 
24
25
  /**
25
26
  @typedef SLIDE_SETTINGS
@@ -113,6 +114,7 @@ const TextureSlides = function (textureIn) {
113
114
  uniforms.diffuse.value = this.texture.impl;
114
115
  uniforms.depth.value = this.texture.size.depth;
115
116
  uniforms.flipY.value = flipY;
117
+ uniforms.flipZ.value = flipZ;
116
118
 
117
119
  const options = {
118
120
  fs: shader.fs,
@@ -284,6 +286,9 @@ const TextureSlides = function (textureIn) {
284
286
  if ("flipY" in locations[0]) {
285
287
  flipY = locations[0].flipY;
286
288
  }
289
+ if ("flipZ" in locations[0]) {
290
+ flipZ = locations[0].flipZ;
291
+ }
287
292
  }
288
293
  this.createSlides(textureData.settings.slides);
289
294
  if (finishCallback != undefined && (typeof finishCallback == 'function')) {
@@ -10,27 +10,35 @@ precision highp sampler2DArray;
10
10
 
11
11
  uniform sampler2DArray diffuse0;
12
12
  uniform sampler2DArray diffuse1;
13
+ uniform sampler2DArray mask;
13
14
  uniform bool discardAlpha;
15
+ uniform bool maskEnabled;
14
16
  uniform float brightness;
15
17
  uniform float contrast;
16
18
  uniform float time;
19
+ uniform int nChannels;
17
20
  in vec3 vUw;
18
21
 
19
22
  out vec4 outColor;
20
23
 
21
24
  void main() {
22
25
 
23
- vec4 color0 = texture( diffuse0, vUw );
24
- vec4 color1 = texture( diffuse1, vUw );
25
- vec4 color = mix(color0, color1, time);
26
+ float color0 = texture( diffuse0, vUw ).r;
27
+ float color1 = texture( diffuse1, vUw ).r;
28
+ float color = mix(color0, color1, time);
29
+ // discard if alpha is zero
30
+ if (maskEnabled && discardAlpha) {
31
+ float mask = texture(mask, vUw).r;
32
+ if (mask == 0.0) discard;
33
+ }
26
34
 
27
35
  // discard if alpha is zero
28
- if (discardAlpha && color.a == 0.0) discard;
36
+ //if (discardAlpha && color.a == 0.0) discard;
29
37
  // Apply brightness
30
- vec3 brightenedColor = color.rgb + vec3(brightness);
38
+ vec3 brightenedColor = vec3(color) + vec3(brightness);
31
39
  // Apply contrast
32
40
  vec3 contrastedColor = (brightenedColor - vec3(0.5)) * contrast + vec3(0.5);
33
- outColor = vec4(contrastedColor, color.a);
41
+ outColor = vec4(contrastedColor, 1.0);
34
42
  }
35
43
  `;
36
44
 
@@ -41,6 +49,7 @@ uniform float depth;
41
49
  uniform vec3 slide;
42
50
  uniform int direction;
43
51
  uniform bool flipY;
52
+ uniform bool flipZ;
44
53
 
45
54
  void main() {
46
55
 
@@ -56,6 +65,8 @@ void main() {
56
65
 
57
66
  if (flipY)
58
67
  slidePos.y = 1.0 - slidePos.y;
68
+ if (flipZ)
69
+ slidePos.z = 1.0 - slidePos.z;
59
70
 
60
71
  vUw.xyz = vec3(slidePos.x, slidePos.y, slidePos.z * depth);
61
72
 
@@ -70,8 +81,12 @@ const getUniforms = function() {
70
81
  discardAlpha: {value: true},
71
82
  diffuse0: { value: undefined },
72
83
  diffuse1: { value: undefined },
73
- direction: {value: 1},
74
- flipY: { value: true},
84
+ direction: { value: 1 },
85
+ flipY: { value: true },
86
+ flipZ: { value: false },
87
+ nChannels: { value: 1 },
88
+ mask: { value: undefined },
89
+ maskEnabled: { value: false },
75
90
  slide: { value: new THREE.Vector3( 0, 0, 1 ) },
76
91
  time: { value: 0 }
77
92
  };
@@ -1455,7 +1455,7 @@ class GLTFWriter {
1455
1455
  let warned = false;
1456
1456
 
1457
1457
  for ( const attributeName in geometry.morphAttributes ) {
1458
-
1458
+
1459
1459
 
1460
1460
  // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
1461
1461
  // Three.js doesn't support TANGENT yet.
@@ -1502,7 +1502,7 @@ class GLTFWriter {
1502
1502
  if ( ! geometry.morphTargetsRelative ) {
1503
1503
 
1504
1504
  if (baseAttribute) {
1505
-
1505
+
1506
1506
  for ( let j = 0, jl = attribute.count; j < jl; j ++ ) {
1507
1507
 
1508
1508
  if (baseAttribute.count > j) {