import { Object3D, BaseComponent, MeshBuilder, MathUtils, Box, Sphere, Mesh, FatLine, PlaneRegion } from '@uino/thing';
/**
* TransformControlComponent 物体变换组件
* @class
* @summary 提供物体变换的功能,变换模式包括平移、旋转和缩放三种,坐标系变换包括世界坐标系和本地坐标系两种。
* 注意:不能跟拖拽组件(DragComponent)或物体拖拽组件(DragControlComponent)同时使用。
* 可通过组件所依附的对象注册变换事件,object.on('TRANSFORM_CONTROL_UPDATE',()=>{ console.log('change') })
* @extends {THING.BaseComponent}
* @memberof THING
* @example
* var app = new THING.App();
* var gui = new dat.GUI();
*
* app.camera.position = [20,20,20];
* app.background = [0.5, 0.5, 0.5];
*
* for (let i = 0; i < 5; i++) {
* const name = 'box' + i;
* window[name] = new THING.Box(2,2,2);
* window[name].position = [-10 + i * 4, 0, 0]
* }
*
* const component = new THING.EXTEND.TransformControlComponent();
*
* // 创建一个空对象,通过setObjects给多个对象添加组件
* window.emptyObject = new THING.Object3D();
* emptyObject.addComponent(component,'transformControlComponent');
* emptyObject.transformControlComponent.setObjects([box0, box1, box3])
*
* // 监听物体变换组件更新事件
* emptyObject.on('TRANSFORM_CONTROL_UPDATE', () => {
* console.warn('update');
* });
*
* const transformation = [
* 'translate',
* 'angle',
* 'scale'
* ];
* const coordinate = [
* 'worldCoordinate',
* 'localCoordinate'
* ];
*
* const dataObj = {
* Coordinate:coordinate[0],
* Transformation:transformation[0],
* Snap:0,
* KeepSize:false,
* Size:1
* };
*
* gui.add(dataObj, 'Coordinate',coordinate).onChange(function(){
* if(dataObj.Coordinate === 'worldCoordinate'){
* emptyObject.transformControlComponent.space = 'world';
* }else if(dataObj.Coordinate === 'localCoordinate'){
* emptyObject.transformControlComponent.space = 'local';
* }
* });
*
* gui.add(dataObj, 'Transformation',transformation).onChange(function(){
* if(dataObj.Transformation === 'translate'){
* emptyObject.transformControlComponent.mode = 'translate';
* }else if(dataObj.Transformation === 'angle'){
* emptyObject.transformControlComponent.mode = 'angle';
* }else if(dataObj.Transformation === 'scale'){
* emptyObject.transformControlComponent.mode = 'scale';
* }
* });
*
* gui.add(dataObj,'Snap',0, 45).step(0.1).onChange(function(e){
* emptyObject.transformControlComponent.snap = dataObj.Snap;
* });
*
* let factor = gui.addFolder('factor');
* factor.add(dataObj,'KeepSize').onChange(function(e){
* emptyObject.transformControlComponent.keepSize = dataObj.KeepSize;
* });
*
* factor.add(dataObj,'Size',0.1, 10).step(0.1).onChange(function(e){
* emptyObject.transformControlComponent.size = dataObj.Size;
* });
* @public
*/
class TransformControlComponent extends BaseComponent {
static isInstancedComponent = true;
/**
* 变换控制组件类构造函数,用于实现物体变换的功能。
* @param {object} param 参数列表
*/
constructor(param) {
super(param);
this._configData = {
"AxisX": [1, 0, 0],
"AxisY": [0, 1, 0],
"AxisZ": [0, 0, 1],
"AxisXY": [1, 1, 0],
"AxisYZ": [0, 1, 1],
"AxisXZ": [1, 0, 1],
"AxisXYZ": [1, 1, 1],
"anglesX": [1, 0, 0],
"anglesY": [0, 1, 0],
"anglesZ": [0, 0, 1],
"anglesXYZ": [1, 1, 1],
"ScaleX": [1, 0, 0],
"ScaleY": [0, 1, 0],
"ScaleZ": [0, 0, 1],
"ScaleXYZ": [1, 1, 1]
}
this._intersection = [];
this._objHelpers = [];
this._plane = { normal: [], constant: 0 };
this._raycaster = { origin: [], direction: [] };
this._mouse = [];
this._offset = [];
this._selected = null;
this._scaleData = [];
this._newVariety = [];
this._statsData = [];
this._startPoint = [];
this._objectPoint = [];
this._objQuaterStart = [];
this._helperCurrentColor = null;
this._isEnterDown = false;
this._downName = null;
this._jd = 0;
this._objCurrentQuaterEng = null;
this._helperParentObj = null;
this._objAngleQuaternionX = [];
this._objAngleQuaternionY = [];
this._objAngleQuaternionZ = [];
this._space = 'world'; // world,local
this._mode = 'translate'; // translate, rotate, scale
this._snap = 0;
this._keepSize = true;
this._size = 1;
this._objMap = {};
this._emptyObjects = [];
// 新增:轴锁定功能
this._lockedAxes = { x: false, y: false, z: false }; // 锁定的轴
}
onAdd(object) {
super.onAdd(object);
// 总是创建helper,确保轴锁定功能正常工作
this._createHelperAxis();
this._enterLevelEvent();
this._dragEvent();
}
onActiveChange(value) {
if (this._helperParentObj) {
this._helperParentObj.setActive(value, true);
}
}
onVisibleChange(value) {
if (this.object.active) {
if (this._helperParentObj) {
this._helperParentObj.setVisible(value, true);
}
}
}
onRemove() {
this.object.app.off('mouseenter', 'TransformControlComponentEnterEnter');
this.object.app.off('mouseleave', 'TransformControlComponentEnterLeave');
this.object.app.off('mousedown', 'TransformControlComponentEnterDown');
this.object.app.off('mouseup', 'TransformControlComponentEnterUp');
this.object.app.off('mousedown', 'TransformControlComponentDragDown');
this.object.app.off('mousemove', 'TransformControlComponentDragMove');
this.object.app.off('mouseup', 'TransformControlComponentDragUp');
this._destroys();
super.onRemove();
}
onUpdate() {
if (this._keepSize) {
let dis = MathUtils.sqrt(MathUtils.getDistanceToSquared(this.object.position, this.object.app.camera.position));
let axisFactor = dis * MathUtils.min(1.9 * MathUtils.tan(MathUtils.PI * this.object.app.camera.fov / 360), 7);
this._helperParentObj.scale =
[
axisFactor / 15 * this._size,
axisFactor / 15 * this._size,
axisFactor / 15 * this._size
];
}
if (this._helperParentObj) {
this._helperParentObj.position = this.object.position;
if (this._space === 'local' || this._mode === 'scale') {
this._helperParentObj.quaternion = this.object.quaternion;
}
}
if (this._mode === 'rotate') {
let eye = MathUtils.normalizeVector(MathUtils.subVector(this.object.app.camera.position, this._helperParentObj.position));
let alignVector = MathUtils.vec3ApplyQuat(eye, this._invert(this._helperParentObj.quaternion));
this._updateAngleAxis('anglesX', [1, 0, 0], [-alignVector[1], alignVector[2]], this._objAngleQuaternionX)
this._updateAngleAxis('anglesY', [0, 1, 0], [alignVector[0], alignVector[2]], this._objAngleQuaternionY)
this._updateAngleAxis('anglesZ', [0, 0, 1], [alignVector[1], alignVector[0]], this._objAngleQuaternionZ)
}
}
_updateAngleAxis(objName, dir, eyeDir, angleQuaternion) {
const obj = this._helperParentObj.children.query(objName)[0];
if (!obj) { return; }
let eyeQuaternion = MathUtils.getQuatFromAxisAngle(dir, MathUtils.radToDeg(MathUtils.atan2(eyeDir[0], eyeDir[1])));
if (this._space === 'local') {
let initialQuaternion = this._helperParentObj.quaternion;
let currentQuaternion = MathUtils.multiplyQuat(eyeQuaternion, angleQuaternion);
obj.quaternion = MathUtils.multiplyQuat(initialQuaternion, currentQuaternion);
}
else {
obj.quaternion = MathUtils.multiplyQuat(eyeQuaternion, angleQuaternion);
}
}
_createHelperAxis() {
this._destroys();
this._helperParentObj = new Object3D();
this._helperParentObj.active = !!this._objMap && Object.keys(this._objMap).length > 0;
if (this._mode === 'translate') {
// 根据轴锁定状态创建helper
if (!this._lockedAxes.x) {
this._createAxisLine([0, 0, 0], [0, 0, 0, 1], [1, 1, 1], 'red', false, 'AxisX');
this._createAxisCone([2, 0, 0], [0, 0, -0.7071067811865475, 0.7071067811865476], [1, 1, 1], 'red', false, 'AxisX');
}
if (!this._lockedAxes.y) {
this._createAxisLine([0, 0, 0], [0, 0, 0.7071067811865475, 0.7071067811865476], [1, 1, 1], 'green', false, 'AxisY');
this._createAxisCone([0, 2, 0], [0, 0, 0, 1], [1, 1, 1], 'green', false, 'AxisY');
}
if (!this._lockedAxes.z) {
this._createAxisLine([0, 0, 0], [0, -0.7071067811865475, 0, 0.7071067811865476], [1, 1, 1], 'blue', false, 'AxisZ');
this._createAxisCone([0, 0, 2], [0.7071067811865475, 0, 0, 0.7071067811865476], [1, 1, 1], 'blue', false, 'AxisZ');
}
// 平面helper - 只有当对应的轴都未锁定时才显示
if (!this._lockedAxes.x && !this._lockedAxes.y) {
this._createAxisPlane([0.3, 0.3, 0], [-0.7071067811865475, 0, 0, 0.7071067811865476], [1, 1, 1], 'blue', false, 'AxisXY');
}
if (!this._lockedAxes.y && !this._lockedAxes.z) {
this._createAxisPlane([0, 0.3, 0.3], [0, 0, 0.7071067811865475, 0.7071067811865476], [1, 1, 1], 'red', false, 'AxisYZ');
}
if (!this._lockedAxes.x && !this._lockedAxes.z) {
this._createAxisPlane([0.3, 0, 0.3], [0, 0, 0, 1], [1, 1, 1], 'green', false, 'AxisXZ');
}
// 中心球 - 只有当所有轴都未锁定时才显示
if (!this._lockedAxes.x && !this._lockedAxes.y && !this._lockedAxes.z) {
this._createAxisCenter([0, 0, 0], [0, 0, 0, 1], [1, 1, 1], 'black', false, 'AxisXYZ');
}
}
else if (this._mode === 'rotate') {
// 旋转模式下的轴控制
if (!this._lockedAxes.x) {
this._createAxisCircle([0, 0, 0], [0.5, 0.5, 0.5, 0.5], [1, 1, 1], 'red', false, 'anglesX', 0.5);
}
if (!this._lockedAxes.y) {
this._createAxisCircle([0, 0, 0], [0.7071067811865475, 0, 0, 0.7071067811865476], [1, 1, 1], 'green', false, 'anglesY', 0.5);
}
if (!this._lockedAxes.z) {
this._createAxisCircle([0, 0, 0], [0, 0, 0.7071067811865476, -0.7071067811865476], [1, 1, 1], 'blue', false, 'anglesZ', 0.5);
}
// 多轴旋转 - 只有当对应的轴都未锁定时才显示
if (!this._lockedAxes.x && !this._lockedAxes.y && !this._lockedAxes.z) {
this._createAxisCircle([0, 0, 0], [0, 0, 0, 1], [1.5, 1.5, 1], 'cyan', false, 'anglesXYZ', 1);
this._createAxisCircle([0, 0, 0], [0, 0, 0, 1], [1, 1, 1], '0x787878', false, 'anglesXYZE', 1);
}
}
else if (this._mode === 'scale') {
// 缩放模式下的轴控制
if (!this._lockedAxes.x) {
this._createAxisLine([0, 0, 0], [0, 0, 0, 1], [1, 1, 1], 'red', false, 'ScaleX');
this._createScaleBox([2, 0, 0], [0, 0, 0, 1], [1, 1, 1], 'red', false, 'ScaleX');
}
if (!this._lockedAxes.y) {
this._createAxisLine([0, 0, 0], [0, 0, 0.7071067811865475, 0.7071067811865476], [1, 1, 1], 'green', false, 'ScaleY');
this._createScaleBox([0, 2, 0], [0, 0, 0, 1], [1, 1, 1], 'green', false, 'ScaleY');
}
if (!this._lockedAxes.z) {
this._createAxisLine([0, 0, 0], [0, -0.7071067811865475, 0, 0.7071067811865476], [1, 1, 1], 'blue', false, 'ScaleZ');
this._createScaleBox([0, 0, 2], [0, 0, 0, 1], [1, 1, 1], 'blue', false, 'ScaleZ');
}
// 平面缩放 - 只有当对应的轴都未锁定时才显示
if (!this._lockedAxes.x && !this._lockedAxes.y) {
this._createAxisPlane([0.3, 0.3, 0], [-0.7071067811865475, 0, 0, 0.7071067811865476], [1, 1, 1], 'blue', false, 'AxisXY');
}
if (!this._lockedAxes.y && !this._lockedAxes.z) {
this._createAxisPlane([0, 0.3, 0.3], [0, 0, 0.7071067811865475, 0.7071067811865476], [1, 1, 1], 'red', false, 'AxisYZ');
}
if (!this._lockedAxes.x && !this._lockedAxes.z) {
this._createAxisPlane([0.3, 0, 0.3], [0, 0, 0, 1], [1, 1, 1], 'green', false, 'AxisXZ');
}
// 整体缩放 - 只有当所有轴都未锁定时才显示
if (!this._lockedAxes.x && !this._lockedAxes.y && !this._lockedAxes.z) {
this._createScaleBox([0, 0, 0], [0, 0, 0, 1], [1, 1, 1], 'black', false, 'ScaleXYZ');
}
}
}
_enterLevelEvent() {
let that = this;
let app = this.object.app;
app.on('mouseenter', function (ev) {
if (that._objHelpers.includes(ev.object)) {
if (!that._isEnterDown) {
if (!that._helperCurrentColor) { that._helperCurrentColor = ev.object.style.color.slice(); }
that.object.app.query(ev.object.name).style.color = 'pink';
}
}
}, 'TransformControlComponentEnterEnter');
app.on('mouseleave', function (ev) {
if (!that._isEnterDown) {
if (that._objHelpers.includes(ev.object)) {
if (that._helperCurrentColor) {
that.object.app.query(ev.object.name).style.color = that._helperCurrentColor;
that._helperCurrentColor = null;
}
}
}
}, 'TransformControlComponentEnterLeave');
app.on('mousedown', function (ev) {
if (that._objHelpers.includes(ev.object)) {
that._isEnterDown = true;
that._downName = ev.object.name;
}
}, 'TransformControlComponentEnterDown');
app.on('mouseup', function () {
if (that._helperCurrentColor) {
that.object.app.query(that._downName).style.color = that._helperCurrentColor;
}
that._isEnterDown = false;
that._helperCurrentColor = null;
that._downName = null;
}, 'TransformControlComponentEnterUp');
}
_dragEvent() {
let app = this.object.app;
let that = this;
app.on("mousedown", function (ev) {
if (!that._objHelpers.includes(ev.object)) { return; }
app.camera.control.enable = false;
that._selected = ev.object;
that._plane.normal = app.camera.transform.getForward();
that._plane.constant = -MathUtils.dotVector(that._plane.normal, ev.object.position);
that._mouse = app.camera.screenToWorld([ev.x, ev.y, 0]);
if (app.camera.projectionType === 'Orthographic') {
that._raycaster.origin = app.camera.screenToWorld([ev.x, ev.y,
(app.camera.near + app.camera.far) / (app.camera.near - app.camera.far)]);
that._raycaster.direction = MathUtils.transformDirection([0, 0, -1], app.camera.matrixWorld);
}
else {
that._raycaster.origin = app.camera.position;
that._raycaster.direction = MathUtils.normalizeVector(MathUtils.subVector(that._mouse, app.camera.position));
}
that._intersection = app.camera.intersectPlane(ev.x, ev.y, that._plane.normal, that._plane.constant);
if (that._intersection) {
that._objectPoint = that.object.position;
that._startPoint = MathUtils.subVector(that._intersection, that._objectPoint);
that._objQuaterStart = that.object.quaternion;
that._offset = MathUtils.subVector(that._intersection, that._objectPoint);
that._scaleData = that.object.scale;
if (that._mode === 'scale') {
that._offset = MathUtils.addVector(that._offset, that.object.position)
}
}
}, 'TransformControlComponentDragDown');
app.on("mousemove", function (ev) {
if (that._selected) {
that._mouse = app.camera.screenToWorld([ev.x, ev.y, 0]);
that._plane.normal = app.camera.transform.getForward();
that._plane.constant = -MathUtils.dotVector(that._plane.normal, that._selected.position);
if (app.camera.projectionType === 'Orthographic') {
that._raycaster.origin = app.camera.screenToWorld([ev.x, ev.y,
(app.camera.near + app.camera.far) / (app.camera.near - app.camera.far)]);
that._raycaster.direction = MathUtils.transformDirection([0, 0, -1], app.camera.matrixWorld);
}
else {
that._raycaster.origin = app.camera.position;
that._raycaster.direction = MathUtils.normalizeVector(MathUtils.subVector(that._mouse, app.camera.position));
}
that._statsData = that._configData[that._selected.name];
that._intersection = app.camera.intersectPlane(ev.x, ev.y, that._plane.normal, that._plane.constant);
if (that._intersection) {
that._intersection = MathUtils.subVector(that._intersection, that._objectPoint);
if (that._mode === 'translate') {
that._turnTranslate();
}
else if (that._mode === 'rotate') {
that._turnAngle();
}
else if (that._mode === 'scale') {
that._turnScale();
}
}
}
}, 'TransformControlComponentDragMove');
app.on("mouseup", function () {
app.camera.control.enable = true;
that._selected = null;
this._jd = 0;
}, 'TransformControlComponentDragUp');
}
_turnTranslate() {
// 应用轴锁定
let effectiveStatsData = [...this._statsData];
if (this._lockedAxes.x) effectiveStatsData[0] = 0;
if (this._lockedAxes.y) effectiveStatsData[1] = 0;
if (this._lockedAxes.z) effectiveStatsData[2] = 0;
for (let i = 0; i < 3; i++) {
let temp = (this._intersection[i] - this._offset[i]);
let snapPos = this._snap > 0 ? temp - (temp % this._snap) : temp;
this._newVariety[i] = (effectiveStatsData[i] ? this._objectPoint[i] + snapPos : this._objectPoint[i]);
}
if (this._space === 'local') {
let offs = MathUtils.subVector(this._intersection, this._startPoint);
let nowVec3x = MathUtils.vec3ApplyQuat([1, 0, 0], this._objQuaterStart);
let newX = MathUtils.dotVector(offs, nowVec3x);
let nowVec3y = MathUtils.vec3ApplyQuat([0, 1, 0], this._objQuaterStart);
let newY = MathUtils.dotVector(offs, nowVec3y);
let nowVec3z = MathUtils.vec3ApplyQuat([0, 0, 1], this._objQuaterStart);
let newZ = MathUtils.dotVector(offs, nowVec3z);
if (this._selected.name.indexOf('X') === -1) newX = 0;
if (this._selected.name.indexOf('Y') === -1) newY = 0;
if (this._selected.name.indexOf('Z') === -1) newZ = 0;
let temp = [
nowVec3x[0] * newX + nowVec3y[0] * newY + nowVec3z[0] * newZ,
nowVec3x[1] * newX + nowVec3y[1] * newY + nowVec3z[1] * newZ,
nowVec3x[2] * newX + nowVec3y[2] * newY + nowVec3z[2] * newZ
];
let beforeSnap = MathUtils.addVector(this._objectPoint, temp);
this.object.position = [
this._snap > 0 ? (beforeSnap[0] - (beforeSnap[0] % this._snap)) : beforeSnap[0],
this._snap > 0 ? (beforeSnap[1] - (beforeSnap[1] % this._snap)) : beforeSnap[1],
this._snap > 0 ? (beforeSnap[2] - (beforeSnap[2] % this._snap)) : beforeSnap[2]
];
}
else {
this.object.position = this._newVariety;
}
this._updateRealObjectsTransform();
}
_turnAngle() {
if ((this._statsData[0] + this._statsData[1] + this._statsData[2]) === 1) {
if (this._space === 'local') {
let axis = MathUtils.vec3ApplyQuat(this._statsData, this._objQuaterStart);
this._jd = this._calculateAngle(axis);
this._objCurrentQuaterEng = MathUtils.getQuatFromAxisAngle(axis, this._jd);
this.object.localQuaternion = MathUtils.multiplyQuat(MathUtils.normalizeQuat(this._objCurrentQuaterEng), this._objQuaterStart);
}
else {
this._jd = this._calculateAngle(this._statsData);
this._objCurrentQuaterEng = MathUtils.getQuatFromAxisAngle(this._statsData, this._jd);
this.object.quaternion = MathUtils.multiplyQuat(this._objCurrentQuaterEng, this._objQuaterStart);
}
}
else {
let cameraNor = MathUtils.normalizeVector(MathUtils.subVector(this.object.app.camera.position, this.app.camera.target));
this._jd = this._calculateAngle(cameraNor);
this._objCurrentQuaterEng = MathUtils.getQuatFromAxisAngle(cameraNor, this._jd)
this.object.quaternion = MathUtils.multiplyQuat(this._objCurrentQuaterEng, this._objQuaterStart);
}
this._updateRealObjectsTransform();
}
_turnScale() {
let scale = [1, 1, 1];
if (this._selected.name === "ScaleXYZ") {
let startLength = MathUtils.getDistance(this._startPoint, [0, 0, 0]);
let nowLength = MathUtils.getDistance(this._intersection, [0, 0, 0]);
let bili = nowLength / startLength;
if (MathUtils.dotVector(this._intersection, this._startPoint) < 0) {
bili *= -1;
}
scale = [
this._snap > 0 ? (this._scaleData[0] * bili - (this._scaleData[0] * bili % this._snap)) : this._scaleData[0] * bili,
this._snap > 0 ? (this._scaleData[1] * bili - (this._scaleData[1] * bili % this._snap)) : this._scaleData[1] * bili,
this._snap > 0 ? (this._scaleData[2] * bili - (this._scaleData[2] * bili % this._snap)) : this._scaleData[2] * bili
];
}
else {
let _tempVector = MathUtils.vec3ApplyQuat(this._startPoint, this._invert(this.object.quaternion));
let _tempVector2 = MathUtils.vec3ApplyQuat(this._intersection, this._invert(this.object.quaternion));
let temp = [
_tempVector2[0] / _tempVector[0],
_tempVector2[1] / _tempVector[1],
_tempVector2[2] / _tempVector[2]
];
if (this._selected.name.indexOf('X') === -1) temp[0] = 1;
if (this._selected.name.indexOf('Y') === -1) temp[1] = 1;
if (this._selected.name.indexOf('Z') === -1) temp[2] = 1;
scale = [
this._scaleData[0] * temp[0],
this._scaleData[1] * temp[1],
this._scaleData[2] * temp[2]
];
}
if (scale[0] === 0) {
scale[0] = 0.001;
}
if (scale[1] === 0) {
scale[1] = 0.001;
}
if (scale[2] === 0) {
scale[2] = 0.001;
}
this.object.scale = scale;
this._updateRealObjectsTransform();
}
_calculateAngle(axis) {
let ON = MathUtils.getDistance(this._intersection, [0, 0, 0]);
let OL = MathUtils.getDistance(this._startPoint, [0, 0, 0]);
let NL = MathUtils.getDistance(this._startPoint, this._intersection);
let cosNL = (OL * OL + ON * ON - NL * NL) / (2 * OL * ON);
let angle = MathUtils.radToDeg(MathUtils.acos(cosNL));
let from = MathUtils.normalizeVector(this._startPoint);
let to = MathUtils.normalizeVector(this._intersection);
let v3 = MathUtils.crossVector(from, to);
let beforeSnap = this._cosData(v3, axis) > 0 ? angle : 360 - angle;
return this._snap > 0 ? (beforeSnap - beforeSnap % this._snap) : beforeSnap;
}
_invert(q) {
let temp = [];
temp[0] = q[0] * -1;
temp[1] = q[1] * -1;
temp[2] = q[2] * -1;
temp[3] = q[3];
return temp;
}
_cosData(a, b) {
return MathUtils.dotVector(a, b) / (MathUtils.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]) * MathUtils.sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]));
}
_createAxisLine(position, angle4, scale, color, depthTest, name) {
let points = [];
let target = MathUtils.addVector([0, 0, 0], MathUtils.scaleVector([1, 0, 0], 1.85));
points.push([0, 0, 0]);
points.push(target);
let axisLine = new FatLine({
selfPoints: points,
width: 3
});
this._objAttribute(axisLine, position, angle4, scale, color, depthTest, name);
}
_createAxisPlane(position, angle4, scale, color, depthTest, name) {
var ZeroPos = [0, 0, 0];
var PosX = [0.4, 0, 0];
var PosZ = [0, 0, 0.4];
var points = [ZeroPos, PosX, MathUtils.addVector(PosX, PosZ), PosZ];
var axisPlane = new PlaneRegion({ points: points });
this._objAttribute(axisPlane, position, angle4, scale, color, depthTest, name);
}
_createAxisCenter(position, angle4, scale, color, depthTest, name) {
let axisXYZCenter = new Sphere(0.15);
this._objAttribute(axisXYZCenter, position, angle4, scale, color, depthTest, name);
}
_createAxisCone(position, angle4, scale, color, depthTest, name) {
let shape = {};
shape.data = MeshBuilder.createCylinder({ radiusTop: 0, radiusBottom: 0.1, height: 0.3 })
let cone = new Mesh(shape);
this._objAttribute(cone, position, angle4, scale, color, depthTest, name);
}
_createAxisCircle(position, angle4, scale, color, depthTest, name, arcs) {
let circleHelper = {};
circleHelper.data = MeshBuilder.createTorus({ radius: 1.4, tube: 0.04, arc: arcs * Math.PI * 2 })
let AxisCircle = new Mesh(circleHelper);
this._objAttribute(AxisCircle, position, angle4, scale, color, depthTest, name);
}
_createScaleBox(position, angle4, scale, color, depthTest, name) {
let axisBox = new Box(0.3, 0.3, 0.3);
this._objAttribute(axisBox, position, angle4, scale, color, depthTest, name);
}
_objAttribute(obj, position, angle4, scale, color, depthTest, name) {
obj.castShadow = false;
obj.receiveShadow = false;
obj.style.lights = true;
obj.style.color = color;
obj.style.opacity = 0.99;
obj.style.depthTest = depthTest;
obj.renderOrder = 100000;
obj.name = name;
if (this._helperParentObj) { obj.parent = this._helperParentObj; }
obj.position = position;
if (obj.name === 'AxisXY' || obj.name === 'AxisYZ' || obj.name === 'AxisXZ') {
obj.style.sideType = 'Double';
}
if (obj.name === 'anglesXYZ' || obj.name === 'anglesXYZE') { obj.lookAt(this.object.app.camera, { always: true }); }
obj.scale = scale;
obj.quaternion = [angle4[0], angle4[1], angle4[2], angle4[3]];
if (obj.name === 'anglesX') {
this._objAngleQuaternionX = [
obj.quaternion[0],
obj.quaternion[1],
obj.quaternion[2],
obj.quaternion[3]
];
}
if (obj.name === 'anglesY') {
this._objAngleQuaternionY = [
obj.quaternion[0],
obj.quaternion[1],
obj.quaternion[2],
obj.quaternion[3]
];
}
if (obj.name === 'anglesZ') {
this._objAngleQuaternionZ = [
obj.quaternion[0],
obj.quaternion[1],
obj.quaternion[2],
obj.quaternion[3]
];
}
if (obj.name === 'anglesXYZE') { return; }
this._objHelpers.push(obj);
}
_destroys() {
if (this._objHelpers.length !== 0) {
for (let val of this._objHelpers) {
if (val) {
if (val.name === 'anglesXYZ' || val.name === 'anglesXYZE') { val.lookAt(); }
val.destroy();
}
}
this._objHelpers = [];
this._helperParentObj.destroy();
this._helperParentObj = null;
}
}
/**
* 控制变换模式,包括平移translate、旋转angle和缩放scale,默认为平移。
* @type {string}
* @public
*/
set mode(mode) {
this._mode = mode;
if (mode === 'angle') {
this._mode = 'rotate';
}
// 总是重新创建helper,确保轴锁定功能正常工作
if (this._helperParentObj) {
this._createHelperAxis();
}
}
get mode() {
return this._mode;
}
/**
* 控制坐标系的变换,包括世界坐标系world和本地坐标系local,默认为世界坐标系。
* @type {string}
* @public
*/
set space(space) {
this._space = space;
// 总是重新创建helper,确保轴锁定功能正常工作
if (this._helperParentObj) {
this._createHelperAxis();
}
}
get space() {
return this._space;
}
/**
* 设置物体操作时平移、旋转和缩放变化的阈值。
* @type {number}
* @public
*/
set snap(snap) {
this._snap = snap;
if (this._snap < 0) {
this._snap = 0;
}
}
get snap() {
return this._snap;
}
/**
* 设置操作杆是否固定呈现出的大小,true开启、false关闭,默认开启。
* @type {boolean}
* @public
*/
set keepSize(bool) {
this._keepSize = bool;
}
get keepSize() {
return this._keepSize;
}
/**
* 操作杆固定大小情况下,再设置操作杆的大小。
* @type {number}
* @public
*/
set size(size) {
this._size = size;
}
get size() {
return this._size;
}
/**
* 设置多个需添加组件的对象。
* @param {Array.<Object>} objs - 需要添加组件的对象。
* @param {boolean} ignoreCenter - 是否忽略中心位置,默认为false,表示不忽略中心位置,需计算中心点。
* @public
*/
setObjects(objs = [], ignoreCenter = false) {
if (!objs.length) {
if (this._helperParentObj) {
this._helperParentObj.active = false;
}
return;
}
let axisPosition = [0, 0, 0];
if (!ignoreCenter) {
let max = [...objs[0].position], min = [...objs[0].position];
for (let i = 1; i < objs.length; i++) {
const objPosition = objs[i].position;
max[0] = MathUtils.max(objPosition[0], max[0]);
max[1] = MathUtils.max(objPosition[1], max[1]);
max[2] = MathUtils.max(objPosition[2], max[2]);
min[0] = MathUtils.min(objPosition[0], min[0]);
min[1] = MathUtils.min(objPosition[1], min[1]);
min[2] = MathUtils.min(objPosition[2], min[2]);
}
axisPosition = MathUtils.addVector(min, MathUtils.divideVector(MathUtils.subVector(max, min), 2));
}
this.object.position = axisPosition;
// 确保helper被创建并激活
if (!this._helperParentObj) {
this._createHelperAxis();
}
this._helperParentObj.active = true;
this._createObjMap(objs);
}
/**
* 获取添加组件的对象。
* @returns {Array.<Object>} 添加组件的对象。
* @public
*/
getObjects() {
return Object.values(this._objMap);
}
_createObjMap(objs) {
// 清空数组和map
if (this._emptyObjects.length) {
for (let i = 0; i < this._emptyObjects.length; i++) {
const emptyObject = this._emptyObjects[i];
emptyObject.destroy();
}
this._emptyObjects = [];
}
this._objMap = {};
if (objs.length === 1) {
this.object.matrixWorld = objs[0].matrixWorld;
}
// 重新设置父子集,数据和map
for (let i = 0; i < objs.length; i++) {
const obj = objs[i];
this._objMap[obj.uuid] = obj;
const emptyObject = new Object3D();
// 设置为空对象的子,操作杆操作的实际为空对象,再把空对象的matrix更新给真正的对象,避免更改场景树的结构
emptyObject.parent = this.object;
// 方便后续通过name字段拿map里的value
emptyObject.name = obj.uuid;
// 继承原先对象的matrix,方便后续直接赋值
emptyObject.matrixWorld = obj.matrixWorld;
this._emptyObjects.push(emptyObject);
}
}
_updateRealObjectsTransform() {
for (let i = 0; i < this._emptyObjects.length; i++) {
const emptyObject = this._emptyObjects[i];
const realObject = this._objMap[emptyObject.name];
realObject.matrixWorld = emptyObject.matrixWorld;
}
this.object.trigger('TRANSFORM_CONTROL_UPDATE');
}
/**
* 锁定指定轴,使其无法移动
* @param {string} axis - 要锁定的轴 ('x', 'y', 'z', 'xy', 'yz', 'xz', 'xyz')
* @param {boolean} locked - 是否锁定
* @public
*/
lockAxis(axis, locked = true) {
let needUpdate = false;
if (axis === 'x' || axis === 'X') {
if (this._lockedAxes.x !== locked) {
this._lockedAxes.x = locked;
needUpdate = true;
}
} else if (axis === 'y' || axis === 'Y') {
if (this._lockedAxes.y !== locked) {
this._lockedAxes.y = locked;
needUpdate = true;
}
} else if (axis === 'z' || axis === 'Z') {
if (this._lockedAxes.z !== locked) {
this._lockedAxes.z = locked;
needUpdate = true;
}
} else if (axis === 'xy' || axis === 'XY') {
if (this._lockedAxes.x !== locked || this._lockedAxes.y !== locked) {
this._lockedAxes.x = locked;
this._lockedAxes.y = locked;
needUpdate = true;
}
} else if (axis === 'yz' || axis === 'YZ') {
if (this._lockedAxes.y !== locked || this._lockedAxes.z !== locked) {
this._lockedAxes.y = locked;
this._lockedAxes.z = locked;
needUpdate = true;
}
} else if (axis === 'xz' || axis === 'XZ') {
if (this._lockedAxes.x !== locked || this._lockedAxes.z !== locked) {
this._lockedAxes.x = locked;
this._lockedAxes.z = locked;
needUpdate = true;
}
} else if (axis === 'xyz' || axis === 'XYZ') {
if (this._lockedAxes.x !== locked || this._lockedAxes.y !== locked || this._lockedAxes.z !== locked) {
this._lockedAxes.x = locked;
this._lockedAxes.y = locked;
this._lockedAxes.z = locked;
needUpdate = true;
}
}
// 如果轴锁定状态发生变化,重新创建helper
if (needUpdate && this._helperParentObj) {
this._createHelperAxis();
// 确保helper的激活状态正确
if (this._helperParentObj) {
this._helperParentObj.active = !!this._objMap && Object.keys(this._objMap).length > 0;
}
}
}
/**
* 锁定所有轴
* @public
*/
lockAllAxes() {
this.lockAxis('xyz', true);
}
/**
* 解锁所有轴
* @public
*/
unlockAllAxes() {
this.lockAxis('xyz', false);
}
/**
* 解锁指定轴
* @param {string} axis - 要解锁的轴 ('x', 'y', 'z', 'xy', 'yz', 'xz', 'xyz')
* @public
*/
unlockAxis(axis) {
this.lockAxis(axis, false);
}
/**
* 获取轴的锁定状态
* @param {string} axis - 轴名称 ('x', 'y', 'z')
* @returns {boolean} 是否锁定
* @public
*/
isAxisLocked(axis) {
if (axis === 'x' || axis === 'X') {
return this._lockedAxes.x;
} else if (axis === 'y' || axis === 'Y') {
return this._lockedAxes.y;
} else if (axis === 'z' || axis === 'Z') {
return this._lockedAxes.z;
}
return false;
}
/**
* 获取当前轴锁定状态
* @returns {object} 轴锁定状态
* @public
*/
getLockedAxesState() {
return { ...this._lockedAxes };
}
}
export { TransformControlComponent }