playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
258 lines (257 loc) • 9.3 kB
JavaScript
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();
class ViewCube extends EventHandler {
static EVENT_CAMERAALIGN = "camera:align";
_size = 0;
_svg;
_group;
_anchor = new Vec4(1, 1, 1, 1);
_colorX = COLOR_RED.clone();
_colorY = COLOR_GREEN.clone();
_colorZ = COLOR_BLUE.clone();
_colorNeg = new Color(0.3, 0.3, 0.3);
_radius = 10;
_textSize = 10;
_lineThickness = 2;
_lineLength = 40;
_shapes;
constructor(anchor) {
super();
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
set lineLength(value) {
this._lineLength = value;
this._resize();
}
get lineLength() {
return this._lineLength;
}
_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})`);
}
_transform(group, x, y) {
group.setAttribute("transform", `translate(${x * this._lineLength}, ${y * this._lineLength})`);
}
_x2y2(line, x, y) {
line.setAttribute("x2", `${x * this._lineLength}`);
line.setAttribute("y2", `${y * this._lineLength}`);
}
_line(color) {
const result = document.createElementNS(this._svg.namespaceURI, "line");
result.setAttribute("stroke", color);
result.setAttribute("stroke-width", `${this._lineThickness}`);
this._group.appendChild(result);
return result;
}
_circle(color, fill = false, text) {
const group = document.createElementNS(this._svg.namespaceURI, "g");
const circle = 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 = 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;
}
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();
}
}
export {
ViewCube
};