@umicat/three-sdk 0.17.1 → 0.17.2
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.
|
@@ -25,10 +25,39 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
25
25
|
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.05, 2000);
|
|
26
26
|
const controls = new OrbitControls(camera, renderer.domElement);
|
|
27
27
|
controls.enableDamping = true;
|
|
28
|
-
// A single persistent marker, repositioned per click
|
|
28
|
+
// A single persistent marker, repositioned per click — the exact world
|
|
29
|
+
// point clicked (what "put a torch at the spot I clicked" needs), distinct
|
|
30
|
+
// from the box below (which entity was hit, not just where).
|
|
29
31
|
const marker = new THREE.Mesh(new THREE.SphereGeometry(0.12, 16, 12), new THREE.MeshBasicMaterial({ color: '#ff3b6b', depthTest: false }));
|
|
30
32
|
marker.visible = false;
|
|
31
33
|
marker.renderOrder = 999;
|
|
34
|
+
// Selection box — Unity/Blender-style "frame the object you clicked", not
|
|
35
|
+
// just a point on its surface. `BoxHelper.setFromObject` (three.js core,
|
|
36
|
+
// no custom geometry math needed) recomputes a world-space AABB wireframe
|
|
37
|
+
// from scratch each call, so retargeting it to a different entity per
|
|
38
|
+
// click is just calling it again — it does not need to track a moving
|
|
39
|
+
// object (this scene never animates in design mode).
|
|
40
|
+
const selectionBox = new THREE.BoxHelper(new THREE.Object3D(), 0xffd23f);
|
|
41
|
+
selectionBox.visible = false;
|
|
42
|
+
selectionBox.renderOrder = 998;
|
|
43
|
+
selectionBox.material.depthTest = false;
|
|
44
|
+
// Orientation gizmo — a small corner compass tracking the main camera's
|
|
45
|
+
// rotation only (never its position/zoom), same convention every 3D DCC
|
|
46
|
+
// uses: X=red, Y=green, Z=blue. Rendered as a second, tiny scene into a
|
|
47
|
+
// scissored corner viewport each frame (see the render loop below) — the
|
|
48
|
+
// simplest way to get a HUD-anchored 3D readout without a second canvas.
|
|
49
|
+
const GIZMO_SIZE = 84;
|
|
50
|
+
const GIZMO_MARGIN = 14;
|
|
51
|
+
const gizmoScene = new THREE.Scene();
|
|
52
|
+
const gizmoCamera = new THREE.OrthographicCamera(-1.6, 1.6, 1.6, -1.6, 0.1, 10);
|
|
53
|
+
const AXES = [
|
|
54
|
+
[new THREE.Vector3(1, 0, 0), 0xff4d4d], // X — red
|
|
55
|
+
[new THREE.Vector3(0, 1, 0), 0x4dff88], // Y — green
|
|
56
|
+
[new THREE.Vector3(0, 0, 1), 0x4d9fff], // Z — blue
|
|
57
|
+
];
|
|
58
|
+
for (const [dir, color] of AXES) {
|
|
59
|
+
gizmoScene.add(new THREE.ArrowHelper(dir, new THREE.Vector3(0, 0, 0), 1, color, 0.35, 0.22));
|
|
60
|
+
}
|
|
32
61
|
let scene = null;
|
|
33
62
|
const entityByObject = new Map();
|
|
34
63
|
const resize = () => {
|
|
@@ -72,16 +101,29 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
72
101
|
const hit = hits[0];
|
|
73
102
|
let node = hit.object;
|
|
74
103
|
let entityId = null;
|
|
104
|
+
let entityRoot = null;
|
|
75
105
|
while (node) {
|
|
76
106
|
const id = entityByObject.get(node);
|
|
77
107
|
if (id) {
|
|
78
108
|
entityId = id;
|
|
109
|
+
entityRoot = node;
|
|
79
110
|
break;
|
|
80
111
|
}
|
|
81
112
|
node = node.parent;
|
|
82
113
|
}
|
|
83
114
|
marker.position.copy(hit.point);
|
|
84
115
|
marker.visible = true;
|
|
116
|
+
// Frame the whole entity the click landed on, not just the point on its
|
|
117
|
+
// surface — `setFromObject` walks the root's full subtree, so a
|
|
118
|
+
// multi-mesh entity (a model with separate parts) gets one box around
|
|
119
|
+
// all of it, matching what "entityId" actually refers to.
|
|
120
|
+
if (entityRoot) {
|
|
121
|
+
selectionBox.setFromObject(entityRoot);
|
|
122
|
+
selectionBox.visible = true;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
selectionBox.visible = false;
|
|
126
|
+
}
|
|
85
127
|
window.parent.postMessage({ type: 'umicat:editor:pickPoint3d', x: hit.point.x, y: hit.point.y, z: hit.point.z, entityId }, '*');
|
|
86
128
|
};
|
|
87
129
|
renderer.domElement.addEventListener('pointerdown', onPointerDown);
|
|
@@ -92,8 +134,30 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
92
134
|
// step, so the loop only ever needs to render.
|
|
93
135
|
renderer.setAnimationLoop(() => {
|
|
94
136
|
controls.update();
|
|
95
|
-
if (scene)
|
|
96
|
-
|
|
137
|
+
if (!scene)
|
|
138
|
+
return;
|
|
139
|
+
renderer.setScissorTest(false);
|
|
140
|
+
renderer.render(scene, camera);
|
|
141
|
+
// Second pass: the orientation gizmo, into a small scissored corner
|
|
142
|
+
// viewport. Its camera copies the MAIN camera's rotation only (not
|
|
143
|
+
// position/zoom) so it reads as "which way am I looking", exactly what
|
|
144
|
+
// orbiting the main view is meant to answer. three.js viewport Y is
|
|
145
|
+
// bottom-up, so "top-right on screen" is `height - size - margin`.
|
|
146
|
+
const w = renderer.domElement.clientWidth;
|
|
147
|
+
const h = renderer.domElement.clientHeight;
|
|
148
|
+
gizmoCamera.position.set(0, 0, 4).applyQuaternion(camera.quaternion);
|
|
149
|
+
gizmoCamera.up.copy(camera.up);
|
|
150
|
+
gizmoCamera.lookAt(0, 0, 0);
|
|
151
|
+
const gx = w - GIZMO_SIZE - GIZMO_MARGIN;
|
|
152
|
+
const gy = h - GIZMO_SIZE - GIZMO_MARGIN;
|
|
153
|
+
renderer.setScissorTest(true);
|
|
154
|
+
renderer.setScissor(gx, gy, GIZMO_SIZE, GIZMO_SIZE);
|
|
155
|
+
renderer.setViewport(gx, gy, GIZMO_SIZE, GIZMO_SIZE);
|
|
156
|
+
// render()'s own autoClear respects the scissor rect just set, so this
|
|
157
|
+
// only wipes the corner box, not the scene pass above.
|
|
158
|
+
renderer.render(gizmoScene, gizmoCamera);
|
|
159
|
+
renderer.setScissorTest(false);
|
|
160
|
+
renderer.setViewport(0, 0, w, h);
|
|
97
161
|
});
|
|
98
162
|
const sceneId = opts.sceneId;
|
|
99
163
|
if (!sceneId)
|
|
@@ -108,6 +172,8 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
108
172
|
entityByObject.set(obj, id);
|
|
109
173
|
marker.visible = false;
|
|
110
174
|
scene.add(marker);
|
|
175
|
+
selectionBox.visible = false;
|
|
176
|
+
scene.add(selectionBox);
|
|
111
177
|
// Frame the whole scene — auto-fit, since a small template scene and a
|
|
112
178
|
// real level's dozens of entities need wildly different orbit distances.
|
|
113
179
|
const box = new THREE.Box3().setFromObject(scene);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umicat/three-sdk",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "Three.js runtime for Umicat games: the scene3d design format, its loader with physics, a kinematic character controller, and the Umicat platform via @umicat/platform-sdk.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|