@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
- renderer.render(scene, camera);
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.1",
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",