cfd-materials 0.1.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/README.md +17 -0
- package/build/docs/404.html +47 -0
- package/build/docs/_demos/:uuid +47 -0
- package/build/docs/cloud-visualization.html +48 -0
- package/build/docs/colorful-button.html +48 -0
- package/build/docs/colorful-input.html +48 -0
- package/build/docs/index.html +48 -0
- package/build/docs/mesh-visualization.html +48 -0
- package/build/docs/umi.4e426eb9.js +1 -0
- package/build/docs/umi.6b1604cb.css +8 -0
- package/build/docs/~demos/:uuid.html +47 -0
- package/build/docs/~demos/cloud-visualization-demo.html +47 -0
- package/build/docs/~demos/colorful-button-demo.html +47 -0
- package/build/docs/~demos/colorful-input-demo.html +47 -0
- package/build/docs/~demos/mesh-visualization-demo.html +47 -0
- package/build/index.css +2 -0
- package/build/index.html +1 -0
- package/build/index.js +53 -0
- package/build/lowcode/assets-daily.json +67 -0
- package/build/lowcode/assets-dev.json +67 -0
- package/build/lowcode/assets-prod.json +67 -0
- package/build/lowcode/designer.html +302 -0
- package/build/lowcode/index.html +304 -0
- package/build/lowcode/index.js +1 -0
- package/build/lowcode/meta.js +1 -0
- package/build/lowcode/preview.css +1 -0
- package/build/lowcode/preview.html +33 -0
- package/build/lowcode/preview.js +310 -0
- package/build/lowcode/render/default/view.css +1 -0
- package/build/lowcode/render/default/view.js +13 -0
- package/build/lowcode/view.css +1 -0
- package/build/lowcode/view.js +13 -0
- package/dist/BizComps.css +1 -0
- package/dist/BizComps.js +14 -0
- package/dist/BizComps.js.map +1 -0
- package/es/components/cloud-visualization/cloud-visualization.d.ts +20 -0
- package/es/components/cloud-visualization/cloud-visualization.js +738 -0
- package/es/components/cloud-visualization/index.d.ts +3 -0
- package/es/components/cloud-visualization/index.js +2 -0
- package/es/components/cloud-visualization/index.scss +55 -0
- package/es/components/colorful-button/colorful-button.d.ts +12 -0
- package/es/components/colorful-button/colorful-button.js +25 -0
- package/es/components/colorful-button/index.d.ts +3 -0
- package/es/components/colorful-button/index.js +2 -0
- package/es/components/colorful-button/index.scss +5 -0
- package/es/components/colorful-input/colorful-input.d.ts +8 -0
- package/es/components/colorful-input/colorful-input.js +19 -0
- package/es/components/colorful-input/index.d.ts +3 -0
- package/es/components/colorful-input/index.js +2 -0
- package/es/components/colorful-input/index.scss +5 -0
- package/es/components/mesh-visualization/index.d.ts +3 -0
- package/es/components/mesh-visualization/index.js +2 -0
- package/es/components/mesh-visualization/index.scss +441 -0
- package/es/components/mesh-visualization/mesh-visualization.d.ts +30 -0
- package/es/components/mesh-visualization/mesh-visualization.js +2609 -0
- package/es/index.d.ts +10 -0
- package/es/index.js +6 -0
- package/es/index.scss +2 -0
- package/es/style.js +3 -0
- package/es/variables.d.ts +2 -0
- package/es/variables.js +2 -0
- package/es/variables.scss +3 -0
- package/lib/components/cloud-visualization/cloud-visualization.d.ts +20 -0
- package/lib/components/cloud-visualization/cloud-visualization.js +743 -0
- package/lib/components/cloud-visualization/index.d.ts +3 -0
- package/lib/components/cloud-visualization/index.js +7 -0
- package/lib/components/cloud-visualization/index.scss +55 -0
- package/lib/components/colorful-button/colorful-button.d.ts +12 -0
- package/lib/components/colorful-button/colorful-button.js +31 -0
- package/lib/components/colorful-button/index.d.ts +3 -0
- package/lib/components/colorful-button/index.js +7 -0
- package/lib/components/colorful-button/index.scss +5 -0
- package/lib/components/colorful-input/colorful-input.d.ts +8 -0
- package/lib/components/colorful-input/colorful-input.js +25 -0
- package/lib/components/colorful-input/index.d.ts +3 -0
- package/lib/components/colorful-input/index.js +7 -0
- package/lib/components/colorful-input/index.scss +5 -0
- package/lib/components/mesh-visualization/index.d.ts +3 -0
- package/lib/components/mesh-visualization/index.js +7 -0
- package/lib/components/mesh-visualization/index.scss +441 -0
- package/lib/components/mesh-visualization/mesh-visualization.d.ts +30 -0
- package/lib/components/mesh-visualization/mesh-visualization.js +2614 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +14 -0
- package/lib/index.scss +2 -0
- package/lib/style.js +3 -0
- package/lib/variables.d.ts +2 -0
- package/lib/variables.js +5 -0
- package/lib/variables.scss +3 -0
- package/lowcode/cloud-visualization/meta.ts +194 -0
- package/lowcode/colorful-button/meta.ts +102 -0
- package/lowcode/colorful-input/meta.ts +56 -0
- package/lowcode/mesh-visualization/meta.ts +278 -0
- package/lowcode_es/cloud-visualization/meta.d.ts +22 -0
- package/lowcode_es/cloud-visualization/meta.js +197 -0
- package/lowcode_es/colorful-button/meta.d.ts +22 -0
- package/lowcode_es/colorful-button/meta.js +85 -0
- package/lowcode_es/colorful-input/meta.d.ts +22 -0
- package/lowcode_es/colorful-input/meta.js +48 -0
- package/lowcode_es/mesh-visualization/meta.d.ts +22 -0
- package/lowcode_es/mesh-visualization/meta.js +285 -0
- package/lowcode_es/meta.js +167 -0
- package/lowcode_es/view.js +18 -0
- package/lowcode_lib/cloud-visualization/meta.d.ts +22 -0
- package/lowcode_lib/cloud-visualization/meta.js +202 -0
- package/lowcode_lib/colorful-button/meta.d.ts +22 -0
- package/lowcode_lib/colorful-button/meta.js +90 -0
- package/lowcode_lib/colorful-input/meta.d.ts +22 -0
- package/lowcode_lib/colorful-input/meta.js +53 -0
- package/lowcode_lib/mesh-visualization/meta.d.ts +22 -0
- package/lowcode_lib/mesh-visualization/meta.js +290 -0
- package/lowcode_lib/meta.js +171 -0
- package/lowcode_lib/view.js +28 -0
- package/package.json +104 -0
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports["default"] = void 0;
|
|
6
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
7
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
8
|
+
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
9
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
10
|
+
var React = _react;
|
|
11
|
+
var THREE = _interopRequireWildcard(require("three"));
|
|
12
|
+
var _variables = require("../../variables");
|
|
13
|
+
require("./index.scss");
|
|
14
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t2 in e) "default" !== _t2 && {}.hasOwnProperty.call(e, _t2) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t2)) && (i.get || i.set) ? o(f, _t2, i) : f[_t2] = e[_t2]); return f; })(e, t); }
|
|
15
|
+
/**
|
|
16
|
+
* 保存 three.js 渲染上下文
|
|
17
|
+
* 用于后续销毁 renderer、停止动画、移除事件等
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 保存 three.js 渲染上下文
|
|
22
|
+
* 用于后续销毁 renderer、停止动画、移除事件等
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 解析 CSV 后得到的数据集结构
|
|
27
|
+
* points: 所有采样点
|
|
28
|
+
* xValues/yValues/zValues: 各坐标轴的离散取值
|
|
29
|
+
* xMin/xMax...: 坐标范围
|
|
30
|
+
* fieldRanges: 各物理量最小值和最大值,用于归一化
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
var GRID_SIZE = 48; //可视化平面分辨率可视化平面分辨率,值越大,画面越细腻,但计算量也越大
|
|
34
|
+
var WORLD_SIZE = 24; //three.js 场景中的世界尺寸,只是显示尺寸不是真实的cfd物理尺寸
|
|
35
|
+
//云图组件的主体部分
|
|
36
|
+
var CloudVisualization = function CloudVisualization(_ref) {
|
|
37
|
+
var _ref$dataUrl = _ref.dataUrl,
|
|
38
|
+
dataUrl = _ref$dataUrl === void 0 ? '' : _ref$dataUrl,
|
|
39
|
+
_ref$field = _ref.field,
|
|
40
|
+
field = _ref$field === void 0 ? 'pressure' : _ref$field,
|
|
41
|
+
_ref$viewMode = _ref.viewMode,
|
|
42
|
+
viewMode = _ref$viewMode === void 0 ? '3d' : _ref$viewMode,
|
|
43
|
+
_ref$displayMode = _ref.displayMode,
|
|
44
|
+
displayMode = _ref$displayMode === void 0 ? 'surface' : _ref$displayMode,
|
|
45
|
+
_ref$colorMap = _ref.colorMap,
|
|
46
|
+
colorMap = _ref$colorMap === void 0 ? 'jet' : _ref$colorMap,
|
|
47
|
+
_ref$opacity = _ref.opacity,
|
|
48
|
+
opacity = _ref$opacity === void 0 ? 1 : _ref$opacity,
|
|
49
|
+
_ref$sliceOrigin = _ref.sliceOrigin,
|
|
50
|
+
sliceOrigin = _ref$sliceOrigin === void 0 ? 0.5 : _ref$sliceOrigin,
|
|
51
|
+
_ref$isoValue = _ref.isoValue,
|
|
52
|
+
isoValue = _ref$isoValue === void 0 ? 0.5 : _ref$isoValue,
|
|
53
|
+
_ref$background = _ref.background,
|
|
54
|
+
background = _ref$background === void 0 ? '#061526' : _ref$background,
|
|
55
|
+
_ref$style = _ref.style,
|
|
56
|
+
style = _ref$style === void 0 ? {} : _ref$style;
|
|
57
|
+
/**
|
|
58
|
+
* viewportRef: three.js canvas 挂载容器
|
|
59
|
+
* viewerRef: 保存 renderer/scene/camera 等对象
|
|
60
|
+
*/
|
|
61
|
+
var viewportRef = (0, _react.useRef)(null);
|
|
62
|
+
var viewerRef = (0, _react.useRef)(null);
|
|
63
|
+
/**
|
|
64
|
+
* dataset: 解析后的 CSV 数据
|
|
65
|
+
* dataStatus: 右下角显示当前数据来源
|
|
66
|
+
*/
|
|
67
|
+
var _useState = (0, _react.useState)(null),
|
|
68
|
+
dataset = _useState[0],
|
|
69
|
+
setDataset = _useState[1];
|
|
70
|
+
var _useState2 = (0, _react.useState)('demo-field'),
|
|
71
|
+
dataStatus = _useState2[0],
|
|
72
|
+
setDataStatus = _useState2[1];
|
|
73
|
+
//合并外部传入样式和默认背景
|
|
74
|
+
var mergedStyle = (0, _extends2["default"])({}, style, {
|
|
75
|
+
background: background
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* 根据当前模式,生成右下角显示的文字标签
|
|
79
|
+
*/
|
|
80
|
+
var displayLabel = (0, _react.useMemo)(function () {
|
|
81
|
+
if (viewMode === '2d') return '2D 云图';
|
|
82
|
+
if (displayMode === 'slice') return '3D 切面';
|
|
83
|
+
if (displayMode === 'isosurface') return '3D 等值面';
|
|
84
|
+
return '3D 表面';
|
|
85
|
+
}, [viewMode, displayMode]);
|
|
86
|
+
/**
|
|
87
|
+
* 作用:
|
|
88
|
+
* 当 dataUrl 变化时,尝试去加载 CSV 文件
|
|
89
|
+
* 成功则解析成 dataset
|
|
90
|
+
* 失败则退回演示模式
|
|
91
|
+
*/
|
|
92
|
+
(0, _react.useEffect)(function () {
|
|
93
|
+
var disposed = false;
|
|
94
|
+
function loadCsvDataset() {
|
|
95
|
+
return _loadCsvDataset.apply(this, arguments);
|
|
96
|
+
}
|
|
97
|
+
function _loadCsvDataset() {
|
|
98
|
+
_loadCsvDataset = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee() {
|
|
99
|
+
var res, text, parsed, _t;
|
|
100
|
+
return _regenerator["default"].wrap(function (_context) {
|
|
101
|
+
while (1) switch (_context.prev = _context.next) {
|
|
102
|
+
case 0:
|
|
103
|
+
if (dataUrl) {
|
|
104
|
+
_context.next = 1;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
setDataset(null);
|
|
108
|
+
setDataStatus('demo-field');
|
|
109
|
+
return _context.abrupt("return");
|
|
110
|
+
case 1:
|
|
111
|
+
_context.prev = 1;
|
|
112
|
+
_context.next = 2;
|
|
113
|
+
return fetch(dataUrl);
|
|
114
|
+
case 2:
|
|
115
|
+
res = _context.sent;
|
|
116
|
+
_context.next = 3;
|
|
117
|
+
return res.text();
|
|
118
|
+
case 3:
|
|
119
|
+
text = _context.sent;
|
|
120
|
+
if (res.ok) {
|
|
121
|
+
_context.next = 4;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
throw new Error("HTTP " + res.status);
|
|
125
|
+
case 4:
|
|
126
|
+
// 解析 CSV 文本为结构化数据
|
|
127
|
+
parsed = parseCsvToDataset(text);
|
|
128
|
+
if (!disposed) {
|
|
129
|
+
setDataset(parsed);
|
|
130
|
+
setDataStatus(dataUrl);
|
|
131
|
+
}
|
|
132
|
+
_context.next = 6;
|
|
133
|
+
break;
|
|
134
|
+
case 5:
|
|
135
|
+
_context.prev = 5;
|
|
136
|
+
_t = _context["catch"](1);
|
|
137
|
+
// 加载失败时,回退到 demo 模式
|
|
138
|
+
if (!disposed) {
|
|
139
|
+
setDataset(null);
|
|
140
|
+
setDataStatus('demo-field (csv load failed)');
|
|
141
|
+
}
|
|
142
|
+
case 6:
|
|
143
|
+
case "end":
|
|
144
|
+
return _context.stop();
|
|
145
|
+
}
|
|
146
|
+
}, _callee, null, [[1, 5]]);
|
|
147
|
+
}));
|
|
148
|
+
return _loadCsvDataset.apply(this, arguments);
|
|
149
|
+
}
|
|
150
|
+
loadCsvDataset();
|
|
151
|
+
return function () {
|
|
152
|
+
disposed = true;
|
|
153
|
+
};
|
|
154
|
+
}, [dataUrl]);
|
|
155
|
+
/**
|
|
156
|
+
* 作用:
|
|
157
|
+
* 当 field / viewMode / displayMode / dataset 等变化时
|
|
158
|
+
* 重新创建 three.js 场景并渲染最新画面
|
|
159
|
+
*/
|
|
160
|
+
(0, _react.useEffect)(function () {
|
|
161
|
+
var mountNode = viewportRef.current;
|
|
162
|
+
if (!mountNode) return;
|
|
163
|
+
// 每次重新渲染前,先销毁旧场景
|
|
164
|
+
destroyViewer();
|
|
165
|
+
var width = Math.max(mountNode.clientWidth, 320);
|
|
166
|
+
var height = Math.max(mountNode.clientHeight, 240);
|
|
167
|
+
var aspect = width / height;
|
|
168
|
+
// 创建场景
|
|
169
|
+
var scene = new THREE.Scene();
|
|
170
|
+
scene.background = new THREE.Color(background);
|
|
171
|
+
var camera;
|
|
172
|
+
/**
|
|
173
|
+
* 2D 模式:使用正交相机
|
|
174
|
+
* 3D 模式:使用透视相机
|
|
175
|
+
*/
|
|
176
|
+
if (viewMode === '2d') {
|
|
177
|
+
var frustum = 16;
|
|
178
|
+
camera = new THREE.OrthographicCamera(-frustum * aspect, frustum * aspect, frustum, -frustum, 0.1, 1000);
|
|
179
|
+
camera.position.set(0, 0, 60);
|
|
180
|
+
camera.lookAt(0, 0, 0);
|
|
181
|
+
} else {
|
|
182
|
+
camera = new THREE.PerspectiveCamera(45, aspect, 0.1, 1000);
|
|
183
|
+
camera.position.set(24, 18, 24);
|
|
184
|
+
camera.lookAt(0, 0, 0);
|
|
185
|
+
}
|
|
186
|
+
var renderer = new THREE.WebGLRenderer({
|
|
187
|
+
antialias: true,
|
|
188
|
+
alpha: false
|
|
189
|
+
});
|
|
190
|
+
renderer.setPixelRatio(window.devicePixelRatio || 1);
|
|
191
|
+
renderer.setSize(width, height);
|
|
192
|
+
// 清空挂载点并插入 canvas
|
|
193
|
+
mountNode.innerHTML = '';
|
|
194
|
+
mountNode.appendChild(renderer.domElement);
|
|
195
|
+
var cleanupFns = [];
|
|
196
|
+
/**
|
|
197
|
+
* 3D 模式下添加灯光
|
|
198
|
+
* 2D 模式不需要复杂灯光
|
|
199
|
+
*/
|
|
200
|
+
if (viewMode === '3d') {
|
|
201
|
+
var ambientLight = new THREE.AmbientLight(0xffffff, 0.85);
|
|
202
|
+
var directionalLight = new THREE.DirectionalLight(0xffffff, 1.1);
|
|
203
|
+
directionalLight.position.set(18, 24, 12);
|
|
204
|
+
scene.add(ambientLight);
|
|
205
|
+
scene.add(directionalLight);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 坐标轴辅助线
|
|
209
|
+
* 只在 3D 模式下显示
|
|
210
|
+
*/
|
|
211
|
+
var axesHelper = new THREE.AxesHelper(10);
|
|
212
|
+
axesHelper.position.set(-10, -8, -10);
|
|
213
|
+
if (viewMode === '3d') {
|
|
214
|
+
scene.add(axesHelper);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 根据当前属性,构建实际可视化对象
|
|
218
|
+
* 比如 2D 平面、3D 曲面、切面、等值区域等
|
|
219
|
+
*/
|
|
220
|
+
var contentCleanup = buildVisualization({
|
|
221
|
+
scene: scene,
|
|
222
|
+
field: field,
|
|
223
|
+
viewMode: viewMode,
|
|
224
|
+
displayMode: displayMode,
|
|
225
|
+
colorMap: colorMap,
|
|
226
|
+
opacity: opacity,
|
|
227
|
+
sliceOrigin: sliceOrigin,
|
|
228
|
+
isoValue: isoValue,
|
|
229
|
+
dataset: dataset
|
|
230
|
+
});
|
|
231
|
+
cleanupFns.push(contentCleanup);
|
|
232
|
+
/**
|
|
233
|
+
* 动画循环
|
|
234
|
+
* 3D 模式下让相机缓慢绕场景转动,增强展示效果
|
|
235
|
+
*/
|
|
236
|
+
var angle = 0;
|
|
237
|
+
var _animate = function animate() {
|
|
238
|
+
if (viewMode === '3d' && camera instanceof THREE.PerspectiveCamera) {
|
|
239
|
+
angle += 0.005;
|
|
240
|
+
camera.position.x = Math.cos(angle) * 30;
|
|
241
|
+
camera.position.z = Math.sin(angle) * 30;
|
|
242
|
+
camera.lookAt(0, 0, 0);
|
|
243
|
+
}
|
|
244
|
+
renderer.render(scene, camera);
|
|
245
|
+
var frameId = requestAnimationFrame(_animate);
|
|
246
|
+
if (viewerRef.current) {
|
|
247
|
+
viewerRef.current.frameId = frameId;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* 监听窗口大小变化,更新相机和渲染器尺寸
|
|
252
|
+
*/
|
|
253
|
+
var handleResize = function handleResize() {
|
|
254
|
+
if (!viewportRef.current || !viewerRef.current) return;
|
|
255
|
+
var nextWidth = Math.max(viewportRef.current.clientWidth, 320);
|
|
256
|
+
var nextHeight = Math.max(viewportRef.current.clientHeight, 240);
|
|
257
|
+
var nextAspect = nextWidth / nextHeight;
|
|
258
|
+
renderer.setSize(nextWidth, nextHeight);
|
|
259
|
+
if (viewerRef.current.camera instanceof THREE.PerspectiveCamera) {
|
|
260
|
+
viewerRef.current.camera.aspect = nextAspect;
|
|
261
|
+
viewerRef.current.camera.updateProjectionMatrix();
|
|
262
|
+
} else if (viewerRef.current.camera instanceof THREE.OrthographicCamera) {
|
|
263
|
+
var _frustum = 16;
|
|
264
|
+
viewerRef.current.camera.left = -_frustum * nextAspect;
|
|
265
|
+
viewerRef.current.camera.right = _frustum * nextAspect;
|
|
266
|
+
viewerRef.current.camera.top = _frustum;
|
|
267
|
+
viewerRef.current.camera.bottom = -_frustum;
|
|
268
|
+
viewerRef.current.camera.updateProjectionMatrix();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
window.addEventListener('resize', handleResize);
|
|
272
|
+
cleanupFns.push(function () {
|
|
273
|
+
return window.removeEventListener('resize', handleResize);
|
|
274
|
+
});
|
|
275
|
+
//保存当前的three.js环境
|
|
276
|
+
viewerRef.current = {
|
|
277
|
+
renderer: renderer,
|
|
278
|
+
scene: scene,
|
|
279
|
+
camera: camera,
|
|
280
|
+
frameId: 0,
|
|
281
|
+
cleanupFns: cleanupFns
|
|
282
|
+
};
|
|
283
|
+
_animate();
|
|
284
|
+
return function () {
|
|
285
|
+
destroyViewer();
|
|
286
|
+
};
|
|
287
|
+
}, [field, viewMode, displayMode, colorMap, opacity, sliceOrigin, isoValue, background, dataset]);
|
|
288
|
+
/**
|
|
289
|
+
* 销毁旧的 three.js 资源
|
|
290
|
+
* 包括动画帧、事件、renderer、canvas 等
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
function destroyViewer() {
|
|
294
|
+
var current = viewerRef.current;
|
|
295
|
+
if (!current) return;
|
|
296
|
+
if (current.frameId) {
|
|
297
|
+
cancelAnimationFrame(current.frameId);
|
|
298
|
+
}
|
|
299
|
+
current.cleanupFns.forEach(function (fn) {
|
|
300
|
+
return fn();
|
|
301
|
+
});
|
|
302
|
+
current.renderer.dispose();
|
|
303
|
+
if (current.renderer.domElement && current.renderer.domElement.parentNode) {
|
|
304
|
+
current.renderer.domElement.parentNode.removeChild(current.renderer.domElement);
|
|
305
|
+
}
|
|
306
|
+
viewerRef.current = null;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 组件最终渲染结构:
|
|
310
|
+
* 顶部工具条 + 中间 viewport + 右下角图例信息
|
|
311
|
+
*/
|
|
312
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
313
|
+
className: _variables.bizCssPrefix + "-cloud-visualization",
|
|
314
|
+
style: mergedStyle
|
|
315
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
316
|
+
className: _variables.bizCssPrefix + "-cloud-visualization__toolbar"
|
|
317
|
+
}, /*#__PURE__*/React.createElement("span", null, "field: ", field), /*#__PURE__*/React.createElement("span", null, "mode: ", displayMode), /*#__PURE__*/React.createElement("span", null, "view: ", viewMode), /*#__PURE__*/React.createElement("span", null, "map: ", colorMap), /*#__PURE__*/React.createElement("span", null, "opacity: ", opacity)), /*#__PURE__*/React.createElement("div", {
|
|
318
|
+
ref: viewportRef,
|
|
319
|
+
className: _variables.bizCssPrefix + "-cloud-visualization__viewport"
|
|
320
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
321
|
+
className: _variables.bizCssPrefix + "-cloud-visualization__legend"
|
|
322
|
+
}, /*#__PURE__*/React.createElement("div", null, displayLabel), /*#__PURE__*/React.createElement("div", null, "slice: ", sliceOrigin), /*#__PURE__*/React.createElement("div", null, "iso: ", isoValue), /*#__PURE__*/React.createElement("div", null, "data: ", dataStatus)));
|
|
323
|
+
};
|
|
324
|
+
CloudVisualization.displayName = 'CloudVisualization';
|
|
325
|
+
var _default = exports["default"] = CloudVisualization;
|
|
326
|
+
/**
|
|
327
|
+
* 根据当前模式,向 scene 中添加不同可视化对象
|
|
328
|
+
*/
|
|
329
|
+
function buildVisualization(params) {
|
|
330
|
+
var scene = params.scene,
|
|
331
|
+
field = params.field,
|
|
332
|
+
viewMode = params.viewMode,
|
|
333
|
+
displayMode = params.displayMode,
|
|
334
|
+
colorMap = params.colorMap,
|
|
335
|
+
opacity = params.opacity,
|
|
336
|
+
sliceOrigin = params.sliceOrigin,
|
|
337
|
+
isoValue = params.isoValue,
|
|
338
|
+
dataset = params.dataset;
|
|
339
|
+
var objects = [];
|
|
340
|
+
// 添加对象到场景,并记录下来,方便后续统一销毁
|
|
341
|
+
var addObject = function addObject(obj) {
|
|
342
|
+
scene.add(obj);
|
|
343
|
+
objects.push(obj);
|
|
344
|
+
};
|
|
345
|
+
//2d只绘制平面热力图
|
|
346
|
+
if (viewMode === '2d') {
|
|
347
|
+
addObject(createHeatmapPlane(field, colorMap, opacity, dataset, sliceOrigin));
|
|
348
|
+
} else if (displayMode === 'surface') {
|
|
349
|
+
/**
|
|
350
|
+
* 3D 表面模式:
|
|
351
|
+
* 用标量值映射曲面高度
|
|
352
|
+
*/
|
|
353
|
+
addObject(createSurfaceMesh(field, colorMap, opacity, dataset, sliceOrigin));
|
|
354
|
+
addObject(createWireFrameBox());
|
|
355
|
+
} else if (displayMode === 'slice') {
|
|
356
|
+
/**
|
|
357
|
+
* 3D 切面模式:
|
|
358
|
+
* 一个半透明表面 + 一个真实切片平面
|
|
359
|
+
*/
|
|
360
|
+
var surface = createSurfaceMesh(field, colorMap, Math.min(opacity, 0.35), dataset, sliceOrigin);
|
|
361
|
+
surface.position.y = -2;
|
|
362
|
+
addObject(surface);
|
|
363
|
+
addObject(createSlicePlane(field, colorMap, opacity, sliceOrigin, dataset));
|
|
364
|
+
addObject(createWireFrameBox());
|
|
365
|
+
} else {
|
|
366
|
+
/**
|
|
367
|
+
* 3D 等值区域模式:
|
|
368
|
+
* 当前用小球群近似表示等值面区域
|
|
369
|
+
*/
|
|
370
|
+
addObject(createIsoSurfaceGroup(field, colorMap, opacity, isoValue, dataset));
|
|
371
|
+
addObject(createWireFrameBox());
|
|
372
|
+
}
|
|
373
|
+
return function () {
|
|
374
|
+
objects.forEach(function (obj) {
|
|
375
|
+
scene.remove(obj);
|
|
376
|
+
disposeObject3D(obj);
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 创建二维热力图平面
|
|
382
|
+
* 平面上每个顶点根据 field 值着色
|
|
383
|
+
*/
|
|
384
|
+
function createHeatmapPlane(field, colorMap, opacity, dataset, sliceOrigin) {
|
|
385
|
+
var geometry = new THREE.PlaneGeometry(WORLD_SIZE, WORLD_SIZE, GRID_SIZE - 1, GRID_SIZE - 1);
|
|
386
|
+
var colors = new Float32Array(GRID_SIZE * GRID_SIZE * 3);
|
|
387
|
+
var positions = geometry.attributes.position;
|
|
388
|
+
for (var i = 0; i < positions.count; i++) {
|
|
389
|
+
var x = positions.getX(i);
|
|
390
|
+
var y = positions.getY(i);
|
|
391
|
+
// 将 three.js 场景坐标映射到 0~1 的归一化空间坐标
|
|
392
|
+
var u = (x + WORLD_SIZE / 2) / WORLD_SIZE;
|
|
393
|
+
var v = (y + WORLD_SIZE / 2) / WORLD_SIZE;
|
|
394
|
+
// 根据当前 field,在 dataset 中取值;如果没有 dataset,则走模拟数据
|
|
395
|
+
var value = sampleFieldValue(dataset, field, u, v, sliceOrigin);
|
|
396
|
+
var color = getColorByMap(value, colorMap);
|
|
397
|
+
colors[i * 3] = color.r;
|
|
398
|
+
colors[i * 3 + 1] = color.g;
|
|
399
|
+
colors[i * 3 + 2] = color.b;
|
|
400
|
+
}
|
|
401
|
+
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
402
|
+
return new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
|
|
403
|
+
vertexColors: true,
|
|
404
|
+
transparent: true,
|
|
405
|
+
opacity: opacity,
|
|
406
|
+
side: THREE.DoubleSide
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 创建三维表面
|
|
411
|
+
* 核心做法:
|
|
412
|
+
* 用 field 值控制平面顶点高度,并通过颜色显示强弱
|
|
413
|
+
*/
|
|
414
|
+
function createSurfaceMesh(field, colorMap, opacity, dataset, sliceOrigin) {
|
|
415
|
+
var geometry = new THREE.PlaneGeometry(WORLD_SIZE, WORLD_SIZE, GRID_SIZE - 1, GRID_SIZE - 1);
|
|
416
|
+
var positions = geometry.attributes.position;
|
|
417
|
+
var colors = new Float32Array(positions.count * 3);
|
|
418
|
+
for (var i = 0; i < positions.count; i++) {
|
|
419
|
+
var x = positions.getX(i);
|
|
420
|
+
var z = positions.getY(i);
|
|
421
|
+
var u = (x + WORLD_SIZE / 2) / WORLD_SIZE;
|
|
422
|
+
var v = (z + WORLD_SIZE / 2) / WORLD_SIZE;
|
|
423
|
+
// 从数据中采样得到该点的物理量值
|
|
424
|
+
var value = sampleFieldValue(dataset, field, u, v, sliceOrigin);
|
|
425
|
+
var height = (value - 0.5) * 8; // 将数值映射为表面高度
|
|
426
|
+
|
|
427
|
+
positions.setZ(i, height);
|
|
428
|
+
// 将数值映射为颜色
|
|
429
|
+
var color = getColorByMap(value, colorMap);
|
|
430
|
+
colors[i * 3] = color.r;
|
|
431
|
+
colors[i * 3 + 1] = color.g;
|
|
432
|
+
colors[i * 3 + 2] = color.b;
|
|
433
|
+
}
|
|
434
|
+
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
435
|
+
geometry.computeVertexNormals();
|
|
436
|
+
// 旋转成水平朝上的表面
|
|
437
|
+
var mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({
|
|
438
|
+
vertexColors: true,
|
|
439
|
+
transparent: true,
|
|
440
|
+
opacity: opacity,
|
|
441
|
+
side: THREE.DoubleSide,
|
|
442
|
+
shininess: 45
|
|
443
|
+
}));
|
|
444
|
+
mesh.rotation.x = -Math.PI / 2;
|
|
445
|
+
return mesh;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* 创建切面平面
|
|
449
|
+
* 这里用 sliceOrigin 表示 x 方向上的切片位置
|
|
450
|
+
*/
|
|
451
|
+
function createSlicePlane(field, colorMap, opacity, sliceOrigin, dataset) {
|
|
452
|
+
var geometry = new THREE.PlaneGeometry(WORLD_SIZE, WORLD_SIZE, GRID_SIZE - 1, GRID_SIZE - 1);
|
|
453
|
+
var positions = geometry.attributes.position;
|
|
454
|
+
var colors = new Float32Array(GRID_SIZE * GRID_SIZE * 3);
|
|
455
|
+
for (var i = 0; i < positions.count; i++) {
|
|
456
|
+
var x = positions.getX(i);
|
|
457
|
+
var y = positions.getY(i);
|
|
458
|
+
var u = (x + WORLD_SIZE / 2) / WORLD_SIZE;
|
|
459
|
+
var v = (y + WORLD_SIZE / 2) / WORLD_SIZE;
|
|
460
|
+
// 固定一个 x 切片位置,在该切面上取值
|
|
461
|
+
var value = sampleFieldValue(dataset, field, sliceOrigin, u, v);
|
|
462
|
+
var color = getColorByMap(value, colorMap);
|
|
463
|
+
colors[i * 3] = color.r;
|
|
464
|
+
colors[i * 3 + 1] = color.g;
|
|
465
|
+
colors[i * 3 + 2] = color.b;
|
|
466
|
+
}
|
|
467
|
+
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
468
|
+
var mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
|
|
469
|
+
vertexColors: true,
|
|
470
|
+
transparent: true,
|
|
471
|
+
opacity: opacity,
|
|
472
|
+
side: THREE.DoubleSide
|
|
473
|
+
}));
|
|
474
|
+
mesh.rotation.y = Math.PI / 2; // 旋转到 yz 平面
|
|
475
|
+
mesh.position.x = (sliceOrigin - 0.5) * WORLD_SIZE; // 根据 sliceOrigin 调整切片位置
|
|
476
|
+
return mesh;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* 创建等值区域
|
|
480
|
+
* 当前不是工业级 marching cubes
|
|
481
|
+
* 而是用小球群表示“接近 isoValue 的区域”
|
|
482
|
+
*/
|
|
483
|
+
function createIsoSurfaceGroup(field, colorMap, opacity, isoValue, dataset) {
|
|
484
|
+
var group = new THREE.Group();
|
|
485
|
+
var sphereGeometry = new THREE.SphereGeometry(0.42, 14, 14);
|
|
486
|
+
for (var xi = 0; xi < 14; xi++) {
|
|
487
|
+
for (var yi = 0; yi < 14; yi++) {
|
|
488
|
+
for (var zi = 0; zi < 14; zi++) {
|
|
489
|
+
var u = xi / 13;
|
|
490
|
+
var v = yi / 13;
|
|
491
|
+
var w = zi / 13;
|
|
492
|
+
var value = sampleFieldValue(dataset, field, u, v, w);
|
|
493
|
+
// 接近阈值的区域用球体表示
|
|
494
|
+
if (Math.abs(value - isoValue) < 0.06) {
|
|
495
|
+
var color = getColorByMap(value, colorMap);
|
|
496
|
+
var material = new THREE.MeshPhongMaterial({
|
|
497
|
+
color: color,
|
|
498
|
+
transparent: true,
|
|
499
|
+
opacity: opacity
|
|
500
|
+
});
|
|
501
|
+
var sphere = new THREE.Mesh(sphereGeometry, material);
|
|
502
|
+
sphere.position.set((u - 0.5) * WORLD_SIZE, (v - 0.5) * WORLD_SIZE, (w - 0.5) * WORLD_SIZE);
|
|
503
|
+
group.add(sphere);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return group;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* 从 dataset 中按当前 field 取值
|
|
512
|
+
* 如果 dataset 不存在,则退回模拟函数 getScalarValue
|
|
513
|
+
*/
|
|
514
|
+
function sampleFieldValue(dataset, field, u, v, w) {
|
|
515
|
+
if (!dataset) {
|
|
516
|
+
return getScalarValue(u, v, w, field);
|
|
517
|
+
}
|
|
518
|
+
// 在 CSV 点集中找最近点
|
|
519
|
+
var point = findNearestPoint(dataset, u, v, w);
|
|
520
|
+
var rawValue = point[field];
|
|
521
|
+
var range = dataset.fieldRanges[field];
|
|
522
|
+
if (range.max === range.min) return 0.5; // 避免除0
|
|
523
|
+
return clamp01((rawValue - range.min) / (range.max - range.min)); // 将当前字段归一化到 0~1
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* 最近邻采样
|
|
527
|
+
* 将归一化坐标 u,v,w 映射回真实坐标范围
|
|
528
|
+
* 再在 dataset.points 中找最近点
|
|
529
|
+
*/
|
|
530
|
+
function findNearestPoint(dataset, u, v, w) {
|
|
531
|
+
var x = lerp(dataset.xMin, dataset.xMax, clamp01(u));
|
|
532
|
+
var y = lerp(dataset.yMin, dataset.yMax, clamp01(v));
|
|
533
|
+
var z = lerp(dataset.zMin, dataset.zMax, clamp01(w));
|
|
534
|
+
var best = dataset.points[0];
|
|
535
|
+
var bestDist = Number.MAX_VALUE;
|
|
536
|
+
for (var i = 0; i < dataset.points.length; i++) {
|
|
537
|
+
var p = dataset.points[i];
|
|
538
|
+
var dx = p.x - x;
|
|
539
|
+
var dy = p.y - y;
|
|
540
|
+
var dz = p.z - z;
|
|
541
|
+
var dist = dx * dx + dy * dy + dz * dz;
|
|
542
|
+
if (dist < bestDist) {
|
|
543
|
+
bestDist = dist;
|
|
544
|
+
best = p;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return best;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* 将 CSV 文本解析为 GridDataset
|
|
551
|
+
* 这里会:
|
|
552
|
+
* 1. 检查表头
|
|
553
|
+
* 2. 转为点数组
|
|
554
|
+
* 3. 统计坐标范围
|
|
555
|
+
* 4. 统计各字段 min/max
|
|
556
|
+
*/
|
|
557
|
+
function parseCsvToDataset(text) {
|
|
558
|
+
var lines = text.split(/\r?\n/).map(function (line) {
|
|
559
|
+
return line.trim();
|
|
560
|
+
}).filter(Boolean);
|
|
561
|
+
if (lines.length < 2) {
|
|
562
|
+
throw new Error('CSV empty');
|
|
563
|
+
}
|
|
564
|
+
var header = lines[0].split(',').map(function (s) {
|
|
565
|
+
return s.trim();
|
|
566
|
+
});
|
|
567
|
+
var indexMap = {};
|
|
568
|
+
header.forEach(function (name, idx) {
|
|
569
|
+
indexMap[name] = idx;
|
|
570
|
+
});
|
|
571
|
+
//检查必填字段
|
|
572
|
+
var required = ['x', 'y', 'z', 'pressure', 'temperature', 'velocity', 'vorticity', 'mach'];
|
|
573
|
+
required.forEach(function (name) {
|
|
574
|
+
if (indexMap[name] === undefined) {
|
|
575
|
+
throw new Error("CSV missing column: " + name);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
//从每一行生成点数据
|
|
579
|
+
var points = lines.slice(1).map(function (line) {
|
|
580
|
+
var cols = line.split(',').map(function (s) {
|
|
581
|
+
return s.trim();
|
|
582
|
+
});
|
|
583
|
+
return {
|
|
584
|
+
x: Number(cols[indexMap.x]),
|
|
585
|
+
y: Number(cols[indexMap.y]),
|
|
586
|
+
z: Number(cols[indexMap.z]),
|
|
587
|
+
pressure: Number(cols[indexMap.pressure]),
|
|
588
|
+
temperature: Number(cols[indexMap.temperature]),
|
|
589
|
+
velocity: Number(cols[indexMap.velocity]),
|
|
590
|
+
vorticity: Number(cols[indexMap.vorticity]),
|
|
591
|
+
mach: Number(cols[indexMap.mach])
|
|
592
|
+
};
|
|
593
|
+
});
|
|
594
|
+
//提取每个坐标轴上的唯一值
|
|
595
|
+
var xValues = uniqueSorted(points.map(function (p) {
|
|
596
|
+
return p.x;
|
|
597
|
+
}));
|
|
598
|
+
var yValues = uniqueSorted(points.map(function (p) {
|
|
599
|
+
return p.y;
|
|
600
|
+
}));
|
|
601
|
+
var zValues = uniqueSorted(points.map(function (p) {
|
|
602
|
+
return p.z;
|
|
603
|
+
}));
|
|
604
|
+
var xMin = xValues[0];
|
|
605
|
+
var xMax = xValues[xValues.length - 1];
|
|
606
|
+
var yMin = yValues[0];
|
|
607
|
+
var yMax = yValues[yValues.length - 1];
|
|
608
|
+
var zMin = zValues[0];
|
|
609
|
+
var zMax = zValues[zValues.length - 1];
|
|
610
|
+
//统计每个物理量范围
|
|
611
|
+
var fields = ['pressure', 'temperature', 'velocity', 'vorticity', 'mach'];
|
|
612
|
+
var fieldRanges = {};
|
|
613
|
+
fields.forEach(function (field) {
|
|
614
|
+
var values = points.map(function (p) {
|
|
615
|
+
return p[field];
|
|
616
|
+
});
|
|
617
|
+
fieldRanges[field] = {
|
|
618
|
+
min: Math.min.apply(null, values),
|
|
619
|
+
max: Math.max.apply(null, values)
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
points: points,
|
|
624
|
+
xValues: xValues,
|
|
625
|
+
yValues: yValues,
|
|
626
|
+
zValues: zValues,
|
|
627
|
+
xMin: xMin,
|
|
628
|
+
xMax: xMax,
|
|
629
|
+
yMin: yMin,
|
|
630
|
+
yMax: yMax,
|
|
631
|
+
zMin: zMin,
|
|
632
|
+
zMax: zMax,
|
|
633
|
+
fieldRanges: fieldRanges
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
//数组去重并排序
|
|
637
|
+
function uniqueSorted(values) {
|
|
638
|
+
var set = {};
|
|
639
|
+
values.forEach(function (v) {
|
|
640
|
+
set[String(v)] = true;
|
|
641
|
+
});
|
|
642
|
+
return Object.keys(set).map(function (v) {
|
|
643
|
+
return Number(v);
|
|
644
|
+
}).sort(function (a, b) {
|
|
645
|
+
return a - b;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
//线性插值
|
|
649
|
+
function lerp(a, b, t) {
|
|
650
|
+
return a + (b - a) * t;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* 创建线框包围盒
|
|
654
|
+
* 用于辅助观察 3D 空间边界
|
|
655
|
+
*/
|
|
656
|
+
function createWireFrameBox() {
|
|
657
|
+
var geometry = new THREE.BoxGeometry(WORLD_SIZE, WORLD_SIZE, WORLD_SIZE);
|
|
658
|
+
var edges = new THREE.EdgesGeometry(geometry);
|
|
659
|
+
var material = new THREE.LineBasicMaterial({
|
|
660
|
+
color: 0x6fa8ff,
|
|
661
|
+
transparent: true,
|
|
662
|
+
opacity: 0.35
|
|
663
|
+
});
|
|
664
|
+
return new THREE.LineSegments(edges, material);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* 默认模拟函数
|
|
668
|
+
* 当没有真实 CSV 数据时,用这个函数生成演示场
|
|
669
|
+
*/
|
|
670
|
+
function getScalarValue(u, v, w, field) {
|
|
671
|
+
switch (field) {
|
|
672
|
+
case 'velocity':
|
|
673
|
+
return clamp01(0.5 + 0.5 * Math.sin(2 * Math.PI * u) * Math.cos(2 * Math.PI * v));
|
|
674
|
+
case 'pressure':
|
|
675
|
+
return clamp01(0.5 + 0.35 * Math.sin(3 * Math.PI * u) + 0.15 * Math.cos(2 * Math.PI * w));
|
|
676
|
+
case 'temperature':
|
|
677
|
+
return clamp01((u + v + w) / 3);
|
|
678
|
+
case 'vorticity':
|
|
679
|
+
return clamp01(0.5 + 0.5 * Math.sin(4 * Math.PI * u) * Math.sin(4 * Math.PI * v) * Math.cos(2 * Math.PI * w));
|
|
680
|
+
case 'mach':
|
|
681
|
+
return clamp01(Math.sqrt((u * u + v * v + w * w) / 3));
|
|
682
|
+
default:
|
|
683
|
+
return 0.5;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
//限制数值在0~1
|
|
687
|
+
function clamp01(value) {
|
|
688
|
+
return Math.max(0, Math.min(1, value));
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* 根据色带名称和归一化数值,得到最终颜色
|
|
692
|
+
*/
|
|
693
|
+
function getColorByMap(value, colorMap) {
|
|
694
|
+
var stops = getColorStops(colorMap);
|
|
695
|
+
return sampleColorStops(stops, value);
|
|
696
|
+
}
|
|
697
|
+
function getColorStops(colorMap) {
|
|
698
|
+
switch (colorMap) {
|
|
699
|
+
case 'jet':
|
|
700
|
+
return ['#00007f', '#004cff', '#29b6ff', '#7dff7a', '#ffe600', '#ff7a00', '#cc0000'];
|
|
701
|
+
case 'coolwarm':
|
|
702
|
+
return ['#3b4cc0', '#6f92f3', '#c8d8f0', '#f2cbb7', '#e67c73', '#b40426'];
|
|
703
|
+
case 'rainbow':
|
|
704
|
+
return ['#6a00ff', '#005eff', '#00c8ff', '#00d26a', '#ffe600', '#ff8c00', '#ff003c'];
|
|
705
|
+
case 'viridis':
|
|
706
|
+
return ['#440154', '#414487', '#2a788e', '#22a884', '#7ad151', '#fde725'];
|
|
707
|
+
default:
|
|
708
|
+
return ['#000000', '#ffffff'];
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* 在颜色控制点之间做线性插值
|
|
713
|
+
* 这样可以让颜色连续变化
|
|
714
|
+
*/
|
|
715
|
+
function sampleColorStops(stops, t) {
|
|
716
|
+
var value = clamp01(t);
|
|
717
|
+
var scaled = value * (stops.length - 1);
|
|
718
|
+
var index = Math.floor(scaled);
|
|
719
|
+
var ratio = scaled - index;
|
|
720
|
+
var c1 = new THREE.Color(stops[index]);
|
|
721
|
+
var c2 = new THREE.Color(stops[Math.min(index + 1, stops.length - 1)]);
|
|
722
|
+
return new THREE.Color(c1.r + (c2.r - c1.r) * ratio, c1.g + (c2.g - c1.g) * ratio, c1.b + (c2.b - c1.b) * ratio);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* 销毁 three.js 对象所占资源
|
|
726
|
+
* 包括 geometry 和 material
|
|
727
|
+
*/
|
|
728
|
+
function disposeObject3D(object) {
|
|
729
|
+
object.traverse(function (child) {
|
|
730
|
+
var mesh = child;
|
|
731
|
+
if (mesh.geometry) {
|
|
732
|
+
mesh.geometry.dispose();
|
|
733
|
+
}
|
|
734
|
+
var material = mesh.material;
|
|
735
|
+
if (Array.isArray(material)) {
|
|
736
|
+
material.forEach(function (m) {
|
|
737
|
+
return m && m.dispose && m.dispose();
|
|
738
|
+
});
|
|
739
|
+
} else if (material && material.dispose) {
|
|
740
|
+
material.dispose();
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|