fl-web-component 2.0.7 → 2.0.9
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 +2 -0
- package/dist/fl-web-component.common.js +7223 -1277
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +1 -1
- package/packages/components/com-flcanvas/index.vue +0 -2
- package/packages/components/com-graphics/component/context.js +123 -0
- package/packages/components/com-graphics/index.vue +1148 -69
- package/packages/utils/StreamLoader.js +73 -16
- package/src/utils/threejs/editor/command.js +36 -0
- package/src/utils/threejs/editor/commands/add-element-command.js +41 -0
- package/src/utils/threejs/editor/commands/add-group-command.js +10 -0
- package/src/utils/threejs/editor/commands/clone-element-command.js +100 -0
- package/src/utils/threejs/editor/commands/move-element-command.js +57 -0
- package/src/utils/threejs/editor/commands/multi-command.js +29 -0
- package/src/utils/threejs/editor/commands/remove-element-command.js +46 -0
- package/src/utils/threejs/editor/commands/reset-original-model-style-command.js +30 -0
- package/src/utils/threejs/editor/commands/set-geometry-params-command.js +41 -0
- package/src/utils/threejs/editor/commands/set-original-model-style-command.js +56 -0
- package/src/utils/threejs/editor/commands/set-position-command.js +54 -0
- package/src/utils/threejs/editor/commands/set-rotation-command.js +53 -0
- package/src/utils/threejs/editor/commands/set-scale-command.js +54 -0
- package/src/utils/threejs/editor/commands/set-value-command.js +107 -0
- package/src/utils/threejs/editor/constants.js +107 -0
- package/src/utils/threejs/editor/element-factory.js +163 -0
- package/src/utils/threejs/editor/event-bus.js +34 -0
- package/src/utils/threejs/editor/history.js +80 -0
- package/src/utils/threejs/editor/scene-command-service.js +1529 -0
- package/src/utils/threejs/editor/scene-event-bridge.js +32 -0
- package/src/utils/threejs/editor/scene-helpers.js +415 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class SetPositionCommand extends Command {
|
|
5
|
+
constructor(service, uuid, newPosition, oldPosition) {
|
|
6
|
+
super(service);
|
|
7
|
+
this.type = COMMAND_TYPE.SET_POSITION;
|
|
8
|
+
this.name = '修改位置';
|
|
9
|
+
this.updatable = true;
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.oldPosition =
|
|
12
|
+
oldPosition && oldPosition.length === 3
|
|
13
|
+
? new service.THREE.Vector3(oldPosition[0], oldPosition[1], oldPosition[2])
|
|
14
|
+
: this.object
|
|
15
|
+
? this.object.position.clone()
|
|
16
|
+
: new service.THREE.Vector3();
|
|
17
|
+
this.newPosition = new service.THREE.Vector3(
|
|
18
|
+
Number(newPosition[0]) || 0,
|
|
19
|
+
Number(newPosition[1]) || 0,
|
|
20
|
+
Number(newPosition[2]) || 0
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute() {
|
|
25
|
+
if (!this.object) return;
|
|
26
|
+
this.object.position.copy(this.newPosition);
|
|
27
|
+
this.object.updateMatrixWorld(true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
undo() {
|
|
31
|
+
if (!this.object) return;
|
|
32
|
+
this.object.position.copy(this.oldPosition);
|
|
33
|
+
this.object.updateMatrixWorld(true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
37
|
+
return (
|
|
38
|
+
command &&
|
|
39
|
+
command.type === this.type &&
|
|
40
|
+
this.object &&
|
|
41
|
+
command.object &&
|
|
42
|
+
command.object.uuid === this.object.uuid &&
|
|
43
|
+
deltaTime <= mergeWindow
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
update(command) {
|
|
48
|
+
this.newPosition.copy(command.newPosition);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getChangedKeys() {
|
|
52
|
+
return ['position'];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { degreesArrayToRadians } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
export default class SetRotationCommand extends Command {
|
|
6
|
+
constructor(service, uuid, newRotation, oldRotation) {
|
|
7
|
+
super(service);
|
|
8
|
+
this.type = COMMAND_TYPE.SET_ROTATION;
|
|
9
|
+
this.name = '修改旋转';
|
|
10
|
+
this.updatable = true;
|
|
11
|
+
this.object = service.getObjectByUuid(uuid);
|
|
12
|
+
const oldRadians = Array.isArray(oldRotation) ? degreesArrayToRadians(oldRotation) : null;
|
|
13
|
+
this.oldRotation =
|
|
14
|
+
oldRadians && oldRadians.length === 3
|
|
15
|
+
? new service.THREE.Euler(oldRadians[0], oldRadians[1], oldRadians[2], 'XYZ')
|
|
16
|
+
: this.object
|
|
17
|
+
? this.object.rotation.clone()
|
|
18
|
+
: new service.THREE.Euler();
|
|
19
|
+
const newRadians = degreesArrayToRadians(newRotation);
|
|
20
|
+
this.newRotation = new service.THREE.Euler(newRadians[0], newRadians[1], newRadians[2], 'XYZ');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
execute() {
|
|
24
|
+
if (!this.object) return;
|
|
25
|
+
this.object.rotation.copy(this.newRotation);
|
|
26
|
+
this.object.updateMatrixWorld(true);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
undo() {
|
|
30
|
+
if (!this.object) return;
|
|
31
|
+
this.object.rotation.copy(this.oldRotation);
|
|
32
|
+
this.object.updateMatrixWorld(true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
36
|
+
return (
|
|
37
|
+
command &&
|
|
38
|
+
command.type === this.type &&
|
|
39
|
+
this.object &&
|
|
40
|
+
command.object &&
|
|
41
|
+
command.object.uuid === this.object.uuid &&
|
|
42
|
+
deltaTime <= mergeWindow
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
update(command) {
|
|
47
|
+
this.newRotation.copy(command.newRotation);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getChangedKeys() {
|
|
51
|
+
return ['rotation'];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class SetScaleCommand extends Command {
|
|
5
|
+
constructor(service, uuid, newScale, oldScale) {
|
|
6
|
+
super(service);
|
|
7
|
+
this.type = COMMAND_TYPE.SET_SCALE;
|
|
8
|
+
this.name = '修改缩放';
|
|
9
|
+
this.updatable = true;
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.oldScale =
|
|
12
|
+
oldScale && oldScale.length === 3
|
|
13
|
+
? new service.THREE.Vector3(oldScale[0], oldScale[1], oldScale[2])
|
|
14
|
+
: this.object
|
|
15
|
+
? this.object.scale.clone()
|
|
16
|
+
: new service.THREE.Vector3(1, 1, 1);
|
|
17
|
+
this.newScale = new service.THREE.Vector3(
|
|
18
|
+
Number(newScale[0]) || 1,
|
|
19
|
+
Number(newScale[1]) || 1,
|
|
20
|
+
Number(newScale[2]) || 1
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute() {
|
|
25
|
+
if (!this.object) return;
|
|
26
|
+
this.object.scale.copy(this.newScale);
|
|
27
|
+
this.object.updateMatrixWorld(true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
undo() {
|
|
31
|
+
if (!this.object) return;
|
|
32
|
+
this.object.scale.copy(this.oldScale);
|
|
33
|
+
this.object.updateMatrixWorld(true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
37
|
+
return (
|
|
38
|
+
command &&
|
|
39
|
+
command.type === this.type &&
|
|
40
|
+
this.object &&
|
|
41
|
+
command.object &&
|
|
42
|
+
command.object.uuid === this.object.uuid &&
|
|
43
|
+
deltaTime <= mergeWindow
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
update(command) {
|
|
48
|
+
this.newScale.copy(command.newScale);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getChangedKeys() {
|
|
52
|
+
return ['scale'];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { updateDisplayName } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
function getTargetInfo(object, propertyPath) {
|
|
6
|
+
const keys = propertyPath.split('.');
|
|
7
|
+
const propertyName = keys.pop();
|
|
8
|
+
let target = object;
|
|
9
|
+
keys.forEach(key => {
|
|
10
|
+
if (!target) return;
|
|
11
|
+
target = target[key];
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
target,
|
|
15
|
+
propertyName,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cloneValue(value) {
|
|
20
|
+
if (value === undefined || value === null) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
if (value && value.isColor) {
|
|
24
|
+
return value.clone();
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'object') {
|
|
27
|
+
return JSON.parse(JSON.stringify(value));
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assignValue(target, propertyName, value) {
|
|
33
|
+
if (!target) return;
|
|
34
|
+
if (target[propertyName] && target[propertyName].isColor) {
|
|
35
|
+
target[propertyName].set(value);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
target[propertyName] = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default class SetValueCommand extends Command {
|
|
42
|
+
constructor(service, uuid, propertyPath, newValue) {
|
|
43
|
+
super(service);
|
|
44
|
+
this.type = COMMAND_TYPE.SET_VALUE;
|
|
45
|
+
this.name = '修改属性';
|
|
46
|
+
this.updatable = true;
|
|
47
|
+
this.object = service.getObjectByUuid(uuid);
|
|
48
|
+
this.propertyPath = propertyPath;
|
|
49
|
+
const { target, propertyName } = getTargetInfo(this.object, propertyPath);
|
|
50
|
+
this.oldValue = target ? cloneValue(target[propertyName]) : undefined;
|
|
51
|
+
this.newValue = cloneValue(newValue);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
execute() {
|
|
55
|
+
if (!this.object) return;
|
|
56
|
+
const { target, propertyName } = getTargetInfo(this.object, this.propertyPath);
|
|
57
|
+
assignValue(target, propertyName, this.newValue);
|
|
58
|
+
if (this.propertyPath === 'name') {
|
|
59
|
+
updateDisplayName(this.object);
|
|
60
|
+
}
|
|
61
|
+
if (this.propertyPath === 'material.opacity' && this.object.material) {
|
|
62
|
+
this.object.material.transparent = Number(this.object.material.opacity) < 1;
|
|
63
|
+
this.object.material.needsUpdate = true;
|
|
64
|
+
}
|
|
65
|
+
if (this.propertyPath.indexOf('material.') === 0 && this.object.material) {
|
|
66
|
+
this.object.material.needsUpdate = true;
|
|
67
|
+
}
|
|
68
|
+
this.object.updateMatrixWorld(true);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
undo() {
|
|
72
|
+
if (!this.object) return;
|
|
73
|
+
const { target, propertyName } = getTargetInfo(this.object, this.propertyPath);
|
|
74
|
+
assignValue(target, propertyName, this.oldValue);
|
|
75
|
+
if (this.propertyPath === 'name') {
|
|
76
|
+
updateDisplayName(this.object);
|
|
77
|
+
}
|
|
78
|
+
if (this.propertyPath === 'material.opacity' && this.object.material) {
|
|
79
|
+
this.object.material.transparent = Number(this.object.material.opacity) < 1;
|
|
80
|
+
this.object.material.needsUpdate = true;
|
|
81
|
+
}
|
|
82
|
+
if (this.propertyPath.indexOf('material.') === 0 && this.object.material) {
|
|
83
|
+
this.object.material.needsUpdate = true;
|
|
84
|
+
}
|
|
85
|
+
this.object.updateMatrixWorld(true);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
89
|
+
return (
|
|
90
|
+
command &&
|
|
91
|
+
command.type === this.type &&
|
|
92
|
+
this.object &&
|
|
93
|
+
command.object &&
|
|
94
|
+
command.object.uuid === this.object.uuid &&
|
|
95
|
+
command.propertyPath === this.propertyPath &&
|
|
96
|
+
deltaTime <= mergeWindow
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
update(command) {
|
|
101
|
+
this.newValue = cloneValue(command.newValue);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getChangedKeys() {
|
|
105
|
+
return [this.propertyPath];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const CUSTOM_ROOT_NAME = 'custom-model-root';
|
|
2
|
+
export const CUSTOM_ROOT_DISPLAY_NAME = '自定义模型元素';
|
|
3
|
+
export const DEFAULT_ELEMENT_COLOR = '#2C8CF0';
|
|
4
|
+
export const HISTORY_MERGE_WINDOW = 500;
|
|
5
|
+
|
|
6
|
+
export const ELEMENT_TYPES = [
|
|
7
|
+
'box',
|
|
8
|
+
'tetrahedron',
|
|
9
|
+
'octahedron',
|
|
10
|
+
'cylinder',
|
|
11
|
+
'ring',
|
|
12
|
+
'sphere',
|
|
13
|
+
'capsule',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const ELEMENT_LABELS = {
|
|
17
|
+
box: '立方体',
|
|
18
|
+
tetrahedron: '四面体',
|
|
19
|
+
octahedron: '八面体',
|
|
20
|
+
cylinder: '圆柱体',
|
|
21
|
+
ring: '圆环体',
|
|
22
|
+
sphere: '球体',
|
|
23
|
+
capsule: '胶囊',
|
|
24
|
+
group: '组',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const TRANSFORM_MODE = {
|
|
28
|
+
TRANSLATE: 'translate',
|
|
29
|
+
ROTATE: 'rotate',
|
|
30
|
+
SCALE: 'scale',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const SCENE_NODE_TYPE = {
|
|
34
|
+
CUSTOM_ELEMENT: 'custom-element',
|
|
35
|
+
CUSTOM_GROUP: 'custom-group',
|
|
36
|
+
CUSTOM_ROOT: 'custom-root',
|
|
37
|
+
ORIGINAL_MODEL: 'original-model',
|
|
38
|
+
HELPER: 'helper',
|
|
39
|
+
UNKNOWN: 'unknown',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const EDITOR_EVENT = {
|
|
43
|
+
OBJECT_ADDED: 'objectAdded',
|
|
44
|
+
OBJECT_REMOVED: 'objectRemoved',
|
|
45
|
+
OBJECT_CHANGED: 'objectChanged',
|
|
46
|
+
OBJECT_SELECTED: 'objectSelected',
|
|
47
|
+
OBJECT_TRANSFORM_CHANGING: 'objectTransformChanging',
|
|
48
|
+
OBJECT_TRANSFORM_CHANGED: 'objectTransformChanged',
|
|
49
|
+
SCENE_GRAPH_CHANGED: 'sceneGraphChanged',
|
|
50
|
+
HISTORY_CHANGED: 'historyChanged',
|
|
51
|
+
TRANSFORM_MODE_CHANGED: 'transformModeChanged',
|
|
52
|
+
ORIGINAL_MODEL_SELECTED: 'originalModelSelected',
|
|
53
|
+
ORIGINAL_MODEL_STYLE_CHANGED: 'originalModelStyleChanged',
|
|
54
|
+
ORIGINAL_MODEL_STYLE_RESET: 'originalModelStyleReset',
|
|
55
|
+
ORIGINAL_MODEL_CHANGE_LIST_CHANGED: 'originalModelChangeListChanged',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const COMMAND_TYPE = {
|
|
59
|
+
ADD_ELEMENT: 'AddElementCommand',
|
|
60
|
+
ADD_GROUP: 'AddGroupCommand',
|
|
61
|
+
REMOVE_ELEMENT: 'RemoveElementCommand',
|
|
62
|
+
CLONE_ELEMENT: 'CloneElementCommand',
|
|
63
|
+
MOVE_ELEMENT: 'MoveElementCommand',
|
|
64
|
+
SET_POSITION: 'SetPositionCommand',
|
|
65
|
+
SET_ROTATION: 'SetRotationCommand',
|
|
66
|
+
SET_SCALE: 'SetScaleCommand',
|
|
67
|
+
SET_VALUE: 'SetValueCommand',
|
|
68
|
+
SET_GEOMETRY_PARAMS: 'SetGeometryParamsCommand',
|
|
69
|
+
SET_ORIGINAL_MODEL_STYLE: 'SetOriginalModelStyleCommand',
|
|
70
|
+
RESET_ORIGINAL_MODEL_STYLE: 'ResetOriginalModelStyleCommand',
|
|
71
|
+
MULTI: 'MultiCommand',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const DEFAULT_GEOMETRY_PARAMS = {
|
|
75
|
+
box: {
|
|
76
|
+
width: 1,
|
|
77
|
+
height: 1,
|
|
78
|
+
depth: 1,
|
|
79
|
+
},
|
|
80
|
+
tetrahedron: {
|
|
81
|
+
radius: 1,
|
|
82
|
+
},
|
|
83
|
+
octahedron: {
|
|
84
|
+
radius: 1,
|
|
85
|
+
},
|
|
86
|
+
cylinder: {
|
|
87
|
+
radiusTop: 1,
|
|
88
|
+
radiusBottom: 1,
|
|
89
|
+
height: 1,
|
|
90
|
+
},
|
|
91
|
+
ring: {
|
|
92
|
+
radius: 1,
|
|
93
|
+
tube: 0.4,
|
|
94
|
+
arc: 360,
|
|
95
|
+
},
|
|
96
|
+
sphere: {
|
|
97
|
+
radius: 1,
|
|
98
|
+
phiStart: 0,
|
|
99
|
+
phiLength: 360,
|
|
100
|
+
thetaStart: 0,
|
|
101
|
+
thetaLength: 180,
|
|
102
|
+
},
|
|
103
|
+
capsule: {
|
|
104
|
+
radius: 1,
|
|
105
|
+
length: 1,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { DEFAULT_ELEMENT_COLOR, DEFAULT_GEOMETRY_PARAMS } from './constants';
|
|
2
|
+
import {
|
|
3
|
+
applyTransform,
|
|
4
|
+
cloneUserData,
|
|
5
|
+
createDefaultElementName,
|
|
6
|
+
createDefaultGroupName,
|
|
7
|
+
createUniqueName,
|
|
8
|
+
getElementBaseLabel,
|
|
9
|
+
updateDisplayName,
|
|
10
|
+
} from './scene-helpers';
|
|
11
|
+
|
|
12
|
+
function normalizeNumber(value, fallback) {
|
|
13
|
+
const nextValue = Number(value);
|
|
14
|
+
return Number.isFinite(nextValue) ? nextValue : fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeCapsuleLengthValue(geometryParams = {}, fallback) {
|
|
18
|
+
if (geometryParams.length !== undefined) {
|
|
19
|
+
return normalizeNumber(geometryParams.length, fallback);
|
|
20
|
+
}
|
|
21
|
+
if (geometryParams.height !== undefined) {
|
|
22
|
+
return normalizeNumber(geometryParams.height, fallback);
|
|
23
|
+
}
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function normalizeGeometryParams(type, geometryParams = {}) {
|
|
28
|
+
const defaults = DEFAULT_GEOMETRY_PARAMS[type];
|
|
29
|
+
if (!defaults) {
|
|
30
|
+
throw new Error(`不支持的元素类型:${type}`);
|
|
31
|
+
}
|
|
32
|
+
const normalized = {};
|
|
33
|
+
Object.keys(defaults).forEach(key => {
|
|
34
|
+
if (type === 'capsule' && key === 'length') {
|
|
35
|
+
normalized[key] = normalizeCapsuleLengthValue(geometryParams, defaults[key]);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
normalized[key] = normalizeNumber(geometryParams[key], defaults[key]);
|
|
39
|
+
});
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createGeometry(THREE, type, geometryParams = {}) {
|
|
44
|
+
const params = normalizeGeometryParams(type, geometryParams);
|
|
45
|
+
switch (type) {
|
|
46
|
+
case 'box':
|
|
47
|
+
return new THREE.BoxGeometry(params.width, params.height, params.depth);
|
|
48
|
+
case 'tetrahedron':
|
|
49
|
+
return new THREE.TetrahedronGeometry(params.radius);
|
|
50
|
+
case 'octahedron':
|
|
51
|
+
return new THREE.OctahedronGeometry(params.radius);
|
|
52
|
+
case 'cylinder':
|
|
53
|
+
return new THREE.CylinderGeometry(params.radiusTop, params.radiusBottom, params.height, 32);
|
|
54
|
+
case 'ring':
|
|
55
|
+
return new THREE.TorusGeometry(
|
|
56
|
+
params.radius,
|
|
57
|
+
params.tube,
|
|
58
|
+
16,
|
|
59
|
+
64,
|
|
60
|
+
(params.arc * Math.PI) / 180
|
|
61
|
+
);
|
|
62
|
+
case 'sphere':
|
|
63
|
+
return new THREE.SphereGeometry(
|
|
64
|
+
params.radius,
|
|
65
|
+
32,
|
|
66
|
+
16,
|
|
67
|
+
(params.phiStart * Math.PI) / 180,
|
|
68
|
+
(params.phiLength * Math.PI) / 180,
|
|
69
|
+
(params.thetaStart * Math.PI) / 180,
|
|
70
|
+
(params.thetaLength * Math.PI) / 180
|
|
71
|
+
);
|
|
72
|
+
case 'capsule':
|
|
73
|
+
return new THREE.CapsuleGeometry(params.radius, params.length, 8, 16);
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`不支持的元素类型:${type}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createDefaultMaterial(THREE, materialOptions = {}) {
|
|
80
|
+
const color = materialOptions.color || DEFAULT_ELEMENT_COLOR;
|
|
81
|
+
const opacity =
|
|
82
|
+
materialOptions.opacity === undefined ? 1 : normalizeNumber(materialOptions.opacity, 1);
|
|
83
|
+
return new THREE.MeshStandardMaterial({
|
|
84
|
+
color,
|
|
85
|
+
transparent: opacity < 1,
|
|
86
|
+
opacity,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createPrimitive(THREE, type, options = {}) {
|
|
91
|
+
const geometryParams = normalizeGeometryParams(type, options.geometryParams);
|
|
92
|
+
const geometry = createGeometry(THREE, type, geometryParams);
|
|
93
|
+
const material = createDefaultMaterial(THREE, options.material);
|
|
94
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
95
|
+
mesh.name = options.name || getElementBaseLabel(type);
|
|
96
|
+
mesh.userData = {
|
|
97
|
+
nodeType: 'custom-element',
|
|
98
|
+
elementType: type,
|
|
99
|
+
// displayName: mesh.name,
|
|
100
|
+
editable: true,
|
|
101
|
+
deletable: true,
|
|
102
|
+
cloneable: true,
|
|
103
|
+
source: options.source || 'manual-create',
|
|
104
|
+
geometryParams,
|
|
105
|
+
};
|
|
106
|
+
applyTransform(mesh, options.transform);
|
|
107
|
+
updateDisplayName(mesh);
|
|
108
|
+
return mesh;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createGroup(THREE, options = {}) {
|
|
112
|
+
const group = new THREE.Group();
|
|
113
|
+
group.name = options.name || '组';
|
|
114
|
+
group.userData = {
|
|
115
|
+
nodeType: 'custom-group',
|
|
116
|
+
// displayName: group.name,
|
|
117
|
+
editable: true,
|
|
118
|
+
deletable: true,
|
|
119
|
+
cloneable: true,
|
|
120
|
+
source: options.source || 'manual-create',
|
|
121
|
+
};
|
|
122
|
+
applyTransform(group, options.transform);
|
|
123
|
+
updateDisplayName(group);
|
|
124
|
+
return group;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function createElementByType(THREE, type, parent, options = {}) {
|
|
128
|
+
const name = options.name || createDefaultElementName(parent, type);
|
|
129
|
+
return createPrimitive(THREE, type, {
|
|
130
|
+
...options,
|
|
131
|
+
name,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function createGroupElement(THREE, parent, options = {}) {
|
|
136
|
+
const name = options.name || createDefaultGroupName(parent);
|
|
137
|
+
return createGroup(THREE, {
|
|
138
|
+
...options,
|
|
139
|
+
name,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function cloneCustomObject(THREE, object, targetParent) {
|
|
144
|
+
const clonedObject = object.clone(true);
|
|
145
|
+
clonedObject.traverse(child => {
|
|
146
|
+
if (child.userData) {
|
|
147
|
+
child.userData = cloneUserData(child.userData);
|
|
148
|
+
}
|
|
149
|
+
if (child.isMesh) {
|
|
150
|
+
child.geometry = child.geometry.clone();
|
|
151
|
+
if (Array.isArray(child.material)) {
|
|
152
|
+
child.material = child.material.map(material => material.clone());
|
|
153
|
+
} else if (child.material) {
|
|
154
|
+
child.material = child.material.clone();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
clonedObject.userData.source = 'clone';
|
|
160
|
+
clonedObject.name = createUniqueName(targetParent, object.name);
|
|
161
|
+
updateDisplayName(clonedObject);
|
|
162
|
+
return clonedObject;
|
|
163
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default class SceneEventBus {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.listeners = new Map();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
on(eventName, handler) {
|
|
7
|
+
if (!this.listeners.has(eventName)) {
|
|
8
|
+
this.listeners.set(eventName, new Set());
|
|
9
|
+
}
|
|
10
|
+
this.listeners.get(eventName).add(handler);
|
|
11
|
+
return () => this.off(eventName, handler);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
off(eventName, handler) {
|
|
15
|
+
const handlers = this.listeners.get(eventName);
|
|
16
|
+
if (!handlers) return;
|
|
17
|
+
handlers.delete(handler);
|
|
18
|
+
if (handlers.size === 0) {
|
|
19
|
+
this.listeners.delete(eventName);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
emit(eventName, payload) {
|
|
24
|
+
const handlers = this.listeners.get(eventName);
|
|
25
|
+
if (!handlers || handlers.size === 0) return;
|
|
26
|
+
handlers.forEach(handler => {
|
|
27
|
+
handler(payload);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clear() {
|
|
32
|
+
this.listeners.clear();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { HISTORY_MERGE_WINDOW } from './constants';
|
|
2
|
+
|
|
3
|
+
export default class History {
|
|
4
|
+
constructor(service, options = {}) {
|
|
5
|
+
this.service = service;
|
|
6
|
+
this.undos = [];
|
|
7
|
+
this.redos = [];
|
|
8
|
+
this.lastCommandTime = 0;
|
|
9
|
+
this.mergeWindow = options.mergeWindow || HISTORY_MERGE_WINDOW;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
execute(command) {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const lastCommand = this.undos[this.undos.length - 1];
|
|
15
|
+
if (
|
|
16
|
+
lastCommand &&
|
|
17
|
+
lastCommand.updatable &&
|
|
18
|
+
command.updatable &&
|
|
19
|
+
typeof lastCommand.canUpdate === 'function' &&
|
|
20
|
+
lastCommand.canUpdate(command, now - this.lastCommandTime, this.mergeWindow)
|
|
21
|
+
) {
|
|
22
|
+
lastCommand.update(command);
|
|
23
|
+
lastCommand.execute();
|
|
24
|
+
this.redos = [];
|
|
25
|
+
this.lastCommandTime = now;
|
|
26
|
+
return {
|
|
27
|
+
command: lastCommand,
|
|
28
|
+
merged: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
command.execute();
|
|
33
|
+
this.undos.push(command);
|
|
34
|
+
this.redos = [];
|
|
35
|
+
this.lastCommandTime = now;
|
|
36
|
+
return {
|
|
37
|
+
command,
|
|
38
|
+
merged: false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
undo() {
|
|
43
|
+
const command = this.undos.pop();
|
|
44
|
+
if (!command) return null;
|
|
45
|
+
command.undo();
|
|
46
|
+
this.redos.push(command);
|
|
47
|
+
return command;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
redo() {
|
|
51
|
+
const command = this.redos.pop();
|
|
52
|
+
if (!command) return null;
|
|
53
|
+
command.execute();
|
|
54
|
+
this.undos.push(command);
|
|
55
|
+
return command;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clear() {
|
|
59
|
+
this.undos = [];
|
|
60
|
+
this.redos = [];
|
|
61
|
+
this.lastCommandTime = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
canUndo() {
|
|
65
|
+
return this.undos.length > 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
canRedo() {
|
|
69
|
+
return this.redos.length > 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getState() {
|
|
73
|
+
return {
|
|
74
|
+
canUndo: this.canUndo(),
|
|
75
|
+
canRedo: this.canRedo(),
|
|
76
|
+
undoCount: this.undos.length,
|
|
77
|
+
redoCount: this.redos.length,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|