@evoke-ui/zsort3d
Version:
TypeScript z-plane rendering engine with 3D depth simulation using Canvas 2D and mouse-based navigation
257 lines • 9.22 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Z3DRenderer = void 0;
const TouchManager_1 = require("./TouchManager");
class Z3DRenderer {
constructor(canvas, data, touchConfig) {
this.centerX = 0;
this.centerY = 0;
this.currentX = 0;
this.currentY = 0;
this.diffX = 0;
this.diffY = 0;
this.offsetY = 0;
this.offsetX = 0;
this.addX = 0;
this.addY = 0;
this.angleX = 0;
this.angleY = 0;
this.perspective = 150;
this.mouseDrag = false;
this.touchInteracting = false;
this.depths = [];
this.animationId = 0;
this.touchManager = null;
this.touchConfig = null;
this.onTapHandler = null;
this.canvas = canvas;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Unable to get 2D rendering context');
}
this.ctx = context;
this.canvasWidth = canvas.width;
this.canvasHeight = canvas.height;
this.centerX = this.canvasWidth / 2;
this.centerY = this.canvasHeight / 2;
this.data = data;
this.depths = new Array(data.length);
this.touchConfig = touchConfig || null;
if (typeof window !== 'undefined') {
this.init();
}
}
resize(w, h) {
this.canvasWidth = w;
this.canvasHeight = h;
this.centerX = w / 2;
this.centerY = h / 2;
this.canvas.width = w;
this.canvas.height = h;
}
get perspectiveDistance() {
return this.perspective;
}
set perspectiveDistance(n) {
this.perspective = n;
}
get mouseX() {
return this.currentX;
}
set mouseX(x) {
this.currentX = x;
}
get mouseY() {
return this.currentY;
}
set mouseY(y) {
this.currentY = y;
}
init() {
this.setupObjects();
this.setupEventHandlers();
this.setupTouchSupport();
this.startRenderLoop();
}
setupObjects() {
for (let i = 0; i < this.data.length; i++) {
this.data[i].xnum = (Math.random() - 0.5) * 160;
this.data[i].ynum = (Math.random() - 0.5) * 160;
this.data[i].znum = (Math.random() - 0.7) * 120;
this.depths[i] = i;
}
}
setupEventHandlers() {
this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this));
this.canvas.addEventListener('mousedown', this.onMouseDown.bind(this));
this.canvas.addEventListener('mouseup', this.onMouseUp.bind(this));
this.canvas.addEventListener('mouseleave', this.onMouseUp.bind(this));
}
onMouseMove(e) {
if (this.mouseDrag) {
const rect = this.canvas.getBoundingClientRect();
this.currentX = e.clientX - rect.left;
this.currentY = e.clientY - rect.top;
}
}
onMouseDown(e) {
this.mouseDrag = true;
e.preventDefault();
}
onMouseUp(_e) {
this.mouseDrag = false;
}
setupTouchSupport() {
if (!this.touchConfig || typeof window === 'undefined')
return;
this.touchManager = new TouchManager_1.TouchManager(this.canvas, this.touchConfig);
this.touchManager.on('pan', (data) => {
if (!this.touchInteracting && data.originalEvent?.type === 'panstart') {
this.touchInteracting = true;
if (data.center) {
this.currentX = data.center.x;
this.currentY = data.center.y;
}
}
else if (data.originalEvent?.type === 'panmove' && data.center) {
this.currentX = data.center.x;
this.currentY = data.center.y;
}
else if (data.originalEvent?.type === 'panend') {
this.touchInteracting = false;
}
});
this.touchManager.on('pinch', (data) => {
if (data.scale) {
const scaleChange = data.scale - 1;
const perspectiveChange = scaleChange * -50;
const newPerspective = Math.max(50, Math.min(500, this.perspective + perspectiveChange));
this.perspective = newPerspective;
}
});
this.touchManager.on('tap', (data) => {
let handled = false;
const sortedIndices = Array.from({ length: this.data.length }, (_, i) => i);
sortedIndices.sort((a, b) => this.data[b].scaleX - this.data[a].scaleX);
for (const index of sortedIndices) {
const obj = this.data[index];
if (obj && typeof obj.handleTouchEvent === 'function') {
handled = obj.handleTouchEvent(data);
if (handled)
break;
}
}
if (!handled && this.onTapHandler) {
this.onTapHandler(data);
}
});
this.touchManager.on('rotate', (data) => {
if (data.rotation) {
const rotationRadians = (data.rotation * Math.PI) / 180;
this.angleY += rotationRadians * 2;
}
});
}
startRenderLoop() {
const render = () => {
this.calculateDelta();
this.render3DStage();
this.animationId = requestAnimationFrame(render);
};
render();
}
calculateDelta() {
this.diffX = this.currentX - this.offsetX;
this.diffY = this.currentY - this.offsetY;
this.offsetX += this.diffX / 20;
this.offsetY += this.diffY / 20;
if (this.offsetX > 0) {
this.addX = Math.floor(this.offsetX / 360) * -360;
}
else {
this.addX = (Math.floor(this.offsetX / 360) - 1) * -360;
}
if (this.offsetY > 0) {
this.addY = Math.floor(this.offsetY / 360) * -360;
}
else {
this.addY = (Math.floor(this.offsetY / 360) - 1) * -360;
}
this.angleY = this.offsetX + this.addX;
this.angleX = this.offsetY + this.addY;
}
render3DStage() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
for (let i = 0; i < this.data.length; i++) {
const obj = this.data[i];
const angX = this.angleX * (Math.PI / 180);
const angY = this.angleY * (Math.PI / 180);
obj.xpos = obj.znum * Math.sin(angY) + obj.xnum * Math.cos(angY);
obj.zpos = obj.znum * Math.cos(angY) - obj.xnum * Math.sin(angY);
obj.ypos = obj.ynum * Math.cos(angX) - obj.zpos * Math.sin(angX);
obj.zpos = obj.ynum * Math.sin(angX) + obj.zpos * Math.cos(angX);
this.depths[i] = 1 / ((obj.zpos / this.perspective) + 1);
obj.x = obj.xpos * this.depths[i] + this.centerX;
obj.y = obj.ypos * this.depths[i] + this.centerY;
obj.scaleX = obj.scaleY = Math.max(0.2, this.depths[i] * 0.4);
obj.alpha = Math.max(0.3, Math.min(1.0, this.depths[i] * 0.8 + 0.2));
}
const sortedIndices = Array.from({ length: this.data.length }, (_, i) => i);
sortedIndices.sort((a, b) => this.data[a].scaleX - this.data[b].scaleX);
for (const index of sortedIndices) {
const obj = this.data[index];
this.ctx.save();
this.ctx.globalAlpha = Math.max(0, Math.min(1, obj.alpha));
obj.render(this.ctx);
this.ctx.restore();
}
}
async enableTouch(config) {
this.touchConfig = config || {};
if (this.touchManager) {
this.touchManager.destroy();
}
this.setupTouchSupport();
}
disableTouch() {
if (this.touchManager) {
this.touchManager.destroy();
this.touchManager = null;
}
this.touchConfig = null;
this.touchInteracting = false;
}
async updateTouchConfig(config) {
if (this.touchManager) {
await this.touchManager.updateConfig(config);
}
this.touchConfig = { ...this.touchConfig, ...config };
}
isTouchEnabled() {
return this.touchManager !== null && this.touchManager.isEnabled();
}
getTouchConfig() {
return this.touchManager ? this.touchManager.getConfig() : null;
}
onTap(handler) {
this.onTapHandler = handler;
}
offTap() {
this.onTapHandler = null;
}
destroy() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
if (this.touchManager) {
this.touchManager.destroy();
this.touchManager = null;
}
this.canvas.removeEventListener('mousemove', this.onMouseMove.bind(this));
this.canvas.removeEventListener('mousedown', this.onMouseDown.bind(this));
this.canvas.removeEventListener('mouseup', this.onMouseUp.bind(this));
this.canvas.removeEventListener('mouseleave', this.onMouseUp.bind(this));
}
}
exports.Z3DRenderer = Z3DRenderer;
//# sourceMappingURL=Z3DRenderer.js.map