UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

370 lines (369 loc) 12.5 kB
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); import { EventHandler } from "../../core/event-handler.js"; import { Color } from "../../core/math/color.js"; import { Mat4 } from "../../core/math/mat4.js"; import { Vec3 } from "../../core/math/vec3.js"; import { Vec4 } from "../../core/math/vec4.js"; import { COLOR_BLUE, COLOR_GREEN, COLOR_RED } from "./color.js"; const tmpV1 = new Vec3(); const tmpV2 = new Vec3(); const tmpV3 = new Vec3(); const tmpM1 = new Mat4(); const _ViewCube = class _ViewCube extends EventHandler { /** * @param {Vec4} [anchor] - The anchor. */ constructor(anchor) { super(); /** @private */ __publicField(this, "_size", 0); /** * @type {SVGSVGElement} * @private */ __publicField(this, "_svg"); /** * @type {Element} * @private */ __publicField(this, "_group"); /** @private */ __publicField(this, "_anchor", new Vec4(1, 1, 1, 1)); /** @private */ __publicField(this, "_colorX", COLOR_RED.clone()); /** @private */ __publicField(this, "_colorY", COLOR_GREEN.clone()); /** @private */ __publicField(this, "_colorZ", COLOR_BLUE.clone()); /** @private */ __publicField(this, "_colorNeg", new Color(0.3, 0.3, 0.3)); /** @private */ __publicField(this, "_radius", 10); /** @private */ __publicField(this, "_textSize", 10); /** @private */ __publicField(this, "_lineThickness", 2); /** @private */ __publicField(this, "_lineLength", 40); /** * @type {{ * nx: SVGAElement, * ny: SVGAElement, * nz: SVGAElement, * px: SVGAElement, * py: SVGAElement, * pz: SVGAElement, * xaxis: SVGLineElement, * yaxis: SVGLineElement, * zaxis: SVGLineElement * }} */ __publicField(this, "_shapes"); this.dom = document.createElement("div"); this.dom.id = "view-cube-container"; this.dom.style.cssText = [ "position: absolute", "margin: auto", "pointer-events: none" ].join(";"); document.body.appendChild(this.dom); this.anchor = anchor ?? this._anchor; this._svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); this._svg.id = "view-cube-svg"; this._group = document.createElementNS(this._svg.namespaceURI, "g"); this._svg.appendChild(this._group); this._resize(); const colX = this._colorX.toString(false); const colY = this._colorY.toString(false); const colZ = this._colorZ.toString(false); this._shapes = { nx: this._circle(colX), ny: this._circle(colY), nz: this._circle(colZ), px: this._circle(colX, true, "X"), py: this._circle(colY, true, "Y"), pz: this._circle(colZ, true, "Z"), xaxis: this._line(colX), yaxis: this._line(colY), zaxis: this._line(colZ) }; this._shapes.px.children[0].addEventListener("pointerdown", () => { this.fire(_ViewCube.EVENT_CAMERAALIGN, Vec3.RIGHT); }); this._shapes.py.children[0].addEventListener("pointerdown", () => { this.fire(_ViewCube.EVENT_CAMERAALIGN, Vec3.UP); }); this._shapes.pz.children[0].addEventListener("pointerdown", () => { this.fire(_ViewCube.EVENT_CAMERAALIGN, Vec3.BACK); }); this._shapes.nx.children[0].addEventListener("pointerdown", () => { this.fire(_ViewCube.EVENT_CAMERAALIGN, Vec3.LEFT); }); this._shapes.ny.children[0].addEventListener("pointerdown", () => { this.fire(_ViewCube.EVENT_CAMERAALIGN, Vec3.DOWN); }); this._shapes.nz.children[0].addEventListener("pointerdown", () => { this.fire(_ViewCube.EVENT_CAMERAALIGN, Vec3.FORWARD); }); this.dom.appendChild(this._svg); } set anchor(value) { this._anchor.copy(value); this.dom.style.top = this._anchor.x ? "0px" : "auto"; this.dom.style.right = this._anchor.y ? "0px" : "auto"; this.dom.style.bottom = this._anchor.z ? "0px" : "auto"; this.dom.style.left = this._anchor.w ? "0px" : "auto"; } get anchor() { return this._anchor; } /** * @type {Color} */ set colorX(value) { this._colorX.copy(value); this._shapes.px.children[0].setAttribute("fill", this._colorX.toString(false)); this._shapes.px.children[0].setAttribute("stroke", this._colorX.toString(false)); this._shapes.nx.children[0].setAttribute("stroke", this._colorX.toString(false)); this._shapes.xaxis.setAttribute("stroke", this._colorX.toString(false)); } get colorX() { return this._colorX; } /** * @type {Color} */ set colorY(value) { this._colorY.copy(value); this._shapes.py.children[0].setAttribute("fill", this._colorY.toString(false)); this._shapes.py.children[0].setAttribute("stroke", this._colorY.toString(false)); this._shapes.ny.children[0].setAttribute("stroke", this._colorY.toString(false)); this._shapes.yaxis.setAttribute("stroke", this._colorY.toString(false)); } get colorY() { return this._colorY; } /** * @type {Color} */ set colorZ(value) { this._colorZ.copy(value); this._shapes.pz.children[0].setAttribute("fill", this._colorZ.toString(false)); this._shapes.pz.children[0].setAttribute("stroke", this._colorZ.toString(false)); this._shapes.nz.children[0].setAttribute("stroke", this._colorZ.toString(false)); this._shapes.zaxis.setAttribute("stroke", this._colorZ.toString(false)); } get colorZ() { return this._colorZ; } /** * @type {Color} */ set colorNeg(value) { this._colorNeg.copy(value); this._shapes.px.children[0].setAttribute("fill", this._colorNeg.toString(false)); this._shapes.py.children[0].setAttribute("fill", this._colorNeg.toString(false)); this._shapes.pz.children[0].setAttribute("fill", this._colorNeg.toString(false)); } get colorNeg() { return this._colorNeg; } /** * @type {number} */ set radius(value) { this._radius = value; this._shapes.px.children[0].setAttribute("r", `${value}`); this._shapes.py.children[0].setAttribute("r", `${value}`); this._shapes.pz.children[0].setAttribute("r", `${value}`); this._shapes.nx.children[0].setAttribute("r", `${value}`); this._shapes.ny.children[0].setAttribute("r", `${value}`); this._shapes.nz.children[0].setAttribute("r", `${value}`); this._resize(); } get radius() { return this._radius; } /** * @type {number} */ set textSize(value) { this._textSize = value; this._shapes.px.children[1].setAttribute("font-size", `${value}`); this._shapes.py.children[1].setAttribute("font-size", `${value}`); this._shapes.pz.children[1].setAttribute("font-size", `${value}`); } get textSize() { return this._textSize; } /** * @type {number} */ set lineThickness(value) { this._lineThickness = value; this._shapes.xaxis.setAttribute("stroke-width", `${value}`); this._shapes.yaxis.setAttribute("stroke-width", `${value}`); this._shapes.zaxis.setAttribute("stroke-width", `${value}`); this._shapes.px.children[0].setAttribute("stroke-width", `${value}`); this._shapes.py.children[0].setAttribute("stroke-width", `${value}`); this._shapes.pz.children[0].setAttribute("stroke-width", `${value}`); this._shapes.nx.children[0].setAttribute("stroke-width", `${value}`); this._shapes.ny.children[0].setAttribute("stroke-width", `${value}`); this._shapes.nz.children[0].setAttribute("stroke-width", `${value}`); this._resize(); } get lineThickness() { return this._lineThickness; } /** * @type {number} */ set lineLength(value) { this._lineLength = value; this._resize(); } get lineLength() { return this._lineLength; } /** @private */ _resize() { this._size = 2 * (this.lineLength + this.radius + this.lineThickness); this.dom.style.width = `${this._size}px`; this.dom.style.height = `${this._size}px`; this._svg.setAttribute("width", `${this._size}`); this._svg.setAttribute("height", `${this._size}`); this._group.setAttribute("transform", `translate(${this._size * 0.5}, ${this._size * 0.5})`); } /** * @private * @param {SVGAElement} group - The group. * @param {number} x - The x. * @param {number} y - The y. */ _transform(group, x, y) { group.setAttribute("transform", `translate(${x * this._lineLength}, ${y * this._lineLength})`); } /** * @private * @param {SVGLineElement} line - The line. * @param {number} x - The x. * @param {number} y - The y. */ _x2y2(line, x, y) { line.setAttribute("x2", `${x * this._lineLength}`); line.setAttribute("y2", `${y * this._lineLength}`); } /** * @private * @param {string} color - The color. * @returns {SVGLineElement} - The line. */ _line(color) { const result = ( /** @type {SVGLineElement} */ document.createElementNS(this._svg.namespaceURI, "line") ); result.setAttribute("stroke", color); result.setAttribute("stroke-width", `${this._lineThickness}`); this._group.appendChild(result); return result; } /** * @private * @param {string} color - The color. * @param {boolean} [fill] - The fill. * @param {string} [text] - The text. * @returns {SVGAElement} - The circle. */ _circle(color, fill = false, text) { const group = ( /** @type {SVGAElement} */ document.createElementNS(this._svg.namespaceURI, "g") ); const circle = ( /** @type {SVGCircleElement} */ document.createElementNS(this._svg.namespaceURI, "circle") ); circle.setAttribute("fill", fill ? color : this._colorNeg.toString(false)); circle.setAttribute("stroke", color); circle.setAttribute("stroke-width", `${this._lineThickness}`); circle.setAttribute("r", `${this._radius}`); circle.setAttribute("cx", "0"); circle.setAttribute("cy", "0"); circle.setAttribute("pointer-events", "all"); group.appendChild(circle); if (text) { const t = ( /** @type {SVGTextElement} */ document.createElementNS(this._svg.namespaceURI, "text") ); t.setAttribute("font-size", `${this._textSize}`); t.setAttribute("font-family", "Arial"); t.setAttribute("font-weight", "bold"); t.setAttribute("text-anchor", "middle"); t.setAttribute("alignment-baseline", "central"); t.textContent = text; group.appendChild(t); } group.setAttribute("cursor", "pointer"); this._group.appendChild(group); return group; } /** * @param {Mat4} cameraMatrix - The camera matrix. */ update(cameraMatrix) { if (!this._size) { return; } tmpM1.invert(cameraMatrix); tmpM1.getX(tmpV1); tmpM1.getY(tmpV2); tmpM1.getZ(tmpV3); this._transform(this._shapes.px, tmpV1.x, -tmpV1.y); this._transform(this._shapes.nx, -tmpV1.x, tmpV1.y); this._transform(this._shapes.py, tmpV2.x, -tmpV2.y); this._transform(this._shapes.ny, -tmpV2.x, tmpV2.y); this._transform(this._shapes.pz, tmpV3.x, -tmpV3.y); this._transform(this._shapes.nz, -tmpV3.x, tmpV3.y); this._x2y2(this._shapes.xaxis, tmpV1.x, -tmpV1.y); this._x2y2(this._shapes.yaxis, tmpV2.x, -tmpV2.y); this._x2y2(this._shapes.zaxis, tmpV3.x, -tmpV3.y); const order = [ { n: ["xaxis", "px"], value: tmpV1.z }, { n: ["yaxis", "py"], value: tmpV2.z }, { n: ["zaxis", "pz"], value: tmpV3.z }, { n: ["nx"], value: -tmpV1.z }, { n: ["ny"], value: -tmpV2.z }, { n: ["nz"], value: -tmpV3.z } ].sort((a, b) => a.value - b.value); const fragment = document.createDocumentFragment(); order.forEach((o) => { o.n.forEach((n) => { fragment.appendChild(this._shapes[n]); }); }); this._group.appendChild(fragment); } destroy() { this.dom.remove(); this.off(); } }; /** * Fired when the user clicks on a face of the view cube. * * @event * @example * const viewCube = new ViewCube() * viewCube.on(ViewCube.EVENT_CAMERAALIGN, function (face) { * console.log('Camera aligned to face: ' + face); * }); */ __publicField(_ViewCube, "EVENT_CAMERAALIGN", "camera:align"); let ViewCube = _ViewCube; export { ViewCube };