zincjs 2.4.1-beta.4 → 2.4.1-beta.6
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 +10 -0
- package/build/zinc.js +3 -3
- package/build/zinc.js.map +1 -1
- package/package.json +1 -1
- package/src/controls.js +71 -49
- package/src/loaders/primitivesLoader.js +59 -16
- package/src/minimap.js +48 -13
- package/src/primitives/geometry.js +2 -0
- package/src/primitives/glyph.js +11 -1
- package/src/primitives/glyphset.js +26 -9
- package/src/primitives/label.js +39 -24
- package/src/primitives/lines.js +2 -0
- package/src/primitives/lines2.js +76 -24
- package/src/primitives/lod.js +11 -4
- package/src/primitives/marker.js +6 -1
- package/src/primitives/markerCluster.js +5 -1
- package/src/primitives/pointset.js +33 -28
- package/src/primitives/textureSlides.js +19 -3
- package/src/primitives/textureVolume.js +2 -0
- package/src/primitives/tubeLines.js +4 -0
- package/src/primitives/zincObject.js +76 -10
- package/src/region.js +85 -35
- package/src/renderRequests.js +20 -0
- package/src/renderer.js +85 -13
- package/src/scene.js +132 -22
- package/src/sceneExporter.js +4 -0
- package/src/sceneLoader.js +58 -19
- package/src/textSprite.js +211 -0
- package/src/tsl/glyphTransform.js +85 -54
- package/src/utilities.js +147 -47
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import SpriteTextModule from 'three-spritetext';
|
|
3
|
+
import { requestRenderAll } from './renderRequests';
|
|
4
|
+
const SpriteText = SpriteTextModule.default || SpriteTextModule;
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Text sprites (labels, marker numbers) are drawn on a canvas by
|
|
8
|
+
* three-spritetext. To keep the text sharp the canvas resolution is chosen
|
|
9
|
+
* from the size of the text on screen instead of a fixed font size, and it
|
|
10
|
+
* is redrawn once web fonts have finished loading.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
//Canvas pixels per screen pixel, a little oversampling smooths the edges
|
|
14
|
+
const OVERSAMPLE = 1.5;
|
|
15
|
+
const MIN_FONT_SIZE = 16;
|
|
16
|
+
const MAX_FONT_SIZE = 256;
|
|
17
|
+
//Only redraw when the screen scale has changed by more than this ratio
|
|
18
|
+
const RESCALE_THRESHOLD = 0.1;
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Screen pixels per unit of sprite height. Text sprites do not use size
|
|
22
|
+
* attenuation, their height is in view units at a distance of one, so this
|
|
23
|
+
* is viewportHeight * pixelRatio / (2 * tan(fov / 2)). Default to a 1000
|
|
24
|
+
* pixels viewport with the 40 degrees field of view used by the scenes.
|
|
25
|
+
*/
|
|
26
|
+
let pixelsPerUnit = 1000 / (2 * Math.tan(20 * Math.PI / 180));
|
|
27
|
+
|
|
28
|
+
//Weak references to every live text sprite, so they can be redrawn
|
|
29
|
+
const sprites = new Set();
|
|
30
|
+
let fontListenerAdded = false;
|
|
31
|
+
|
|
32
|
+
const fontSizeForHeight = (textHeight) => {
|
|
33
|
+
const size = Math.round(textHeight * pixelsPerUnit * OVERSAMPLE);
|
|
34
|
+
return Math.min(Math.max(size, MIN_FONT_SIZE), MAX_FONT_SIZE);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* The canvas matches the on screen size so mipmaps are not required,
|
|
39
|
+
* they blur the text when it is shrunk.
|
|
40
|
+
*/
|
|
41
|
+
const applyTextureSettings = (sprite) => {
|
|
42
|
+
const texture = sprite.material?.map;
|
|
43
|
+
if (texture) {
|
|
44
|
+
texture.generateMipmaps = false;
|
|
45
|
+
texture.minFilter = THREE.LinearFilter;
|
|
46
|
+
texture.magFilter = THREE.LinearFilter;
|
|
47
|
+
texture.anisotropy = 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* SpriteText redraws its canvas and creates a new texture in every setter,
|
|
53
|
+
* set all font properties before a single redraw instead.
|
|
54
|
+
*/
|
|
55
|
+
const setFont = (sprite, fontFace, fontSize, fontWeight) => {
|
|
56
|
+
if ('_fontFace' in sprite && typeof sprite._genCanvas === 'function') {
|
|
57
|
+
sprite._fontFace = fontFace;
|
|
58
|
+
sprite._fontSize = fontSize;
|
|
59
|
+
sprite._fontWeight = fontWeight;
|
|
60
|
+
sprite._genCanvas();
|
|
61
|
+
} else {
|
|
62
|
+
sprite.fontFace = fontFace;
|
|
63
|
+
sprite.fontSize = fontSize;
|
|
64
|
+
sprite.fontWeight = fontWeight;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Redraw the sprite at the resolution required for its current height.
|
|
70
|
+
*/
|
|
71
|
+
const refreshTextSprite = (sprite, force = false) => {
|
|
72
|
+
const fontSize = fontSizeForHeight(sprite.textHeight);
|
|
73
|
+
if (force || fontSize !== sprite.fontSize) {
|
|
74
|
+
setFont(sprite, sprite.fontFace, fontSize, sprite.fontWeight);
|
|
75
|
+
applyTextureSettings(sprite);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const forEachSprite = (callback) => {
|
|
80
|
+
sprites.forEach((ref) => {
|
|
81
|
+
const sprite = ref.deref();
|
|
82
|
+
if (sprite) {
|
|
83
|
+
callback(sprite);
|
|
84
|
+
} else {
|
|
85
|
+
sprites.delete(ref);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const redrawAll = () => {
|
|
91
|
+
forEachSprite((sprite) => refreshTextSprite(sprite, true));
|
|
92
|
+
requestRenderAll();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* The canvas is drawn with a fallback font if a web font has not been
|
|
97
|
+
* loaded yet, redraw once it is available.
|
|
98
|
+
*/
|
|
99
|
+
const waitForFont = (sprite) => {
|
|
100
|
+
if (typeof document === 'undefined' || !document.fonts) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!fontListenerAdded && document.fonts.addEventListener) {
|
|
104
|
+
//Catch fonts from stylesheets added after the sprites are created
|
|
105
|
+
document.fonts.addEventListener('loadingdone', redrawAll);
|
|
106
|
+
fontListenerAdded = true;
|
|
107
|
+
}
|
|
108
|
+
const font = `${sprite.fontWeight} ${sprite.fontSize}px ${sprite.fontFace}`;
|
|
109
|
+
try {
|
|
110
|
+
if (!document.fonts.check(font)) {
|
|
111
|
+
document.fonts.load(font).then(() => {
|
|
112
|
+
refreshTextSprite(sprite, true);
|
|
113
|
+
requestRenderAll();
|
|
114
|
+
}).catch(() => {});
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
//Invalid font string, keep the fallback font
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const registerTextSprite = (sprite) => {
|
|
122
|
+
sprites.add(new WeakRef(sprite));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Stop tracking a text sprite, call this when it is disposed.
|
|
127
|
+
*/
|
|
128
|
+
const releaseTextSprite = (sprite) => {
|
|
129
|
+
sprites.forEach((ref) => {
|
|
130
|
+
const trackedSprite = ref.deref();
|
|
131
|
+
if (!trackedSprite || trackedSprite === sprite) {
|
|
132
|
+
sprites.delete(ref);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create a text sprite rendered with a node material, the canvas resolution
|
|
139
|
+
* is chosen from the size of the text on screen.
|
|
140
|
+
*
|
|
141
|
+
* @param {String} text - Text to display.
|
|
142
|
+
* @param {Number} textHeight - Height of a line of text in view units.
|
|
143
|
+
* @param {String} colour - CSS colour of the text.
|
|
144
|
+
* @param {String} fontFace - Font family.
|
|
145
|
+
* @param {Number|String} fontWeight - Font weight.
|
|
146
|
+
* @return {SpriteText}
|
|
147
|
+
*/
|
|
148
|
+
const createTextSprite = (text, textHeight, colour, fontFace, fontWeight) => {
|
|
149
|
+
const sprite = colour ? new SpriteText(text, textHeight, colour) :
|
|
150
|
+
new SpriteText(text, textHeight);
|
|
151
|
+
setFont(sprite, fontFace, fontSizeForHeight(textHeight), fontWeight);
|
|
152
|
+
//Replace the SpriteMaterial created by SpriteText with a node material
|
|
153
|
+
//using the same canvas texture.
|
|
154
|
+
const originalMaterial = sprite.material;
|
|
155
|
+
sprite.material = new THREE.SpriteNodeMaterial({
|
|
156
|
+
map: originalMaterial.map,
|
|
157
|
+
});
|
|
158
|
+
originalMaterial.map = null;
|
|
159
|
+
originalMaterial.dispose();
|
|
160
|
+
sprite.material.sizeAttenuation = false;
|
|
161
|
+
applyTextureSettings(sprite);
|
|
162
|
+
registerTextSprite(sprite);
|
|
163
|
+
waitForFont(sprite);
|
|
164
|
+
return sprite;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Set the height of a text sprite and redraw it once at the matching
|
|
169
|
+
* resolution.
|
|
170
|
+
*/
|
|
171
|
+
const setTextSpriteHeight = (sprite, textHeight) => {
|
|
172
|
+
if ('_textHeight' in sprite) {
|
|
173
|
+
sprite._textHeight = textHeight;
|
|
174
|
+
refreshTextSprite(sprite, true);
|
|
175
|
+
} else {
|
|
176
|
+
sprite.textHeight = textHeight;
|
|
177
|
+
refreshTextSprite(sprite);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update the number of screen pixels per unit of text height, e.g. when the
|
|
183
|
+
* viewport is resized. Text sprites are redrawn if it has changed
|
|
184
|
+
* significantly.
|
|
185
|
+
*
|
|
186
|
+
* @param {Number} value - viewportHeight * pixelRatio / (2 * tan(fov / 2))
|
|
187
|
+
*/
|
|
188
|
+
const setTextPixelsPerUnit = (value) => {
|
|
189
|
+
if (!(value > 0) || !isFinite(value)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (Math.abs(value / pixelsPerUnit - 1) > RESCALE_THRESHOLD) {
|
|
193
|
+
pixelsPerUnit = value;
|
|
194
|
+
forEachSprite((sprite) => refreshTextSprite(sprite));
|
|
195
|
+
requestRenderAll();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const getTextPixelsPerUnit = () => pixelsPerUnit;
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
applyTextureSettings as applyTextSpriteTextureSettings,
|
|
203
|
+
createTextSprite,
|
|
204
|
+
fontSizeForHeight,
|
|
205
|
+
getTextPixelsPerUnit,
|
|
206
|
+
refreshTextSprite,
|
|
207
|
+
registerTextSprite,
|
|
208
|
+
releaseTextSprite,
|
|
209
|
+
setTextPixelsPerUnit,
|
|
210
|
+
setTextSpriteHeight,
|
|
211
|
+
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
int,
|
|
8
8
|
float,
|
|
9
9
|
vec3,
|
|
10
|
+
vec4,
|
|
10
11
|
uniform,
|
|
11
12
|
mix,
|
|
12
13
|
select,
|
|
@@ -28,6 +29,9 @@ const repeatModeToInt = (repeat_mode) => {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
//Order of the vec4 fields of each glyph instance in the output buffer
|
|
33
|
+
const OUTPUT_FIELDS = { position: 0, axis1: 1, axis2: 2, axis3: 3, color: 4 };
|
|
34
|
+
|
|
31
35
|
const multiplierForRepeatMode = (repeat_mode) => {
|
|
32
36
|
if (repeat_mode == "AXES_2D" || repeat_mode == "MIRROR") return 2;
|
|
33
37
|
if (repeat_mode == "AXES_3D") return 3;
|
|
@@ -66,9 +70,10 @@ const multiplierForRepeatMode = (repeat_mode) => {
|
|
|
66
70
|
* @param {Array<number>} params.scaleFactors
|
|
67
71
|
* @returns {Object} { uniforms, compute, outputs } - update
|
|
68
72
|
* uniforms.bottomFrame/topFrame/proportion.value then pass `compute` to
|
|
69
|
-
* renderer.compute()/computeAsync(); read results back
|
|
70
|
-
* outputs.
|
|
71
|
-
*
|
|
73
|
+
* renderer.compute()/computeAsync(); read results back with
|
|
74
|
+
* readbackGlyphTransform(). outputs.buffer is the interleaved storage
|
|
75
|
+
* buffer (outputs.stride vec4 per instance), outputs.position/axis1/
|
|
76
|
+
* axis2/axis3/color provide element(index) accessors for materials.
|
|
72
77
|
*/
|
|
73
78
|
function createGlyphTransformCompute({
|
|
74
79
|
baseCount,
|
|
@@ -87,18 +92,38 @@ function createGlyphTransformCompute({
|
|
|
87
92
|
}) {
|
|
88
93
|
const multiplier = multiplierForRepeatMode(repeat_mode);
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
95
|
+
// All inputs are interleaved into a single storage buffer and all outputs
|
|
96
|
+
// into another, one vec4 per field. Separate buffers per field needed up
|
|
97
|
+
// to 11 storage buffers in the compute stage, more than some devices
|
|
98
|
+
// support (e.g. 10 on iOS). vec4 also avoids three.js padding vec3
|
|
99
|
+
// storage data into an extra copy.
|
|
100
|
+
const hasColor = !!colorData;
|
|
101
|
+
const inputSources = [positionsData, axis1Data, axis2Data, axis3Data, scaleData];
|
|
102
|
+
if (hasColor) inputSources.push(colorData);
|
|
103
|
+
const inputStride = inputSources.length;
|
|
104
|
+
const outputStride = hasColor ? OUTPUT_FIELDS.color + 1 : OUTPUT_FIELDS.axis3 + 1;
|
|
105
|
+
const recordCount = positionsData.length / 3;
|
|
106
|
+
const inputData = new Float32Array(recordCount * inputStride * 4);
|
|
107
|
+
for (let record = 0; record < recordCount; record++) {
|
|
108
|
+
for (let field = 0; field < inputStride; field++) {
|
|
109
|
+
const source = inputSources[field];
|
|
110
|
+
const target = (record * inputStride + field) * 4;
|
|
111
|
+
inputData[target] = source[record * 3];
|
|
112
|
+
inputData[target + 1] = source[record * 3 + 1];
|
|
113
|
+
inputData[target + 2] = source[record * 3 + 2];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const inputBuffer = attributeArray(inputData, 'vec4');
|
|
117
|
+
const outputBuffer = attributeArray(outputCount * outputStride, 'vec4');
|
|
118
|
+
|
|
119
|
+
const readInput = (recordIdx, field) =>
|
|
120
|
+
inputBuffer.element(recordIdx.mul(int(inputStride)).add(int(field))).xyz;
|
|
121
|
+
const writeOutput = (outIdx, field, value) =>
|
|
122
|
+
outputBuffer.element(outIdx.mul(int(outputStride)).add(int(field))).assign(vec4(value, 0));
|
|
123
|
+
//Accessor used by the render material to read an output field
|
|
124
|
+
const outputField = (field) => ({
|
|
125
|
+
element: (idx) => outputBuffer.element(int(idx).mul(int(outputStride)).add(int(field))).xyz,
|
|
126
|
+
});
|
|
102
127
|
|
|
103
128
|
const uniforms = {
|
|
104
129
|
bottomFrame: uniform(0, 'int'),
|
|
@@ -121,11 +146,11 @@ function createGlyphTransformCompute({
|
|
|
121
146
|
const topIdx = uniforms.topFrame.mul(int(baseCount)).add(inputIdx);
|
|
122
147
|
|
|
123
148
|
// Matches updateMorphGlyphsets(): bottom*proportion + top*(1-proportion).
|
|
124
|
-
const point = mix(
|
|
125
|
-
const axis1 = mix(
|
|
126
|
-
const axis2 = mix(
|
|
127
|
-
const axis3 = mix(
|
|
128
|
-
const scale = mix(
|
|
149
|
+
const point = mix(readInput(topIdx, 0), readInput(bottomIdx, 0), uniforms.proportion);
|
|
150
|
+
const axis1 = mix(readInput(topIdx, 1), readInput(bottomIdx, 1), uniforms.proportion);
|
|
151
|
+
const axis2 = mix(readInput(topIdx, 2), readInput(bottomIdx, 2), uniforms.proportion);
|
|
152
|
+
const axis3 = mix(readInput(topIdx, 3), readInput(bottomIdx, 3), uniforms.proportion);
|
|
153
|
+
const scale = mix(readInput(topIdx, 4), readInput(bottomIdx, 4), uniforms.proportion);
|
|
129
154
|
|
|
130
155
|
// NOTE: each branch below assigns straight into the output storage
|
|
131
156
|
// buffer elements (outPosition.element(outIdx).assign(...) etc.),
|
|
@@ -175,10 +200,10 @@ function createGlyphTransformCompute({
|
|
|
175
200
|
a3.assign(a3.negate());
|
|
176
201
|
});
|
|
177
202
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
writeOutput(outIdx, OUTPUT_FIELDS.position, p);
|
|
204
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis1, a1);
|
|
205
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis2, a2);
|
|
206
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis3, a3);
|
|
182
207
|
|
|
183
208
|
}).Else(() => {
|
|
184
209
|
|
|
@@ -220,16 +245,16 @@ function createGlyphTransformCompute({
|
|
|
220
245
|
.div(max(mag2, 1e-8));
|
|
221
246
|
const finalAxis2 = select(mag2.greaterThan(0), axis2Raw.mul(scaling2), axis2Raw);
|
|
222
247
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
248
|
+
writeOutput(outIdx, OUTPUT_FIELDS.position, finalPoint);
|
|
249
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis1, finalAxis1);
|
|
250
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis2, finalAxis2);
|
|
251
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis3, finalAxis3);
|
|
227
252
|
|
|
228
253
|
});
|
|
229
254
|
|
|
230
|
-
if (
|
|
231
|
-
const color = mix(
|
|
232
|
-
|
|
255
|
+
if (hasColor) {
|
|
256
|
+
const color = mix(readInput(topIdx, 5), readInput(bottomIdx, 5), uniforms.proportion);
|
|
257
|
+
writeOutput(outIdx, OUTPUT_FIELDS.color, color);
|
|
233
258
|
}
|
|
234
259
|
|
|
235
260
|
} )().compute( outputCount );
|
|
@@ -238,11 +263,14 @@ function createGlyphTransformCompute({
|
|
|
238
263
|
uniforms,
|
|
239
264
|
compute: computeFn,
|
|
240
265
|
outputs: {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
266
|
+
buffer: outputBuffer,
|
|
267
|
+
stride: outputStride,
|
|
268
|
+
count: outputCount,
|
|
269
|
+
position: outputField(OUTPUT_FIELDS.position),
|
|
270
|
+
axis1: outputField(OUTPUT_FIELDS.axis1),
|
|
271
|
+
axis2: outputField(OUTPUT_FIELDS.axis2),
|
|
272
|
+
axis3: outputField(OUTPUT_FIELDS.axis3),
|
|
273
|
+
color: hasColor ? outputField(OUTPUT_FIELDS.color) : undefined,
|
|
246
274
|
},
|
|
247
275
|
};
|
|
248
276
|
}
|
|
@@ -264,27 +292,30 @@ function createGlyphTransformCompute({
|
|
|
264
292
|
* @param {THREE.WebGPURenderer} renderer
|
|
265
293
|
* @param {Object} glyphCompute - return value of createGlyphTransformCompute().
|
|
266
294
|
* @returns {Promise<Object>} { position, axis1, axis2, axis3, color } -
|
|
267
|
-
* plain Float32Arrays
|
|
268
|
-
* present when the glyphset has colours).
|
|
295
|
+
* plain Float32Arrays with a 4 float stride per output glyph instance
|
|
296
|
+
* (color only present when the glyphset has colours).
|
|
269
297
|
*/
|
|
270
298
|
async function readbackGlyphTransform(renderer, glyphCompute) {
|
|
271
|
-
const {
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
299
|
+
const { buffer, stride, count, color } = glyphCompute.outputs;
|
|
300
|
+
//A single readback of the interleaved output buffer
|
|
301
|
+
const data = new Float32Array(await renderer.getArrayBufferAsync(buffer.value));
|
|
302
|
+
//Split into one array per field, each with a 4 float stride per instance
|
|
303
|
+
const extract = (field) => {
|
|
304
|
+
const result = new Float32Array(count * 4);
|
|
305
|
+
for (let i = 0; i < count; i++) {
|
|
306
|
+
const source = (i * stride + field) * 4;
|
|
307
|
+
result[i * 4] = data[source];
|
|
308
|
+
result[i * 4 + 1] = data[source + 1];
|
|
309
|
+
result[i * 4 + 2] = data[source + 2];
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
};
|
|
282
313
|
return {
|
|
283
|
-
position:
|
|
284
|
-
axis1:
|
|
285
|
-
axis2:
|
|
286
|
-
axis3:
|
|
287
|
-
color:
|
|
314
|
+
position: extract(OUTPUT_FIELDS.position),
|
|
315
|
+
axis1: extract(OUTPUT_FIELDS.axis1),
|
|
316
|
+
axis2: extract(OUTPUT_FIELDS.axis2),
|
|
317
|
+
axis3: extract(OUTPUT_FIELDS.axis3),
|
|
318
|
+
color: color ? extract(OUTPUT_FIELDS.color) : undefined,
|
|
288
319
|
};
|
|
289
320
|
}
|
|
290
321
|
|