@tolokoban/tgd
Version:
ToloGameDev library for WebGL2
189 lines • 15.1 kB
JavaScript
/* eslint-disable unicorn/prevent-abbreviations */
import { TgdCameraPerspective } from "./../../camera/index.js";
import { TgdContext } from "./../../context/index.js";
import { TgdControllerCameraOrbit } from "./../../controller/index.js";
import { TgdEvent } from "./../../event/index.js";
import { TgdQuat, TgdMat3, TgdVec3 } from "./../../math/index.js";
import { TgdPainterClear, TgdPainterDepth, TgdPainterLogic } from "./../../painter/index.js";
import { TipsPainter } from "./painter/tips.js";
/**
* The Gizmo displays the orientation of an attached camera.
* You can click one of the axis to force the camera to face it.
*/
export class TgdCanvasGizmo {
constructor(options = {}) {
this.options = options;
/**
* The user clicked a tip, so we dispatch the target orientation.
*/
this.eventTipClick = new TgdEvent();
this._canvas = null;
this.context = null;
this.contextExternal = null;
this.cameraInternal = new TgdCameraPerspective({
fovy: Math.PI / 3,
near: 0.01,
far: 10,
transfo: { distance: 2.7 },
});
this.orbiter = null;
this.tipsPainter = null;
this.handleExternalPaint = () => {
this.context?.paint();
};
this.handleInternalToExternal = (internalCamera) => {
const { contextExternal } = this;
if (contextExternal?.camera) {
contextExternal.camera.transfo.orientation = internalCamera.transfo.orientation;
contextExternal.paint();
}
};
this.handleTap = (event) => {
const camera = this.context?.camera;
if (!camera)
return;
const { origin, direction } = camera.castRay(event.x, event.y);
const maxDistance = 1;
let bestDistance = maxDistance;
let bestTip = TIPS[0];
for (const tip of TIPS) {
const distance = tip.distanceToLineSquared(origin, direction);
if (distance < bestDistance) {
bestDistance = distance;
bestTip = tip;
}
}
if (bestDistance < maxDistance) {
const axisX = new TgdVec3();
const axisY = new TgdVec3();
const axisZ = bestTip;
axisX.from(this.findAxisX());
if (axisX.isClose(axisZ)) {
axisY.from(this.findAxisY());
axisX.from(axisY).cross(axisZ);
}
else {
axisY.from(axisZ).cross(axisX);
}
const quat = new TgdQuat().fromAxes(axisX, axisY, axisZ);
if (quat.isEqual(camera.transfo.orientation)) {
quat.rotateAroundY(Math.PI);
}
this.eventTipClick.dispatch({
from: camera.transfo.orientation,
to: quat,
});
}
};
if (options.canvas)
this.canvas = options.canvas;
}
/**
* Attach the camera we want to track and control.
*/
attachContext(context) {
this.detach();
this.contextExternal = context;
this.attach();
}
detach() {
if (!this.contextExternal)
return;
this.contextExternal.eventPaint.removeListener(this.handleExternalPaint);
this.contextExternal = null;
}
attach() {
this.contextExternal?.eventPaint.addListener(this.handleExternalPaint);
this.context?.paint();
}
get canvas() {
return this._canvas;
}
set canvas(canvas) {
if (canvas === this._canvas)
return;
this._canvas = canvas;
if (this.context) {
this.context.inputs.pointer.eventTap.removeListener(this.handleTap);
this.context.delete();
this.context = null;
const { orbiter } = this;
if (orbiter) {
orbiter.detach();
orbiter.eventChange.removeListener(this.handleInternalToExternal);
}
}
this.tipsPainter?.delete();
if (!canvas)
return;
const context = new TgdContext(canvas, {
alpha: true,
depth: true,
antialias: true,
name: "GizmoCanvas",
...this.options,
});
context.inputs.pointer.eventTap.addListener(this.handleTap);
this.context = context;
context.camera = this.cameraInternal;
this.orbiter = new TgdControllerCameraOrbit(context, {
speedPanning: 0,
speedZoom: 0,
});
this.orbiter.eventChange.addListener(this.handleInternalToExternal);
const painter = new TipsPainter(context);
this.tipsPainter = painter;
context.add(new TgdPainterLogic(() => {
const srcTransfo = this.contextExternal?.camera.transfo;
if (!srcTransfo)
return;
const dstTransfo = this.context?.camera.transfo;
if (!dstTransfo)
return;
dstTransfo.orientation = srcTransfo.orientation;
}), new TgdPainterClear(context, {
color: [0, 0, 0, 0],
depth: 1,
}), new TgdPainterDepth(context, { enabled: true }), painter);
context.paint();
}
findAxisX() {
let bestScore = 0;
let bestTip = TIPS[0];
const vec = new TgdVec3();
const mat = new TgdMat3();
mat.fromQuat(this.cameraInternal.transfo.orientation).transpose();
for (const tip of TIPS) {
vec.from(tip).applyMatrix(mat);
if (vec.x > bestScore) {
bestScore = vec.x;
bestTip = tip;
}
}
return bestTip;
}
findAxisY() {
let bestScore = 0;
let bestTip = TIPS[0];
const vec = new TgdVec3();
const mat = new TgdMat3();
mat.fromQuat(this.cameraInternal.transfo.orientation).transpose();
for (const tip of TIPS) {
vec.from(tip).applyMatrix(mat);
if (vec.y > bestScore) {
bestScore = vec.y;
bestTip = tip;
}
}
return bestTip;
}
}
const TIPS = [
new TgdVec3(1, 0, 0),
new TgdVec3(0, 1, 0),
new TgdVec3(0, 0, 1),
new TgdVec3(-1, 0, 0),
new TgdVec3(0, -1, 0),
new TgdVec3(0, 0, -1),
];
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2l6bW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY2FudmFzL2dpem1vL2dpem1vLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGtEQUFrRDtBQUNsRCxPQUFPLEVBQWEsb0JBQW9CLEVBQUUsTUFBTSxhQUFhLENBQUE7QUFDN0QsT0FBTyxFQUFFLFVBQVUsRUFBcUIsTUFBTSxjQUFjLENBQUE7QUFDNUQsT0FBTyxFQUFFLHdCQUF3QixFQUFFLE1BQU0saUJBQWlCLENBQUE7QUFDMUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUNyQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFDckQsT0FBTyxFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBRWhGLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQU01Qzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sY0FBYztJQXFCdkIsWUFBNkIsVUFBaUMsRUFBRTtRQUFuQyxZQUFPLEdBQVAsT0FBTyxDQUE0QjtRQXBCaEU7O1dBRUc7UUFDSSxrQkFBYSxHQUFHLElBQUksUUFBUSxFQUcvQixDQUFBO1FBRUksWUFBTyxHQUE2QixJQUFJLENBQUE7UUFDeEMsWUFBTyxHQUFzQixJQUFJLENBQUE7UUFDakMsb0JBQWUsR0FBc0IsSUFBSSxDQUFBO1FBQ2hDLG1CQUFjLEdBQUcsSUFBSSxvQkFBb0IsQ0FBQztZQUN2RCxJQUFJLEVBQUUsSUFBSSxDQUFDLEVBQUUsR0FBRyxDQUFDO1lBQ2pCLElBQUksRUFBRSxJQUFJO1lBQ1YsR0FBRyxFQUFFLEVBQUU7WUFDUCxPQUFPLEVBQUUsRUFBRSxRQUFRLEVBQUUsR0FBRyxFQUFFO1NBQzdCLENBQUMsQ0FBQTtRQUNNLFlBQU8sR0FBb0MsSUFBSSxDQUFBO1FBQy9DLGdCQUFXLEdBQXVCLElB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