165 lines
5.8 KiB
JavaScript
165 lines
5.8 KiB
JavaScript
// Минимальный плагин трансформаций: хранит выбранный объект, даёт доступ к pos/rot/scale,
|
|
// реагирует на клики по дереву. Трансформации применяются на уровне SceneModel.
|
|
|
|
export class TransformGizmo {
|
|
constructor({ viewer, sdk, treeView, transformStore, onTargetChange, onTransformChange, sceneModels }) {
|
|
this.viewer = viewer;
|
|
this.scene = viewer.scene;
|
|
this.sdk = sdk;
|
|
this.treeView = treeView;
|
|
this.transformStore = transformStore || {};
|
|
this.sceneModels = sceneModels || {};
|
|
this.onTargetChange = onTargetChange;
|
|
this.onTransformChange = onTransformChange;
|
|
|
|
this.mode = "translate"; // translate | rotate | scale
|
|
this.target = null; // { modelId, objectId }
|
|
|
|
this._bindTree();
|
|
}
|
|
|
|
_isPointCloudTarget(model, objectId = null) {
|
|
const entity = objectId ? this.scene.objects[objectId] : null;
|
|
const firstObject = entity || Object.values(model?.objects || {})[0];
|
|
const firstMesh = firstObject?.meshes?.[0] || model?.meshes?.[0];
|
|
return firstMesh?._geometry?._state?.primitiveName === "points";
|
|
}
|
|
|
|
setMode(next) {
|
|
if (!["translate", "rotate", "scale"].includes(next)) return;
|
|
this.mode = next;
|
|
this._log(`Режим гизмы: ${next}`);
|
|
}
|
|
|
|
clearTarget() {
|
|
if (this.target?.objectId) {
|
|
this.scene.setObjectsHighlighted([this.target.objectId], false);
|
|
}
|
|
this.scene.setObjectsHighlighted(this.scene.highlightedObjectIds || [], false);
|
|
this.target = null;
|
|
this.onTargetChange?.(null);
|
|
this._log("Выбор очищен");
|
|
}
|
|
|
|
setTarget({ modelId, objectId }) {
|
|
if (!modelId) return;
|
|
|
|
this.scene.setObjectsHighlighted(this.scene.highlightedObjectIds || [], false);
|
|
|
|
const model = this.sceneModels ? this.sceneModels[modelId] || this.scene.models[modelId] : this.scene.models[modelId];
|
|
if (!model) {
|
|
this._log(`Модель ${modelId} не найдена`, true);
|
|
return;
|
|
}
|
|
|
|
// Если указан объект
|
|
if (objectId) {
|
|
const entity = this.scene.objects[objectId];
|
|
if (!entity) {
|
|
this._log(`Объект ${objectId} не найден`, true);
|
|
} else {
|
|
const saved = this.transformStore?.[modelId]?.[objectId];
|
|
if (saved) {
|
|
if (saved.position) model.position = saved.position;
|
|
if (saved.rotation) model.rotation = saved.rotation;
|
|
if (saved.scale) model.scale = saved.scale;
|
|
}
|
|
this.onTargetChange?.({
|
|
modelId,
|
|
objectId,
|
|
transform: {
|
|
position: [...model.position],
|
|
rotation: [...model.rotation],
|
|
scale: model.scale ? [...model.scale] : [1, 1, 1],
|
|
},
|
|
});
|
|
if (!this._isPointCloudTarget(model, objectId)) {
|
|
this.scene.setObjectsHighlighted([objectId], true);
|
|
}
|
|
this.target = { modelId, objectId };
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Если objectId нет, работаем всей моделью
|
|
const ids = Object.keys(model.objects || {});
|
|
if (ids.length && !this._isPointCloudTarget(model)) {
|
|
this.scene.setObjectsHighlighted(ids, true);
|
|
}
|
|
this.onTargetChange?.({
|
|
modelId,
|
|
objectId: null,
|
|
transform: {
|
|
position: [...model.position],
|
|
rotation: [...model.rotation],
|
|
scale: model.scale ? [...model.scale] : [1, 1, 1],
|
|
},
|
|
});
|
|
this.target = { modelId, objectId: null };
|
|
}
|
|
|
|
handleTreeNodeClick(evt) {
|
|
const node = evt?.treeViewNode || evt?.node || evt;
|
|
const objectId = node?.objectId || node?.id || node?.object?.id || node?.entityId || node?.objectIdGiven;
|
|
const modelId = node?.modelId || node?.model?.id || node?.modelID || node?.model?.modelId || node?.modelIdGiven;
|
|
if (objectId && modelId) {
|
|
this.setTarget({ modelId, objectId });
|
|
} else if (modelId) {
|
|
// нет objectId - подсветим всю модель
|
|
const model = this.scene.models[modelId];
|
|
if (model) {
|
|
const ids = Object.keys(model.objects || {});
|
|
this.scene.setObjectsHighlighted(this.scene.highlightedObjectIds || [], false);
|
|
if (!this._isPointCloudTarget(model)) {
|
|
this.scene.setObjectsHighlighted(ids, true);
|
|
}
|
|
this.target = { modelId, objectId: null };
|
|
}
|
|
}
|
|
}
|
|
|
|
updateTransform({ position, rotation, scale }) {
|
|
if (!this.target?.modelId) return;
|
|
const model = this.sceneModels ? this.sceneModels[this.target.modelId] || this.scene.models[this.target.modelId] : this.scene.models[this.target.modelId];
|
|
if (!model) return;
|
|
if (position) model.position = position;
|
|
if (rotation) model.rotation = rotation;
|
|
if (scale) model.scale = scale;
|
|
// сохраняем в store
|
|
if (!this.transformStore[this.target.modelId]) {
|
|
this.transformStore[this.target.modelId] = {};
|
|
}
|
|
const key = this.target.objectId || "__model__";
|
|
this.transformStore[this.target.modelId][key] = {
|
|
position: [...model.position],
|
|
rotation: [...model.rotation],
|
|
scale: model.scale ? [...model.scale] : [1, 1, 1],
|
|
};
|
|
this.onTransformChange?.({
|
|
modelId: this.target.modelId,
|
|
objectId: this.target.objectId,
|
|
transform: {
|
|
position: [...model.position],
|
|
rotation: [...model.rotation],
|
|
scale: model.scale ? [...model.scale] : [1, 1, 1],
|
|
},
|
|
});
|
|
}
|
|
|
|
_bindTree() {
|
|
if (this.treeView?.on) {
|
|
this.treeView.on("nodeTitleClicked", (e) => this.handleTreeNodeClick(e));
|
|
// подстрахуемся: некоторые деревья бросают другой эвент
|
|
this.treeView.on("nodeClicked", (e) => this.handleTreeNodeClick(e));
|
|
}
|
|
}
|
|
|
|
_log(msg, isError = false) {
|
|
if (isError) {
|
|
console.error("[TransformGizmo]", msg);
|
|
} else {
|
|
console.log("[TransformGizmo]", msg);
|
|
}
|
|
}
|
|
}
|