manipulator3d
Version:
3D Manipulator Gizmo for Threejs
971 lines (970 loc) • 32.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
import { Group, MeshBasicMaterial, DoubleSide, LineBasicMaterial, BufferGeometry, Float32BufferAttribute, SphereGeometry, TorusGeometry, CylinderGeometry, Mesh, Line, Raycaster, Vector3, Quaternion } from "three";
function vec3_copy(out, a) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
return out;
}
function vec3_add(out, a, b) {
out[0] = a[0] + b[0];
out[1] = a[1] + b[1];
out[2] = a[2] + b[2];
return out;
}
function vec3_sub(out, a, b) {
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
out[2] = a[2] - b[2];
return out;
}
function vec3_cross(out, a, b) {
const ax = a[0], ay = a[1], az = a[2], bx = b[0], by = b[1], bz = b[2];
out[0] = ay * bz - az * by;
out[1] = az * bx - ax * bz;
out[2] = ax * by - ay * bx;
return out;
}
function vec3_dot(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
function vec3_scaleAndAdd(out, add, v, s) {
out[0] = v[0] * s + add[0];
out[1] = v[1] * s + add[1];
out[2] = v[2] * s + add[2];
return out;
}
function vec3_scale(out, a, s) {
out[0] = a[0] * s;
out[1] = a[1] * s;
out[2] = a[2] * s;
return out;
}
function vec3_norm(out, a) {
let mag = Math.sqrt(a[0] ** 2 + a[1] ** 2 + a[2] ** 2);
if (mag != 0) {
mag = 1 / mag;
out[0] = a[0] * mag;
out[1] = a[1] * mag;
out[2] = a[2] * mag;
}
return out;
}
function vec3_transformQuat(out, v, q) {
const qx = q[0], qy = q[1], qz = q[2], qw = q[3], vx = v[0], vy = v[1], vz = v[2], x1 = qy * vz - qz * vy, y1 = qz * vx - qx * vz, z1 = qx * vy - qy * vx, x2 = qw * x1 + qy * z1 - qz * y1, y2 = qw * y1 + qz * x1 - qx * z1, z2 = qw * z1 + qx * y1 - qy * x1;
out[0] = vx + 2 * x2;
out[1] = vy + 2 * y2;
out[2] = vz + 2 * z2;
return out;
}
function vec3_sqrLen(a, b) {
if (b === void 0)
return a[0] ** 2 + a[1] ** 2 + a[2] ** 2;
return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2;
}
function vec3_len(a, b) {
if (b === void 0)
return Math.sqrt(a[0] ** 2 + a[1] ** 2 + a[2] ** 2);
return Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2);
}
function vec3_mul(out, a, b) {
out[0] = a[0] * b[0];
out[1] = a[1] * b[1];
out[2] = a[2] * b[2];
return out;
}
function vec3_lerp(out, a, b, t) {
const ti = 1 - t;
out[0] = a[0] * ti + b[0] * t;
out[1] = a[1] * ti + b[1] * t;
out[2] = a[2] * ti + b[2] * t;
return out;
}
function quat_copy(out, a) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
return out;
}
function quat_mul(out, a, b) {
const ax = a[0], ay = a[1], az = a[2], aw = a[3], bx = b[0], by = b[1], bz = b[2], bw = b[3];
out[0] = ax * bw + aw * bx + ay * bz - az * by;
out[1] = ay * bw + aw * by + az * bx - ax * bz;
out[2] = az * bw + aw * bz + ax * by - ay * bx;
out[3] = aw * bw - ax * bx - ay * by - az * bz;
return out;
}
function quat_normalize(out, q) {
let len = q[0] ** 2 + q[1] ** 2 + q[2] ** 2 + q[3] ** 2;
if (len > 0) {
len = 1 / Math.sqrt(len);
out[0] = q[0] * len;
out[1] = q[1] * len;
out[2] = q[2] * len;
out[3] = q[3] * len;
}
return out;
}
function quat_setAxisAngle(out, axis, rad) {
const half = rad * 0.5, s = Math.sin(half);
out[0] = s * axis[0];
out[1] = s * axis[1];
out[2] = s * axis[2];
out[3] = Math.cos(half);
return out;
}
function quat_sqrLen(a, b) {
if (b === void 0)
return a[0] ** 2 + a[1] ** 2 + a[2] ** 2 + a[3] ** 2;
return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2 + (a[3] - b[3]) ** 2;
}
class Ray {
constructor() {
__publicField(this, "posStart", [0, 0, 0]);
__publicField(this, "posEnd", [0, 0, 0]);
__publicField(this, "direction", [0, 0, 0]);
__publicField(this, "vecLength", [0, 0, 0]);
}
posAt(t, out) {
out = out || [0, 0, 0];
out[0] = this.vecLength[0] * t + this.posStart[0];
out[1] = this.vecLength[1] * t + this.posStart[1];
out[2] = this.vecLength[2] * t + this.posStart[2];
return out;
}
directionAt(len, out) {
out = out || [0, 0, 0];
out[0] = this.direction[0] * len + this.posStart[0];
out[1] = this.direction[1] * len + this.posStart[1];
out[2] = this.direction[2] * len + this.posStart[2];
return out;
}
fromCaster(caster) {
vec3_copy(this.posStart, caster.ray.origin.toArray());
vec3_copy(this.direction, caster.ray.direction.toArray());
const len = caster.far == Infinity ? 1e3 : caster.far;
vec3_scale(this.vecLength, this.direction, len);
vec3_add(this.posEnd, this.posStart, this.vecLength);
}
}
function intersectSphere(ray, origin, radius) {
const radiusSq = radius * radius;
const rayToCenter = vec3_sub([0, 0, 0], origin, ray.posStart);
const tProj = vec3_dot(rayToCenter, ray.direction);
const oppLenSq = vec3_sqrLen(rayToCenter) - tProj * tProj;
return !(oppLenSq > radiusSq);
}
function intersectPlane(ray, planePos, planeNorm) {
const denom = vec3_dot(ray.vecLength, planeNorm);
if (denom <= 1e-6 && denom >= -1e-6)
return null;
const t = vec3_dot(vec3_sub([0, 0, 0], planePos, ray.posStart), planeNorm) / denom;
return t >= 0 ? t : null;
}
function intersectTri(ray, v0, v1, v2, out, cullFace = true) {
const v0v1 = vec3_sub([0, 0, 0], v1, v0);
const v0v2 = vec3_sub([0, 0, 0], v2, v0);
const pvec = vec3_cross([0, 0, 0], ray.direction, v0v2);
const det = vec3_dot(v0v1, pvec);
if (cullFace && det < 1e-6)
return false;
const idet = 1 / det;
const tvec = vec3_sub([0, 0, 0], ray.posStart, v0);
const u = vec3_dot(tvec, pvec) * idet;
if (u < 0 || u > 1)
return false;
const qvec = vec3_cross([0, 0, 0], tvec, v0v1);
const v = vec3_dot(ray.direction, qvec) * idet;
if (v < 0 || u + v > 1)
return false;
if (out) {
const len = vec3_dot(v0v2, qvec) * idet;
ray.directionAt(len, out);
}
return true;
}
function nearPoint(ray, p, distLimit = 0.1) {
const v = vec3_sub([0, 0, 0], p, ray.posStart);
vec3_mul(v, v, ray.vecLength);
const t = (v[0] + v[1] + v[2]) / vec3_sqrLen(ray.vecLength);
if (t < 0 || t > 1)
return null;
const lenSqr = vec3_sqrLen(ray.posAt(t, v), p);
return lenSqr <= distLimit * distLimit ? t : null;
}
class NearSegmentResult {
constructor() {
__publicField(this, "segPosition", [0, 0, 0]);
__publicField(this, "rayPosition", [0, 0, 0]);
__publicField(this, "distanceSq", 0);
__publicField(this, "distance", 0);
}
}
function nearSegment(ray, p0, p1, results = null) {
const u = vec3_sub([0, 0, 0], p1, p0), v = ray.vecLength, w = vec3_sub([0, 0, 0], p0, ray.posStart), a = vec3_dot(u, u), b = vec3_dot(u, v), c = vec3_dot(v, v), d = vec3_dot(u, w), e = vec3_dot(v, w), D = a * c - b * b;
let tU = 0, tV = 0;
if (D < 1e-6) {
tU = 0;
tV = b > c ? d / b : e / c;
} else {
tU = (b * e - c * d) / D;
tV = (a * e - b * d) / D;
}
if (tU < 0 || tU > 1 || tV < 0 || tV > 1)
return false;
if (results) {
vec3_lerp(results.rayPosition, ray.posStart, ray.posEnd, tV);
vec3_lerp(results.segPosition, p0, p1, tU);
results.distance = vec3_len(results.segPosition, results.rayPosition);
}
return true;
}
const ManipulatorMode = Object.freeze({
Translate: 0,
Rotate: 1,
Scale: 2
});
class ManipulatorData {
constructor() {
__publicField(this, "scaleFactor", 10);
__publicField(this, "minFlipAdjust", -0.02);
__publicField(this, "minHitDistance", 0.1);
__publicField(this, "minPntDistance", 0.1);
__publicField(this, "minArcDistance", 0.1);
__publicField(this, "lastCamPos", [0, 0, 0]);
__publicField(this, "lastCamRot", [0, 0, 0, 1]);
__publicField(this, "hasHit", false);
__publicField(this, "hasUpdated", true);
__publicField(this, "isDragging", false);
__publicField(this, "isActive", false);
__publicField(this, "intersectPos", [0, 0, 0]);
__publicField(this, "position", [0, 0, 0]);
__publicField(this, "scale", [1, 1, 1]);
__publicField(this, "infoScale", 1);
__publicField(this, "arcRadius", 1.5);
__publicField(this, "axisLen", 1.5);
__publicField(this, "midPointLen", 0.55);
__publicField(this, "sclPointLen", 1.8);
__publicField(this, "useTranslate", true);
__publicField(this, "useScale", true);
__publicField(this, "useRotate", true);
__publicField(this, "axes", [
{ dir: [1, 0, 0], endPos: [1, 0, 0], midPos: [0, 0, 0], sclPos: [0, 0, 0] },
{ dir: [0, 1, 0], endPos: [0, 1, 0], midPos: [0, 0, 0], sclPos: [0, 0, 0] },
{ dir: [0, 0, 1], endPos: [0, 0, 1], midPos: [0, 0, 0], sclPos: [0, 0, 0] }
]);
__publicField(this, "traceStep", 0.1);
__publicField(this, "traceLine", {
isActive: false,
hitPos: [0, 0, 0],
origin: [0, 0, 0],
a: [0, 0, 0],
b: [0, 0, 0],
dir: [0, 0, 0]
});
__publicField(this, "activeMode", ManipulatorMode.Translate);
__publicField(this, "activeAxis", -1);
__publicField(this, "activePlane", -1);
__publicField(this, "onDragStart", null);
__publicField(this, "onDragEnd", null);
__publicField(this, "onTranslate", null);
__publicField(this, "onRotate", null);
__publicField(this, "onScale", null);
}
setPosition(x, y, z) {
if (x.length === 3) {
this.position[0] = x[0];
this.position[1] = x[1];
this.position[2] = x[2];
} else {
this.position[0] = x;
this.position[1] = y;
this.position[2] = z;
}
this.hasUpdated = true;
return this;
}
updateFromCamera(camPos, camRot, forceUpdate = false) {
if ((this.isDragging || Math.abs(vec3_sqrLen(camPos, this.lastCamPos)) <= 1e-6 && Math.abs(quat_sqrLen(camRot, this.lastCamRot)) <= 1e-6) && !forceUpdate)
return this;
vec3_copy(this.lastCamPos, camPos);
quat_copy(this.lastCamRot, camRot);
this._calcCameraScale();
return this;
}
resetState() {
this.traceLine.isActive = false;
this.activeMode = ManipulatorMode.Translate;
this.activeAxis = -1;
this.activePlane = -1;
}
startDrag() {
this.isDragging = true;
if (this.onDragStart)
this.onDragStart();
}
stopDrag() {
this.isDragging = false;
if (this.onDragEnd)
this.onDragEnd();
}
_movePlane(ray, i) {
const norm = this.axes[i].dir;
const t = intersectPlane(ray, this.position, norm);
if (t != null) {
ray.posAt(t, this.position);
this.calcAxesPosition();
if (this.onTranslate)
this.onTranslate(this.position.slice(0));
return true;
}
return false;
}
_moveTrace(ray) {
const segResult = new NearSegmentResult();
if (nearSegment(ray, this.traceLine.a, this.traceLine.b, segResult)) {
vec3_copy(this.traceLine.hitPos, segResult.segPosition);
switch (this.activeMode) {
case ManipulatorMode.Translate:
vec3_copy(this.position, segResult.segPosition);
this.calcAxesPosition();
if (this.onTranslate)
this.onTranslate(segResult.segPosition.slice(0));
break;
case ManipulatorMode.Rotate:
case ManipulatorMode.Scale:
const dir = vec3_sub([0, 0, 0], segResult.segPosition, this.traceLine.origin);
const dist = vec3_len(dir);
const sign = Math.sign(vec3_dot(dir, this.traceLine.dir));
const step = dist / this.traceStep * sign;
if (this.onRotate && this.activeMode === ManipulatorMode.Rotate) {
this.onRotate(step, this.activeAxis);
}
if (this.onScale && this.activeMode === ManipulatorMode.Scale) {
this.onScale(step, this.activeAxis >= 0 ? this.activeAxis : null);
}
break;
}
return true;
}
return false;
}
onRayDown(ray) {
if (this.isActive && this._rayIntersect(ray)) {
this.startDrag();
return true;
}
return false;
}
onRayHover(ray) {
return this.isActive && !this.isDragging ? this._rayIntersect(ray) : false;
}
onRayMove(ray) {
if (this.isActive && !this.isDragging)
return false;
if (this.activeAxis != -1) {
this._moveTrace(ray);
} else if (this.activePlane != -1) {
return this._movePlane(ray, this.activePlane);
}
return false;
}
_setTraceLine(pos, axis = -1) {
this.traceLine.isActive = true;
vec3_copy(this.traceLine.origin, pos);
vec3_copy(this.traceLine.hitPos, pos);
if (axis == -1) {
vec3_transformQuat(this.traceLine.dir, [1, 0, 0], this.lastCamRot);
} else {
vec3_copy(this.traceLine.dir, this.axes[axis].dir);
}
vec3_scaleAndAdd(this.traceLine.a, pos, this.traceLine.dir, -1e3);
vec3_scaleAndAdd(this.traceLine.b, pos, this.traceLine.dir, 1e3);
}
_calcCameraScale() {
const eyeDir = vec3_sub([0, 0, 0], this.lastCamPos, this.position);
const eyeLen = vec3_len(eyeDir);
this.infoScale = eyeLen / this.scaleFactor;
vec3_norm(eyeDir, eyeDir);
vec3_scale(this.scale, [1, 1, 1], this.infoScale);
if (vec3_dot(eyeDir, [1, 0, 0]) < this.minFlipAdjust)
this.scale[0] = -this.scale[0];
if (vec3_dot(eyeDir, [0, 1, 0]) < this.minFlipAdjust)
this.scale[1] = -this.scale[1];
if (vec3_dot(eyeDir, [0, 0, 1]) < this.minFlipAdjust)
this.scale[2] = -this.scale[2];
this.calcAxesPosition();
}
calcAxesPosition() {
const a = this.axes;
for (let i = 0; i < 3; i++) {
vec3_scaleAndAdd(a[i].endPos, this.position, a[i].dir, this.scale[i] * this.axisLen);
vec3_scaleAndAdd(a[i].midPos, this.position, a[i].dir, this.scale[i] * this.midPointLen);
vec3_scaleAndAdd(a[i].sclPos, this.position, a[i].dir, this.scale[i] * this.sclPointLen);
}
this.hasUpdated = true;
}
_rayIntersect(ray) {
if (!this._testSphere(ray)) {
return false;
}
const lastAxis = this.activeAxis;
this.resetState();
let hit = this.useScale && this._testPoints(ray);
hit = hit || this.useTranslate && this._testPlanes(ray);
hit = hit || this.useTranslate && this._testAxis(ray);
hit = hit || this.useRotate && this._testArc(ray);
if (lastAxis !== this.activeAxis) {
this.hasUpdated = true;
}
this.hasHit = hit;
return hit;
}
_testSphere(ray) {
return intersectSphere(ray, this.position, (this.sclPointLen + this.minPntDistance) * this.infoScale);
}
_testPlanes(ray) {
const a = this.axes;
let i, ii;
for (i = 0; i < 3; i++) {
ii = (i + 1) % 3;
if (intersectTri(ray, a[i].midPos, a[ii].midPos, this.position, this.intersectPos, false)) {
this.activePlane = (i + 2) % 3;
return true;
}
}
return false;
}
_testArc(ray) {
const minDistance = this.infoScale * this.minArcDistance;
const a = this.axes;
const hitPos = [0, 0, 0];
const hitDir = [0, 0, 0];
const axis = [0, 0, 0];
const radius = this.arcRadius * this.infoScale;
let t, dist;
let i, ii, iii;
for (i = 0; i < 3; i++) {
t = intersectPlane(ray, this.position, a[i].dir);
if (t === null)
continue;
ray.posAt(t, hitPos);
dist = vec3_len(this.position, hitPos);
if (Math.abs(dist - radius) <= minDistance) {
ii = (i + 1) % 3;
vec3_sub(hitDir, hitPos, this.position);
vec3_scale(axis, a[ii].dir, Math.sign(this.scale[ii]));
if (vec3_dot(hitDir, axis) >= 0) {
iii = (i + 2) % 3;
vec3_scale(axis, a[iii].dir, Math.sign(this.scale[iii]));
if (vec3_dot(hitDir, axis) >= 0) {
this.activeAxis = i;
this.activeMode = ManipulatorMode.Rotate;
this._setTraceLine(hitPos);
vec3_copy(this.intersectPos, hitPos);
return true;
}
}
}
}
return false;
}
_testAxis(ray) {
const minDistance = this.infoScale * this.minHitDistance;
const segResult = new NearSegmentResult();
const pos = [0, 0, 0];
let min = Infinity;
let axis = -1;
let ax;
for (let i = 0; i < 3; i++) {
ax = this.axes[i];
if (nearSegment(ray, this.position, ax.endPos, segResult)) {
if (segResult.distance <= minDistance && segResult.distance < min) {
min = segResult.distance;
axis = i;
vec3_copy(pos, segResult.segPosition);
}
}
}
if (axis !== -1) {
this.activeAxis = axis;
this._setTraceLine(pos, axis);
vec3_copy(this.intersectPos, pos);
return true;
}
return false;
}
_testPoints(ray) {
const minDistance = this.infoScale * this.minPntDistance;
let t = nearPoint(ray, this.position, minDistance);
if (t !== null) {
this._setTraceLine(this.position);
this.activeMode = ManipulatorMode.Scale;
this.activeAxis = -2;
return true;
}
for (let i = 0; i < 3; i++) {
t = nearPoint(ray, this.axes[i].sclPos, minDistance);
if (t !== null) {
this._setTraceLine(this.axes[i].sclPos, i);
this.activeAxis = i;
this.activeMode = ManipulatorMode.Scale;
vec3_copy(this.intersectPos, this.axes[i].sclPos);
return true;
}
}
return false;
}
}
class ManipulatorMesh extends Group {
constructor(data) {
super();
__publicField(this, "axisColors", [8509299, 7186922, 16216426]);
__publicField(this, "axisLines", []);
__publicField(this, "axisArcs", []);
__publicField(this, "axisPoints", []);
__publicField(this, "axisTris", []);
__publicField(this, "grpCtrl", new Group());
__publicField(this, "meshTracePnt", null);
__publicField(this, "meshTraceLine", null);
__publicField(this, "colSelect", 16777215);
__publicField(this, "colOrigin", 16776960);
const PIH = Math.PI * 0.5;
const lineRadius = 0.03;
const arcRadius = data.arcRadius;
const arcThickness = 0.03;
const sclDistance = data.sclPointLen;
this.visible = false;
const matBasic = new MeshBasicMaterial({
depthTest: false,
depthWrite: false,
fog: false,
toneMapped: false,
transparent: true,
side: DoubleSide,
opacity: 1,
color: 16777215
});
const matLine = new LineBasicMaterial({
depthTest: false,
depthWrite: false,
fog: false,
toneMapped: false,
transparent: true,
color: 9474192
});
const geoTrace = new BufferGeometry();
geoTrace.setAttribute("position", new Float32BufferAttribute([0, 0, 0, 0, 100, 0], 3));
const geoTri = new BufferGeometry();
geoTri.setAttribute("position", new Float32BufferAttribute([0, 0, 0, data.midPointLen, 0, 0, 0, data.midPointLen, 0], 3));
const geoSphere = new SphereGeometry(0.1, 8, 8);
const geoArc = new TorusGeometry(arcRadius, arcThickness, 3, 10, PIH);
const geoAxisLine = new CylinderGeometry(lineRadius, lineRadius, data.axisLen, 3);
geoAxisLine.translate(0, data.axisLen * 0.5, 0);
const yAxisLine = new Mesh(geoAxisLine, matBasic.clone());
this.grpCtrl.add(yAxisLine);
const zAxisLine = new Mesh(geoAxisLine, matBasic.clone());
zAxisLine.rotation.x = PIH;
this.grpCtrl.add(zAxisLine);
const xAxisLine = new Mesh(geoAxisLine, matBasic.clone());
xAxisLine.rotation.z = -PIH;
this.grpCtrl.add(xAxisLine);
this.axisLines.push(xAxisLine, yAxisLine, zAxisLine);
const zAxisArc = new Mesh(geoArc, matBasic.clone());
this.grpCtrl.add(zAxisArc);
const xAxisArc = new Mesh(geoArc, matBasic.clone());
xAxisArc.rotation.y = -PIH;
this.grpCtrl.add(xAxisArc);
const yAxisArc = new Mesh(geoArc, matBasic.clone());
yAxisArc.rotation.x = PIH;
this.grpCtrl.add(yAxisArc);
this.axisArcs.push(xAxisArc, yAxisArc, zAxisArc);
const zAxisPnt = new Mesh(geoSphere, matBasic.clone());
zAxisPnt.position.z = sclDistance;
this.grpCtrl.add(zAxisPnt);
const xAxisPnt = new Mesh(geoSphere, matBasic.clone());
xAxisPnt.position.x = sclDistance;
this.grpCtrl.add(xAxisPnt);
const yAxisPnt = new Mesh(geoSphere, matBasic.clone());
yAxisPnt.position.y = sclDistance;
this.grpCtrl.add(yAxisPnt);
this.axisPoints.push(xAxisPnt, yAxisPnt, zAxisPnt);
const zAxisTri = new Mesh(geoTri, matBasic.clone());
this.grpCtrl.add(zAxisTri);
const yAxisTri = new Mesh(geoTri, matBasic.clone());
yAxisTri.rotation.x = PIH;
this.grpCtrl.add(yAxisTri);
const xAxisTri = new Mesh(geoTri, matBasic.clone());
xAxisTri.rotation.y = -PIH;
this.grpCtrl.add(xAxisTri);
this.axisTris.push(xAxisTri, yAxisTri, zAxisTri);
this.meshTraceLine = new Line(geoTrace, matLine);
this.meshTraceLine.visible = false;
this.add(this.meshTraceLine);
this.meshTracePnt = new Mesh(geoSphere, matBasic.clone());
this.meshTracePnt.visible = false;
this.add(this.meshTracePnt);
this.origin = new Mesh(geoSphere, matBasic.clone());
this.grpCtrl.add(this.origin);
this.add(this.grpCtrl);
}
showGizmo() {
this.grpCtrl.visible = true;
}
hideGizmo() {
this.grpCtrl.visible = false;
}
updateLook(data) {
let itm;
for (itm of this.axisArcs)
itm.visible = data.useRotate;
this.origin.visible = data.useScale;
for (itm of this.axisPoints)
itm.visible = data.useScale;
for (itm of this.axisTris)
itm.visible = data.useTranslate;
}
update(data) {
if (!data.hasUpdated && !data.hasHit)
return;
this.grpCtrl.scale.fromArray(data.scale);
this.grpCtrl.position.fromArray(data.position);
if (data.activeAxis === -2 && data.activeMode === ManipulatorMode.Scale) {
this.origin.material.color.setHex(this.colSelect);
} else {
this.origin.material.color.setHex(this.colOrigin);
}
for (let i = 0; i < 3; i++) {
this.axisLines[i].material.color.setHex(this.axisColors[i]);
this.axisArcs[i].material.color.setHex(this.axisColors[i]);
this.axisPoints[i].material.color.setHex(this.axisColors[i]);
this.axisTris[i].material.color.setHex(this.axisColors[i]);
if (i === data.activeAxis) {
switch (data.activeMode) {
case ManipulatorMode.Translate:
this.axisLines[i].material.color.setHex(this.colSelect);
break;
case ManipulatorMode.Rotate:
this.axisArcs[i].material.color.setHex(this.colSelect);
break;
case ManipulatorMode.Scale:
this.axisPoints[i].material.color.setHex(this.colSelect);
break;
}
}
if (i === data.activePlane) {
this.axisTris[i].material.color.setHex(16777215);
}
}
if (data.traceLine.isActive) {
const sclPnt = Math.abs(data.scale[2]);
this.meshTracePnt.visible = true;
this.meshTracePnt.scale.set(sclPnt, sclPnt, sclPnt);
this.meshTracePnt.position.fromArray(data.traceLine.origin);
this.meshTraceLine.visible = true;
this.meshTraceLine.geometry.attributes.position.needsUpdate = true;
const pntArray = this.meshTraceLine.geometry.attributes.position.array;
pntArray[0] = data.traceLine.a[0];
pntArray[1] = data.traceLine.a[1];
pntArray[2] = data.traceLine.a[2];
pntArray[3] = data.traceLine.b[0];
pntArray[4] = data.traceLine.b[1];
pntArray[5] = data.traceLine.b[2];
} else {
this.meshTraceLine.visible = false;
this.meshTracePnt.visible = false;
}
}
}
class Manipulator3D {
constructor(scene, camera, renderer = null, excludeMesh = false) {
__publicField(this, "mesh", null);
__publicField(this, "data", null);
__publicField(this, "attachedObject", null);
__publicField(this, "_camera", null);
__publicField(this, "_renderer", null);
__publicField(this, "_caster", new Raycaster());
__publicField(this, "_ray", new Ray());
__publicField(this, "_scaleStep", 0.1);
__publicField(this, "_rotateStep", 10 * Math.PI / 180);
__publicField(this, "_intersectOffset", [0, 0, 0]);
__publicField(this, "_initDragPosition", [0, 0, 0]);
__publicField(this, "_initDragQuaternion", [0, 0, 0, 1]);
__publicField(this, "_initDragScale", [1, 1, 1]);
__publicField(this, "_currentPosition", [0, 0, 0]);
__publicField(this, "_currentQuaternion", [0, 0, 0, 1]);
__publicField(this, "_currentScale", [1, 1, 1]);
__publicField(this, "_3jsVec", new Vector3());
__publicField(this, "_3jsQuat", new Quaternion());
__publicField(this, "_stopClick", false);
__publicField(this, "_onClick", (e) => {
if (this._stopClick) {
e.stopImmediatePropagation();
this._stopClick = false;
}
});
__publicField(this, "_onPointerMove", (e) => {
var _a;
this.update();
this._updateRaycaster(e);
if (!this.data.isDragging) {
this.data.onRayHover(this._ray);
} else {
this.data.onRayMove(this._ray);
(_a = this.renderer) == null ? void 0 : _a.domElement.setPointerCapture(e.pointerId);
e.preventDefault();
e.stopPropagation();
}
});
__publicField(this, "_onPointerDown", (e) => {
this._updateRaycaster(e);
if (this.data.onRayDown(this._ray)) {
e.preventDefault();
e.stopPropagation();
this._stopClick = true;
}
});
__publicField(this, "_onPointerUp", (e) => {
var _a;
if (this.data.isDragging) {
this.data.stopDrag();
(_a = this.renderer) == null ? void 0 : _a.domElement.releasePointerCapture(e.pointerId);
}
});
this.data = new ManipulatorData();
this._camera = camera;
if (!excludeMesh) {
this.mesh = new ManipulatorMesh(this.data);
scene.add(this.mesh);
}
this.data.onDragStart = () => this._onDragStart();
this.data.onDragEnd = () => this._onDragEnd();
this.data.onTranslate = (pos) => this._onTranslate(pos);
this.data.onRotate = (steps, iAxis) => this._onRotate(steps, iAxis);
this.data.onScale = (steps, iAxis) => this._onScale(steps, iAxis);
if (renderer)
this.setRenderer(renderer);
this.update(true);
}
setRenderer(renderer) {
renderer.domElement;
this._renderer = renderer;
this.addMouseListeners();
}
rayHover(rayCaster) {
this.update();
this._ray.fromCaster(rayCaster);
return this.data.onRayHover(this._ray);
}
rayMove(rayCaster) {
this.update();
this._ray.fromCaster(rayCaster);
return this.data.onRayMove(this._ray);
}
rayDown(rayCaster) {
this._ray.fromCaster(rayCaster);
return this.data.onRayDown(this._ray);
}
rayUp() {
if (this.data.isDragging)
this.data.stopDrag();
}
isDragging() {
return this.data.isDragging;
}
isActive() {
return this.data.isActive;
}
setActive(isOn) {
this.data.isActive = isOn;
if (this.mesh)
this.mesh.visible = isOn;
if (isOn)
this.updateStateFromCamera();
return this;
}
moveTo(p) {
this.data.setPosition(p);
this.data.calcAxesPosition();
vec3_copy(this._currentPosition, p);
this.update(true);
return this;
}
useTranslate(b) {
this.data.useTranslate = b;
this.mesh.updateLook(this.data);
return this;
}
useRotate(b) {
this.data.useRotate = b;
this.mesh.updateLook(this.data);
return this;
}
useScale(b) {
this.data.useScale = b;
this.mesh.updateLook(this.data);
return this;
}
setTraceLineStepDistance(n) {
this.data.traceStep = n;
return this;
}
setScaleStep(n) {
this._scaleStep = n;
return this;
}
setRotationStep(n) {
this._rotateStep = n * Math.PI / 180;
return this;
}
setScaleFactor(n) {
this.data.scaleFactor = n;
return this;
}
resetInitialValues(q = [0, 0, 0, 1], s = [1, 1, 1]) {
if (s)
vec3_copy(this._initDragScale, s);
if (q)
quat_copy(this._initDragQuaternion, q);
return this;
}
attach(obj) {
if (!this.data.isActive)
return;
this.attachedObject = obj;
this.moveTo(obj.position.toArray());
return this;
}
detach() {
this.attachedObject = null;
return this;
}
update(forceUpdate = false) {
if (!this.data.isActive && !forceUpdate)
return false;
this.updateStateFromCamera(forceUpdate);
if ((this.data.hasUpdated || this.data.hasHit) && this.mesh) {
this.mesh.update(this.data);
this.data.hasUpdated = false;
this.data.hasHit = false;
return true;
}
return false;
}
updateStateFromCamera(forceUpdate = false) {
this._camera.getWorldPosition(this._3jsVec);
this._camera.getWorldQuaternion(this._3jsQuat);
this.data.updateFromCamera(this._3jsVec.toArray(), this._3jsQuat.toArray(), forceUpdate);
}
isDragging() {
return this.data.isDragging;
}
_screenToNDCCoord(e) {
const c = this._renderer.domElement;
const rect = c.getBoundingClientRect();
const x = e.clientX - rect.x;
const y = e.clientY - rect.y;
return {
x: x / rect.width * 2 - 1,
y: -(y / rect.height) * 2 + 1
};
}
_updateRaycaster(e) {
this._caster.setFromCamera(this._screenToNDCCoord(e), this._camera);
this._ray.fromCaster(this._caster);
}
addMouseListeners() {
var _a;
const canvas = (_a = this._renderer) == null ? void 0 : _a.domElement;
if (!canvas)
return;
canvas.addEventListener("click", this._onClick);
canvas.addEventListener("pointermove", this._onPointerMove);
canvas.addEventListener("pointerdown", this._onPointerDown);
canvas.addEventListener("pointerup", this._onPointerUp);
}
removeMouseListeners() {
var _a;
const canvas = (_a = this._renderer) == null ? void 0 : _a.domElement;
if (!canvas)
return;
canvas.removeEventListener("click", this._onClick);
canvas.removeEventListener("pointermove", this._onPointerMove);
canvas.removeEventListener("pointerdown", this._onPointerDown);
canvas.removeEventListener("pointerup", this._onPointerUp);
}
_onDragStart() {
if (this.attachedObject) {
vec3_copy(this._initDragPosition, this.attachedObject.position.toArray());
vec3_copy(this._initDragScale, this.attachedObject.scale.toArray());
quat_copy(this._initDragQuaternion, this.attachedObject.quaternion.toArray());
} else {
vec3_copy(this._initDragPosition, this._currentPosition);
vec3_copy(this._initDragScale, this._currentScale);
quat_copy(this._initDragQuaternion, this._currentQuaternion);
}
vec3_sub(this._intersectOffset, this.data.position, this.data.intersectPos);
if (this.data.activeMode !== ManipulatorMode.Translate) {
this.mesh.hideGizmo();
}
this.data.hasUpdated = true;
this._emit("dragstart");
}
_onDragEnd() {
this.data.calcAxesPosition();
this.data.resetState();
this.mesh.showGizmo();
this._emit("dragend");
}
_onTranslate(pos) {
const offsetPos = vec3_add([0, 0, 0], pos, this._intersectOffset);
this.data.setPosition(offsetPos);
if (this.attachedObject)
this.attachedObject.position.fromArray(offsetPos);
vec3_copy(this._currentPosition, offsetPos);
this._emit("translate", offsetPos);
}
_onRotate(steps, iAxis) {
const sign = iAxis === 1 ? 1 : -Math.sign(this.data.scale[iAxis]);
const q = quat_setAxisAngle([0, 0, 0, 1], this.data.axes[iAxis].dir, this._rotateStep * steps * sign);
quat_mul(q, q, this._initDragQuaternion);
quat_normalize(q, q);
if (this.attachedObject)
this.attachedObject.quaternion.fromArray(q);
quat_copy(this._currentQuaternion, q);
this._emit("rotate", q);
}
_onScale(steps, iAxis) {
const scl = this._initDragScale.slice(0);
const inc = steps * this._scaleStep;
if (iAxis === null) {
scl[0] += inc;
scl[1] += inc;
scl[2] += inc;
} else {
scl[iAxis] += inc * Math.sign(this.data.scale[iAxis]);
}
if (this.attachedObject)
this.attachedObject.scale.fromArray(scl);
vec3_copy(this._currentScale, scl);
this._emit("scale", scl);
}
on(evtName, fn) {
this._renderer.domElement.addEventListener(evtName, fn);
return this;
}
off(evtName, fn) {
this._renderer.domElement.removeEventListener(evtName, fn);
return this;
}
_emit(evtName, detail = null) {
this._renderer.domElement.dispatchEvent(new CustomEvent(evtName, { detail, bubbles: true, cancelable: true, composed: false }));
}
}
export { Manipulator3D, ManipulatorData, ManipulatorMesh, ManipulatorMode };