@umicat/three-sdk 0.17.0 → 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.
|
@@ -21,13 +21,43 @@ import { loadScene3D } from '../SceneLoader3D.js';
|
|
|
21
21
|
* real game.
|
|
22
22
|
*/
|
|
23
23
|
export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
24
|
+
hideNonCanvasStaticMarkup(renderer.domElement);
|
|
24
25
|
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.05, 2000);
|
|
25
26
|
const controls = new OrbitControls(camera, renderer.domElement);
|
|
26
27
|
controls.enableDamping = true;
|
|
27
|
-
// 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).
|
|
28
31
|
const marker = new THREE.Mesh(new THREE.SphereGeometry(0.12, 16, 12), new THREE.MeshBasicMaterial({ color: '#ff3b6b', depthTest: false }));
|
|
29
32
|
marker.visible = false;
|
|
30
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
|
+
}
|
|
31
61
|
let scene = null;
|
|
32
62
|
const entityByObject = new Map();
|
|
33
63
|
const resize = () => {
|
|
@@ -71,16 +101,29 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
71
101
|
const hit = hits[0];
|
|
72
102
|
let node = hit.object;
|
|
73
103
|
let entityId = null;
|
|
104
|
+
let entityRoot = null;
|
|
74
105
|
while (node) {
|
|
75
106
|
const id = entityByObject.get(node);
|
|
76
107
|
if (id) {
|
|
77
108
|
entityId = id;
|
|
109
|
+
entityRoot = node;
|
|
78
110
|
break;
|
|
79
111
|
}
|
|
80
112
|
node = node.parent;
|
|
81
113
|
}
|
|
82
114
|
marker.position.copy(hit.point);
|
|
83
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
|
+
}
|
|
84
127
|
window.parent.postMessage({ type: 'umicat:editor:pickPoint3d', x: hit.point.x, y: hit.point.y, z: hit.point.z, entityId }, '*');
|
|
85
128
|
};
|
|
86
129
|
renderer.domElement.addEventListener('pointerdown', onPointerDown);
|
|
@@ -91,8 +134,30 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
91
134
|
// step, so the loop only ever needs to render.
|
|
92
135
|
renderer.setAnimationLoop(() => {
|
|
93
136
|
controls.update();
|
|
94
|
-
if (scene)
|
|
95
|
-
|
|
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);
|
|
96
161
|
});
|
|
97
162
|
const sceneId = opts.sceneId;
|
|
98
163
|
if (!sceneId)
|
|
@@ -107,6 +172,8 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
107
172
|
entityByObject.set(obj, id);
|
|
108
173
|
marker.visible = false;
|
|
109
174
|
scene.add(marker);
|
|
175
|
+
selectionBox.visible = false;
|
|
176
|
+
scene.add(selectionBox);
|
|
110
177
|
// Frame the whole scene — auto-fit, since a small template scene and a
|
|
111
178
|
// real level's dozens of entities need wildly different orbit distances.
|
|
112
179
|
const box = new THREE.Box3().setFromObject(scene);
|
|
@@ -126,3 +193,42 @@ export async function runEditorDesignPlayer3D(renderer, opts = {}) {
|
|
|
126
193
|
window.parent.postMessage({ type: 'umicat:editor:scene3dLoaded', sceneId, error: true }, '*');
|
|
127
194
|
}
|
|
128
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Hides every other top-level DOM node the game shipped, keeping only the
|
|
198
|
+
* canvas. Mirrors `@umicat/phaser-sdk`'s edit-mode markup hider (`UmicatGame.ts`)
|
|
199
|
+
* exactly, including its `data-umicat-keep` opt-out — same bug, same fix,
|
|
200
|
+
* different renderer.
|
|
201
|
+
*
|
|
202
|
+
* Two distinct problems, one cause: `index.html` markup has no owner in edit
|
|
203
|
+
* mode, because none of the game's own code runs to manage it. A static score
|
|
204
|
+
* HUD just clutters a scene that's supposed to read as empty of gameplay. A
|
|
205
|
+
* game with its own full-screen input-capturing overlay (a drawing surface
|
|
206
|
+
* that "the whole screen collects strokes" is a real, shipped example) is
|
|
207
|
+
* worse: that overlay sits ABOVE the canvas and silently swallows every
|
|
208
|
+
* pointer event meant for OrbitControls — confirmed live, orbit/zoom did
|
|
209
|
+
* nothing at all until this ran, with zero console errors, because the events
|
|
210
|
+
* simply never reached the renderer's own `domElement`.
|
|
211
|
+
*/
|
|
212
|
+
function hideNonCanvasStaticMarkup(canvas) {
|
|
213
|
+
if (typeof document === 'undefined' || !document.body)
|
|
214
|
+
return;
|
|
215
|
+
const hidden = [];
|
|
216
|
+
for (const el of Array.from(document.body.children)) {
|
|
217
|
+
if (!(el instanceof HTMLElement))
|
|
218
|
+
continue;
|
|
219
|
+
const tag = el.tagName;
|
|
220
|
+
if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'LINK' || tag === 'NOSCRIPT')
|
|
221
|
+
continue;
|
|
222
|
+
if (el === canvas)
|
|
223
|
+
continue;
|
|
224
|
+
if (el.hasAttribute('data-umicat-keep'))
|
|
225
|
+
continue; // opt OUT, for markup that IS design-time content
|
|
226
|
+
if (el.style.display === 'none')
|
|
227
|
+
continue;
|
|
228
|
+
el.style.display = 'none';
|
|
229
|
+
hidden.push(el.id ? `#${el.id}` : tag.toLowerCase());
|
|
230
|
+
}
|
|
231
|
+
if (hidden.length > 0) {
|
|
232
|
+
console.info(`[umicat/editor] hid ${hidden.join(', ')} — index.html markup with no game code running to remove it. Add data-umicat-keep to keep one visible in Edit.`);
|
|
233
|
+
}
|
|
234
|
+
}
|
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",
|