@ifc-lite/viewer 1.1.7 → 1.5.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/LICENSE +373 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/Arrow.dom-B0e15b_b.js +20 -0
- package/dist/assets/arrow2-bb-jcVEo.js +2 -0
- package/dist/assets/arrow2_bg-4Y7xYo54.wasm +0 -0
- package/dist/assets/arrow2_bg-BlXl-cSQ.js +1 -0
- package/dist/assets/arrow2_bg-BoXCojjR.wasm +0 -0
- package/dist/assets/desktop-cache-oPzaWXYE.js +1 -0
- package/dist/assets/event-DIOks52T.js +1 -0
- package/dist/assets/ifc-cache-BAN4vcd4.js +1 -0
- package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
- package/dist/assets/index-Dgd6vzw_.js +65252 -0
- package/dist/assets/index-v3mcCUPN.css +1 -0
- package/dist/assets/native-bridge-Ci7NLjlZ.js +111 -0
- package/dist/assets/wasm-bridge-Dc82YpdZ.js +1 -0
- package/dist/favicon-16x16-cropped.png +0 -0
- package/dist/favicon-16x16.png +0 -0
- package/dist/favicon-192x192-cropped.png +0 -0
- package/dist/favicon-192x192.png +0 -0
- package/dist/favicon-32x32-cropped.png +0 -0
- package/dist/favicon-32x32.png +0 -0
- package/dist/favicon-48x48-cropped.png +0 -0
- package/dist/favicon-48x48.png +0 -0
- package/dist/favicon-512x512-cropped.png +0 -0
- package/dist/favicon-512x512.png +0 -0
- package/dist/favicon-64x64-cropped.png +0 -0
- package/dist/favicon-64x64.png +0 -0
- package/dist/favicon-96x96-cropped.png +0 -0
- package/dist/favicon-96x96.png +0 -0
- package/dist/favicon-square-512.png +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +3 -0
- package/dist/index.html +44 -0
- package/dist/logo.png +0 -0
- package/dist/manifest.json +48 -0
- package/index.html +33 -2
- package/package.json +34 -17
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16-cropped.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-192x192-cropped.png +0 -0
- package/public/favicon-192x192.png +0 -0
- package/public/favicon-32x32-cropped.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-48x48-cropped.png +0 -0
- package/public/favicon-48x48.png +0 -0
- package/public/favicon-512x512-cropped.png +0 -0
- package/public/favicon-512x512.png +0 -0
- package/public/favicon-64x64-cropped.png +0 -0
- package/public/favicon-64x64.png +0 -0
- package/public/favicon-96x96-cropped.png +0 -0
- package/public/favicon-96x96.png +0 -0
- package/public/favicon-square-512.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +3 -0
- package/public/logo.png +0 -0
- package/public/manifest.json +48 -0
- package/src/App.tsx +2 -0
- package/src/components/ui/alert.tsx +62 -0
- package/src/components/ui/badge.tsx +39 -0
- package/src/components/ui/dialog.tsx +120 -0
- package/src/components/ui/label.tsx +27 -0
- package/src/components/ui/select.tsx +151 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/components/ui/tabs.tsx +1 -1
- package/src/components/viewer/BCFPanel.tsx +1164 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +875 -0
- package/src/components/viewer/DataConnector.tsx +840 -0
- package/src/components/viewer/DrawingSettingsPanel.tsx +536 -0
- package/src/components/viewer/EntityContextMenu.tsx +45 -17
- package/src/components/viewer/ExportChangesButton.tsx +195 -0
- package/src/components/viewer/ExportDialog.tsx +402 -0
- package/src/components/viewer/HierarchyPanel.tsx +1132 -218
- package/src/components/viewer/IDSPanel.tsx +661 -0
- package/src/components/viewer/KeyboardShortcutsDialog.tsx +245 -39
- package/src/components/viewer/MainToolbar.tsx +418 -94
- package/src/components/viewer/PropertiesPanel.tsx +1355 -91
- package/src/components/viewer/PropertyEditor.tsx +611 -0
- package/src/components/viewer/Section2DPanel.tsx +3313 -0
- package/src/components/viewer/SheetSetupPanel.tsx +502 -0
- package/src/components/viewer/StatusBar.tsx +27 -16
- package/src/components/viewer/TitleBlockEditor.tsx +437 -0
- package/src/components/viewer/ToolOverlays.tsx +935 -127
- package/src/components/viewer/ViewerLayout.tsx +40 -11
- package/src/components/viewer/Viewport.tsx +1276 -336
- package/src/components/viewer/ViewportContainer.tsx +554 -18
- package/src/components/viewer/ViewportOverlays.tsx +24 -7
- package/src/hooks/useBCF.ts +504 -0
- package/src/hooks/useIDS.ts +1065 -0
- package/src/hooks/useIfc.ts +1534 -205
- package/src/hooks/useIfcCache.ts +279 -0
- package/src/hooks/useKeyboardShortcuts.ts +50 -8
- package/src/hooks/useModelSelection.ts +61 -0
- package/src/hooks/useViewerSelectors.ts +218 -0
- package/src/hooks/useWebGPU.ts +80 -0
- package/src/index.css +265 -27
- package/src/lib/platform.ts +23 -0
- package/src/services/cacheService.ts +142 -0
- package/src/services/desktop-cache.ts +143 -0
- package/src/services/fs-cache.ts +212 -0
- package/src/services/ifc-cache.ts +14 -6
- package/src/store/constants.ts +85 -0
- package/src/store/index.ts +214 -0
- package/src/store/slices/bcfSlice.ts +372 -0
- package/src/store/slices/cameraSlice.ts +63 -0
- package/src/store/slices/dataSlice.test.ts +226 -0
- package/src/store/slices/dataSlice.ts +112 -0
- package/src/store/slices/drawing2DSlice.ts +340 -0
- package/src/store/slices/hoverSlice.ts +40 -0
- package/src/store/slices/idsSlice.ts +310 -0
- package/src/store/slices/loadingSlice.ts +33 -0
- package/src/store/slices/measurementSlice.test.ts +217 -0
- package/src/store/slices/measurementSlice.ts +293 -0
- package/src/store/slices/modelSlice.test.ts +271 -0
- package/src/store/slices/modelSlice.ts +211 -0
- package/src/store/slices/mutationSlice.ts +502 -0
- package/src/store/slices/sectionSlice.test.ts +125 -0
- package/src/store/slices/sectionSlice.ts +58 -0
- package/src/store/slices/selectionSlice.test.ts +286 -0
- package/src/store/slices/selectionSlice.ts +263 -0
- package/src/store/slices/sheetSlice.ts +565 -0
- package/src/store/slices/uiSlice.ts +58 -0
- package/src/store/slices/visibilitySlice.test.ts +304 -0
- package/src/store/slices/visibilitySlice.ts +277 -0
- package/src/store/types.test.ts +135 -0
- package/src/store/types.ts +248 -0
- package/src/store.ts +40 -515
- package/src/utils/ifcConfig.ts +82 -0
- package/src/utils/localParsingUtils.ts +287 -0
- package/src/utils/serverDataModel.ts +783 -0
- package/src/utils/spatialHierarchy.ts +283 -0
- package/src/utils/viewportUtils.ts +334 -0
- package/src/vite-env.d.ts +23 -0
- package/src/webgpu-types.d.ts +128 -0
- package/src-tauri/Cargo.toml +29 -0
- package/src-tauri/build.rs +7 -0
- package/src-tauri/capabilities/default.json +18 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/lib.rs +21 -0
- package/src-tauri/src/main.rs +10 -0
- package/src-tauri/tauri.conf.json +39 -0
- package/vite.config.ts +174 -26
- package/public/ifc-lite_bg.wasm +0 -0
- package/public/web-ifc.wasm +0 -0
- package/src/components/Viewport.tsx +0 -723
- package/src/components/viewer/BoxSelectionOverlay.tsx +0 -53
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Local parsing utility functions
|
|
7
|
+
* Pure functions for geometry processing, bounds calculation, and batch management
|
|
8
|
+
*
|
|
9
|
+
* Extracted from useIfc.ts for reusability and testability
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { MeshData } from '@ifc-lite/geometry';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Types
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bounding box for 3D geometry
|
|
20
|
+
*/
|
|
21
|
+
export interface Bounds3D {
|
|
22
|
+
min: { x: number; y: number; z: number };
|
|
23
|
+
max: { x: number; y: number; z: number };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Coordinate information for geometry
|
|
28
|
+
*/
|
|
29
|
+
export interface CoordinateInfo {
|
|
30
|
+
originShift: { x: number; y: number; z: number };
|
|
31
|
+
originalBounds: Bounds3D;
|
|
32
|
+
shiftedBounds: Bounds3D;
|
|
33
|
+
/** True if model had large coordinates requiring RTC shift. NOT the same as proper georeferencing via IfcMapConversion. */
|
|
34
|
+
hasLargeCoordinates: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Geometry statistics
|
|
39
|
+
*/
|
|
40
|
+
export interface GeometryStats {
|
|
41
|
+
totalVertices: number;
|
|
42
|
+
totalTriangles: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Bounds Calculation
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Maximum coordinate threshold for valid geometry (10km)
|
|
51
|
+
* Matches CoordinateHandler's NORMAL_COORD_THRESHOLD
|
|
52
|
+
* Coordinates beyond this are likely corrupted or unshifted original coordinates
|
|
53
|
+
*/
|
|
54
|
+
export const MAX_VALID_COORD = 10000;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create an initial bounds object with infinite values
|
|
58
|
+
* Use this as a starting point for incremental bounds calculation
|
|
59
|
+
*/
|
|
60
|
+
export function createEmptyBounds(): Bounds3D {
|
|
61
|
+
return {
|
|
62
|
+
min: { x: Infinity, y: Infinity, z: Infinity },
|
|
63
|
+
max: { x: -Infinity, y: -Infinity, z: -Infinity },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Update bounds incrementally from a mesh's positions
|
|
69
|
+
* Mutates the bounds object for performance
|
|
70
|
+
*
|
|
71
|
+
* @param bounds - Bounds object to update (mutated)
|
|
72
|
+
* @param positions - Float32Array of vertex positions (x,y,z triplets)
|
|
73
|
+
* @param maxCoord - Maximum valid coordinate value (default: 10km)
|
|
74
|
+
*/
|
|
75
|
+
export function updateBoundsFromPositions(
|
|
76
|
+
bounds: Bounds3D,
|
|
77
|
+
positions: Float32Array | number[],
|
|
78
|
+
maxCoord: number = MAX_VALID_COORD
|
|
79
|
+
): void {
|
|
80
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
81
|
+
const x = positions[i];
|
|
82
|
+
const y = positions[i + 1];
|
|
83
|
+
const z = positions[i + 2];
|
|
84
|
+
// Filter out corrupted/unshifted vertices (> threshold from origin)
|
|
85
|
+
const isValid = Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) &&
|
|
86
|
+
Math.abs(x) < maxCoord && Math.abs(y) < maxCoord && Math.abs(z) < maxCoord;
|
|
87
|
+
if (isValid) {
|
|
88
|
+
bounds.min.x = Math.min(bounds.min.x, x);
|
|
89
|
+
bounds.min.y = Math.min(bounds.min.y, y);
|
|
90
|
+
bounds.min.z = Math.min(bounds.min.z, z);
|
|
91
|
+
bounds.max.x = Math.max(bounds.max.x, x);
|
|
92
|
+
bounds.max.y = Math.max(bounds.max.y, y);
|
|
93
|
+
bounds.max.z = Math.max(bounds.max.z, z);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Calculate bounds from an array of meshes
|
|
100
|
+
*
|
|
101
|
+
* @param meshes - Array of mesh data
|
|
102
|
+
* @returns Bounds and geometry statistics
|
|
103
|
+
*/
|
|
104
|
+
export function calculateMeshBounds(meshes: MeshData[]): { bounds: Bounds3D; stats: GeometryStats } {
|
|
105
|
+
const bounds = createEmptyBounds();
|
|
106
|
+
let totalVertices = 0;
|
|
107
|
+
let totalTriangles = 0;
|
|
108
|
+
|
|
109
|
+
for (const mesh of meshes) {
|
|
110
|
+
updateBoundsFromPositions(bounds, mesh.positions);
|
|
111
|
+
totalVertices += mesh.positions.length / 3;
|
|
112
|
+
totalTriangles += mesh.indices.length / 3;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
bounds,
|
|
117
|
+
stats: { totalVertices, totalTriangles },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create coordinate info from bounds
|
|
123
|
+
*
|
|
124
|
+
* @param bounds - Calculated geometry bounds
|
|
125
|
+
* @param originShift - Optional origin shift (defaults to zero)
|
|
126
|
+
* @param hasLargeCoordinates - Whether model had large coordinates requiring RTC shift
|
|
127
|
+
* @returns Coordinate info object with cloned bounds and computed shiftedBounds
|
|
128
|
+
*/
|
|
129
|
+
export function createCoordinateInfo(
|
|
130
|
+
bounds: Bounds3D,
|
|
131
|
+
originShift: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 },
|
|
132
|
+
hasLargeCoordinates: boolean = false
|
|
133
|
+
): CoordinateInfo {
|
|
134
|
+
// Deep-clone the incoming bounds into originalBounds
|
|
135
|
+
const originalBounds: Bounds3D = {
|
|
136
|
+
min: { x: bounds.min.x, y: bounds.min.y, z: bounds.min.z },
|
|
137
|
+
max: { x: bounds.max.x, y: bounds.max.y, z: bounds.max.z },
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Compute shiftedBounds by subtracting originShift from each min/max
|
|
141
|
+
const shiftedBounds: Bounds3D = {
|
|
142
|
+
min: {
|
|
143
|
+
x: bounds.min.x - originShift.x,
|
|
144
|
+
y: bounds.min.y - originShift.y,
|
|
145
|
+
z: bounds.min.z - originShift.z,
|
|
146
|
+
},
|
|
147
|
+
max: {
|
|
148
|
+
x: bounds.max.x - originShift.x,
|
|
149
|
+
y: bounds.max.y - originShift.y,
|
|
150
|
+
z: bounds.max.z - originShift.z,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
originShift: { x: originShift.x, y: originShift.y, z: originShift.z },
|
|
156
|
+
originalBounds,
|
|
157
|
+
shiftedBounds,
|
|
158
|
+
hasLargeCoordinates,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Render Throttling
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Calculate render interval based on file size
|
|
168
|
+
* Adaptive throttling: smaller files get more frequent updates, larger files fewer
|
|
169
|
+
*
|
|
170
|
+
* @param fileSizeMB - File size in megabytes
|
|
171
|
+
* @returns Render interval in milliseconds
|
|
172
|
+
*/
|
|
173
|
+
export function getRenderIntervalMs(fileSizeMB: number): number {
|
|
174
|
+
if (fileSizeMB > 100) {
|
|
175
|
+
return 200; // Huge files: 5 updates/sec
|
|
176
|
+
} else if (fileSizeMB > 50) {
|
|
177
|
+
return 100; // Large files: 10 updates/sec
|
|
178
|
+
} else if (fileSizeMB > 20) {
|
|
179
|
+
return 75; // Medium files: ~13 updates/sec
|
|
180
|
+
}
|
|
181
|
+
return 50; // Small files: 20 updates/sec
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Calculate server streaming render interval
|
|
186
|
+
*
|
|
187
|
+
* @param fileSizeMB - File size in megabytes
|
|
188
|
+
* @returns Render interval in milliseconds
|
|
189
|
+
*/
|
|
190
|
+
export function getServerStreamIntervalMs(fileSizeMB: number): number {
|
|
191
|
+
return fileSizeMB > 100 ? 200 : 100;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Storey Height Calculation
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Calculate storey heights from elevation differences
|
|
200
|
+
* When property data doesn't provide heights, we can infer them from
|
|
201
|
+
* the difference between consecutive storey elevations
|
|
202
|
+
*
|
|
203
|
+
* @param storeyElevations - Map of storey ID to elevation
|
|
204
|
+
* @returns Map of storey ID to calculated height
|
|
205
|
+
*/
|
|
206
|
+
export function calculateStoreyHeights(storeyElevations: Map<number, number>): Map<number, number> {
|
|
207
|
+
const heights = new Map<number, number>();
|
|
208
|
+
|
|
209
|
+
if (storeyElevations.size < 2) {
|
|
210
|
+
return heights;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const entries = Array.from(storeyElevations.entries()) as Array<[number, number]>;
|
|
214
|
+
const sortedStoreys = entries.sort((a, b) => a[1] - b[1]); // Sort by elevation ascending
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < sortedStoreys.length - 1; i++) {
|
|
217
|
+
const [storeyId, elevation] = sortedStoreys[i];
|
|
218
|
+
const nextElevation = sortedStoreys[i + 1][1];
|
|
219
|
+
const height = nextElevation - elevation;
|
|
220
|
+
if (height > 0) {
|
|
221
|
+
heights.set(storeyId, height);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return heights;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// Progress Calculation
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Calculate progress percentage for geometry streaming
|
|
234
|
+
*
|
|
235
|
+
* @param currentMeshes - Number of meshes processed so far
|
|
236
|
+
* @param estimatedTotal - Estimated total meshes (may be 0 or inaccurate)
|
|
237
|
+
* @param basePercent - Base percentage (start of geometry phase)
|
|
238
|
+
* @param maxPercent - Maximum percentage to reach
|
|
239
|
+
* @returns Progress percentage
|
|
240
|
+
*/
|
|
241
|
+
export function calculateStreamingProgress(
|
|
242
|
+
currentMeshes: number,
|
|
243
|
+
estimatedTotal: number,
|
|
244
|
+
basePercent: number = 50,
|
|
245
|
+
maxPercent: number = 95
|
|
246
|
+
): number {
|
|
247
|
+
const denominator = Math.max(estimatedTotal / 10, currentMeshes);
|
|
248
|
+
// Guard against division by zero (both currentMeshes and estimatedTotal are 0)
|
|
249
|
+
if (denominator === 0) {
|
|
250
|
+
return basePercent;
|
|
251
|
+
}
|
|
252
|
+
const progressRange = maxPercent - basePercent;
|
|
253
|
+
return Math.min(maxPercent, basePercent + (currentMeshes / denominator) * progressRange);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ============================================================================
|
|
257
|
+
// Mesh Conversion
|
|
258
|
+
// ============================================================================
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Normalize IFCX mesh color to RGBA format
|
|
262
|
+
*
|
|
263
|
+
* @param color - Optional color array (RGB or RGBA)
|
|
264
|
+
* @returns Normalized RGBA color tuple
|
|
265
|
+
*/
|
|
266
|
+
export function normalizeColor(
|
|
267
|
+
color?: [number, number, number, number] | [number, number, number] | number[]
|
|
268
|
+
): [number, number, number, number] {
|
|
269
|
+
// Return default color if no color provided or array is too short
|
|
270
|
+
if (!color || color.length < 3) {
|
|
271
|
+
return [0.7, 0.7, 0.7, 1.0];
|
|
272
|
+
}
|
|
273
|
+
if (color.length === 4) {
|
|
274
|
+
return color as [number, number, number, number];
|
|
275
|
+
}
|
|
276
|
+
return [color[0], color[1], color[2], 1.0];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Convert server mesh colors from float [0-1] to byte [0-255]
|
|
281
|
+
*
|
|
282
|
+
* @param floatColors - Float color array [0-1]
|
|
283
|
+
* @returns Uint8Array of byte colors [0-255]
|
|
284
|
+
*/
|
|
285
|
+
export function convertFloatColorToBytes(floatColors: number[]): Uint8Array {
|
|
286
|
+
return new Uint8Array(floatColors.map((c: number) => Math.round(c * 255)));
|
|
287
|
+
}
|