UNPKG

@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
"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