fix(studio): клик по части папки выделяет всю папку + free-drag папки + удаление обновляет дерево + затухание длинной тени
1) Длинная тень-полоса: csm.frustumEdgeFalloff=8 — тень персонажа больше не тянется на весь пол. 2) Удаление примитива/модели/блока через ПКМ теперь обновляет дерево (markDirty + hierarchyDirtyRef) — раньше объект удалялся, но не пропадал. 3) Клик по СЦЕНЕ по объекту в папке → выделяется ВСЯ папка (selectByMesh проверяет folderId). Отдельную часть — через раскрытие папки в дереве. 4) Free-drag папки: зажал ЛКМ на группе и тянешь — двигается вся папка (moveFolderBy по дельте центра). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ed7310a532
commit
bf93219266
@ -3351,14 +3351,20 @@ const KubikonEditor = () => {
|
||||
onDeleteBlock={(x, y, z) => {
|
||||
sceneRef.current?.blockManager?.removeBlock(x, y, z);
|
||||
sceneRef.current?.clearSelection();
|
||||
markDirty();
|
||||
hierarchyDirtyRef.current = true;
|
||||
}}
|
||||
onDeleteModel={(id) => {
|
||||
sceneRef.current?.modelManager?.removeInstance(id);
|
||||
sceneRef.current?.clearSelection();
|
||||
markDirty();
|
||||
hierarchyDirtyRef.current = true;
|
||||
}}
|
||||
onDeletePrimitive={(id) => {
|
||||
sceneRef.current?.primitiveManager?.removeInstance(id);
|
||||
sceneRef.current?.clearSelection();
|
||||
markDirty();
|
||||
hierarchyDirtyRef.current = true;
|
||||
}}
|
||||
onFocusSelection={() => sceneRef.current?.focusOnSelection()}
|
||||
onCreateFolder={(name, parentId) =>
|
||||
|
||||
@ -1723,6 +1723,9 @@ export class BabylonScene {
|
||||
: ShadowGenerator.QUALITY_MEDIUM;
|
||||
csm.darkness = 0.4;
|
||||
csm.autoCalcDepthBounds = true;
|
||||
// Плавное затухание тени у края каскада — убирает «полосу-хвост»
|
||||
// тени персонажа на весь пол при движении (баг 2026-06-05).
|
||||
csm.frustumEdgeFalloff = 8;
|
||||
this._shadowGenerator = csm;
|
||||
} else {
|
||||
// Обычный ShadowGenerator. Поднял разрешение для soft до 2048.
|
||||
@ -5679,6 +5682,14 @@ export class BabylonScene {
|
||||
this.selection?.selectByMesh(m);
|
||||
const sel = this.selection?.getSelection();
|
||||
if (!sel || sel.type === 'block' || sel.type === 'spawn') return false;
|
||||
// Папка — тащим всю группу через folderManager (по дельте центра).
|
||||
if (sel.type === 'folder') {
|
||||
const g = this.folderManager?.getFolderObjects(sel.folderId);
|
||||
this._freeDragCandidate = { folder: true, folderId: sel.folderId, last: { ...(g?.center || { x: 0, y: 0, z: 0 }) } };
|
||||
this._freeDragHalf = { x: 0.5, y: 0.5, z: 0.5 };
|
||||
this._freeDragActive = false;
|
||||
return true;
|
||||
}
|
||||
const root = this._getSelectionRoot(sel);
|
||||
if (!root) return false;
|
||||
this._freeDragCandidate = { root };
|
||||
@ -5693,6 +5704,24 @@ export class BabylonScene {
|
||||
if (!cand) return;
|
||||
const sel = this.selection?.getSelection();
|
||||
if (!sel) return;
|
||||
|
||||
// Папка: тащим всю группу по дельте от последнего центра (проекция курсора
|
||||
// на горизонтальную плоскость по высоте центра группы).
|
||||
if (cand.folder) {
|
||||
const ray = this.scene.createPickingRay(this.scene.pointerX, this.scene.pointerY, null, this.scene.activeCamera);
|
||||
if (Math.abs(ray.direction.y) < 1e-4) return;
|
||||
const t = (cand.last.y - ray.origin.y) / ray.direction.y;
|
||||
if (t < 0) return;
|
||||
const px = ray.origin.x + ray.direction.x * t;
|
||||
const pz = ray.origin.z + ray.direction.z * t;
|
||||
const dx = px - cand.last.x, dz = pz - cand.last.z;
|
||||
if (dx || dz) {
|
||||
this.folderManager?.moveFolderBy(cand.folderId, dx, 0, dz);
|
||||
cand.last.x = px; cand.last.z = pz;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const root = cand.root;
|
||||
const half = this._freeDragHalf || { x: 0.5, y: 0.5, z: 0.5 };
|
||||
|
||||
|
||||
@ -76,24 +76,37 @@ export class SelectionManager {
|
||||
selectByMesh(mesh) {
|
||||
if (!mesh) return this.clear();
|
||||
const m = mesh.metadata;
|
||||
// Если объект лежит в папке — клик по СЦЕНЕ выделяет ВСЮ папку целиком
|
||||
// (отдельную часть можно выбрать через дерево). folderId берём из data.
|
||||
const folderIdOf = (kind, id) => {
|
||||
let d = null;
|
||||
if (kind === 'model') d = this.modelManager?.instances?.get(id);
|
||||
else if (kind === 'userModel') d = this.userModelManager?.instances?.get(id);
|
||||
else if (kind === 'primitive') d = this.primitiveManager?.instances?.get(id);
|
||||
return d ? (d.folderId ?? null) : null;
|
||||
};
|
||||
if (m?.isBlock) {
|
||||
return this.selectBlockAt(m.gridX, m.gridY, m.gridZ);
|
||||
}
|
||||
if (m?.isModel) {
|
||||
// Заблокированный объект (Фаза 5.11) не выделяется кликом по
|
||||
// сцене — только через иерархию (чтобы можно было снять lock).
|
||||
const md = this.modelManager?.instances?.get(m.instanceId);
|
||||
if (md && md.locked) return this.clear();
|
||||
const fid = folderIdOf('model', m.instanceId);
|
||||
if (fid != null) return this.selectFolder(fid);
|
||||
return this.selectModelByInstanceId(m.instanceId);
|
||||
}
|
||||
if (m?.isUserModel) {
|
||||
const ud = this.userModelManager?.instances?.get(m.instanceId);
|
||||
if (ud && ud.locked) return this.clear();
|
||||
const fid = folderIdOf('userModel', m.instanceId);
|
||||
if (fid != null) return this.selectFolder(fid);
|
||||
return this.selectUserModelByInstanceId(m.instanceId);
|
||||
}
|
||||
if (m?.isPrimitive) {
|
||||
const pd = this.primitiveManager?.instances?.get(m.primitiveId);
|
||||
if (pd && pd.locked) return this.clear();
|
||||
const fid = folderIdOf('primitive', m.primitiveId);
|
||||
if (fid != null) return this.selectFolder(fid);
|
||||
return this.selectPrimitiveById(m.primitiveId);
|
||||
}
|
||||
if (m?.isSpawn) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user