diff --git a/src/editor/KubikonEditor.jsx b/src/editor/KubikonEditor.jsx index 7061068..0ef3930 100644 --- a/src/editor/KubikonEditor.jsx +++ b/src/editor/KubikonEditor.jsx @@ -778,12 +778,17 @@ const KubikonEditor = () => { const kit = getKit(kitId); const s = sceneRef.current; if (!kit || !s) return; - // Точка вставки — перед камерой редактора (~6м), как у paste. - let px = 0, pz = 0; + // Точка вставки — на ТВЁРДОЙ поверхности под центром экрана (пол/объект), + // чтобы предмет встал на землю в фокусе камеры, а не висел под камерой. + let px = 0, pz = 0, py = 0; try { - const cam = s.camera; - const fwd = cam?.getForwardRay ? cam.getForwardRay().direction : null; - if (cam && fwd) { px = cam.position.x + fwd.x * 6; pz = cam.position.z + fwd.z * 6; } + const gp = s.getPlacementPointAtCenter?.(); + if (gp) { px = gp.x; pz = gp.z; py = gp.y; } + else { + const cam = s.camera; + const fwd = cam?.getForwardRay ? cam.getForwardRay().direction : null; + if (cam && fwd) { px = cam.position.x + fwd.x * 6; pz = cam.position.z + fwd.z * 6; } + } } catch (e) { /* ignore */ } // 1) Создаём примитивы кита. Запоминаем все id (первый — для on-target скрипта). @@ -792,7 +797,7 @@ const KubikonEditor = () => { if (Array.isArray(kit.prims)) { for (const p of kit.prims) { const newId = s.primitiveManager?.addInstance(p.type || 'cube', { - x: px + (p.x || 0), y: (p.y != null ? p.y : 1), z: pz + (p.z || 0), + x: px + (p.x || 0), y: py + (p.y != null ? p.y : 1), z: pz + (p.z || 0), sx: p.sx, sy: p.sy, sz: p.sz, color: p.color, material: p.material, canCollide: p.canCollide !== false, visible: true, anchored: true, @@ -829,6 +834,7 @@ const KubikonEditor = () => { } markDirty(); + hierarchyDirtyRef.current = true; // пересобрать дерево (примитивы с folderId) setScriptsList(s.getScripts?.() || []); if (s.folderManager) setFoldersList(s.folderManager.getAll()); // Выделим созданное и наведём камеру (видно, куда добавилось). diff --git a/src/editor/engine/BabylonScene.js b/src/editor/engine/BabylonScene.js index 7b7b31d..edaccc0 100644 --- a/src/editor/engine/BabylonScene.js +++ b/src/editor/engine/BabylonScene.js @@ -2936,6 +2936,29 @@ export class BabylonScene { return { mesh, point: pi.pickedPoint, pickInfo: pi }; } + /** + * Точка под центром экрана на твёрдой поверхности (пол/объект) — для + * спавна предметов из тулбокса «в фокусе камеры на земле». + * Возвращает { x, y, z } (y — высота поверхности). Fallback: проекция на y=0. + */ + getPlacementPointAtCenter() { + const hit = this._pickFromCenter(); + if (hit && hit.point) { + return { x: hit.point.x, y: hit.point.y, z: hit.point.z }; + } + // Нет попадания — проецируем луч из центра на плоскость y=0. + try { + const w = this.engine?.getRenderWidth?.() || this.canvas.width; + const h = this.engine?.getRenderHeight?.() || this.canvas.height; + const ray = this.scene.createPickingRay(w / 2, h / 2, null, this.scene.activeCamera); + if (Math.abs(ray.direction.y) > 1e-4) { + const t = -ray.origin.y / ray.direction.y; + if (t > 0) return { x: ray.origin.x + ray.direction.x * t, y: 0, z: ray.origin.z + ray.direction.z * t }; + } + } catch (e) { /* ignore */ } + return null; + } + /** * Извлечь target {kind, ref} из mesh (proxy/прим/модель). * Используется при клике/touch в Play. @@ -5921,6 +5944,13 @@ export class BabylonScene { */ enterPlayMode() { if (this._isPlaying) return; + // Снять любое выделение редактора (объект/папка) перед запуском игры — + // иначе в Play остаётся подсветка/гизмо выбранного объекта. + try { + if (this._folderPivot) { this._folderPivot.dispose(); this._folderPivot = null; this._folderPivotId = null; } + this._gizmo?.attachTo(null); + this.selection?.clear?.(); + } catch (e) { /* ignore */ } this._isPlaying = true; // Сброс состояния касаний — каждый прогон начинается «не касаясь», // иначе rising-edge touch не сработает, если при стопе игрок стоял на цели.