UNPKG

matrix-engine-wgpu

Version:

Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas

286 lines (253 loc) 10.5 kB
import {computeWorldVertsAndAABB, touchCoordinate, rayIntersectsAABB, rayIntersectsSphere, getRayFromMouse2, getRayFromMouse, addRaycastsListener} from "../../../src/engine/raycast.js"; import {mat4, vec4} from "wgpu-matrix"; import {byId, LOG_MATRIX, mb} from "../../../src/engine/utils.js"; import {followPath} from "./nav-mesh.js"; export class Controller { ignoreList = ['ground', 'mouseTarget_Circle']; selected = []; nav = null; // ONLY LOCAL heroe_bodies = null; distanceForAction = 36; constructor(core) { this.core = core; this.canvas = this.core.canvas; this.dragStart = null; this.dragEnd = null; this.selecting = false; this.canvas.addEventListener('mousedown', (e) => { if(e.button === 2) { // right m this.selecting = true; this.dragStart = {x: e.clientX, y: e.clientY}; this.dragEnd = {x: e.clientX, y: e.clientY}; } // else if(e.button === 0) { } }); this.canvas.addEventListener('mousemove', (e) => { if(this.selecting) { this.dragEnd = {x: e.clientX, y: e.clientY}; } }); this.canvas.addEventListener('mouseup', (e) => { if(this.selecting) { this.selecting = false; this.selectCharactersInRect(this.dragStart, this.dragEnd); this.dragStart = this.dragEnd = null; setTimeout(() => { if(this.ctx) this.ctx.clearRect(0, 0, this.overlay.width, this.overlay.height); }, 100); } }); addRaycastsListener(undefined, 'click'); this.canvas.addEventListener("ray.hit.event", (e) => { // console.log('ray.hit.event detected', e); const {hitObject, hitPoint, button, eventName} = e.detail; if(e.detail.hitObject.name == 'ground') { dispatchEvent(new CustomEvent(`onMouseTarget`, { detail: { type: 'normal', x: hitPoint[0], y: hitPoint[1], z: hitPoint[2] } })); this.core.localHero.heroFocusAttackOn = null; // return; } else if(this.core.enemies && this.core.enemies.isEnemy(e.detail.hitObject.name)) { dispatchEvent(new CustomEvent(`onMouseTarget`, { detail: { type: 'attach', x: e.detail.hitObject.position.x,// hitPoint[0], blocked by colision observer y: e.detail.hitObject.position.y, z: e.detail.hitObject.position.z } })); } else { // for now if (app.net.virtualEmiter != null) { console.log("only emiter - navigate friendly_creeps creep from controller :", e.detail.hitObject.name); dispatchEvent(new CustomEvent('navigate-friendly_creeps', {detail: 'test'})) } // must be friendly objs return; } if(button == 0 && e.detail.hitObject.name != 'ground' && e.detail.hitObject.name !== this.heroe_bodies[0].name) { if(this.heroe_bodies.length == 2) { if(e.detail.hitObject.name == this.heroe_bodies[1].name) { console.log("Hit object SELF SLICKED :", e.detail.hitObject.name); return; } } const LH = this.core.localHero.heroe_bodies[0]; console.log("Hit object VS LH DISTANCE : ", this.distance3D(LH.position, e.detail.hitObject.position)) // after all check is it eneimy this.core.localHero.heroFocusAttackOn = e.detail.hitObject; let testDistance = this.distance3D(LH.position, e.detail.hitObject.position); // 37 LIMIT FOR ATTACH if(testDistance < this.distanceForAction) { console.log("this.core.localHero.setAttack [e.detail.hitObject]") this.core.localHero.setAttack(e.detail.hitObject); return; } } // Only react to LEFT CLICK if(button !== 0 || this.heroe_bodies === null || !this.selected.includes(this.heroe_bodies[0])) { console.log(" no local here ") // not hero but maybe other creaps . based on selected.... return; } // Define start (hero position) and end (clicked point) const hero = this.heroe_bodies[0]; dispatchEvent(new CustomEvent('set-walk')); const start = [hero.position.x, hero.position.y, hero.position.z]; const end = [hitPoint[0], hitPoint[1], hitPoint[2]]; // app.net.send({ // heroName: app.localHero.name, // sceneName: hero.name, // followPath: {start: start, end: end}, // }) const path = this.nav.findPath(start, end); if(!path || path.length === 0) {console.warn('No valid path found.'); return;} // no need if position = position of root ??? test last bug track for(var x = 0;x < this.heroe_bodies.length;x++) { followPath(this.heroe_bodies[x], path, this.core); } // followPath(this.heroe_bodies[0], path, this.core); }); document.body.addEventListener("contextmenu", (e) => { e.preventDefault(); }); this.canvas.addEventListener("contextmenu", (e) => { e.preventDefault(); }); this.activateVisualRect(); let hiddenAt = null; if(location.hostname.indexOf('localhost') == -1) { console.log('Security stuff activated'); console.log = function() {}; // Security stuff if(window.innerHeight < window.outerHeight) { let test = window.outerHeight - window.innerHeight; // 87 person comp case -> addressbar ~~~ if(test > 100) { console.log('BAN', test); location.assign('https://maximumroulette.com'); } } if(window.innerWidth < window.outerWidth) { let testW = window.outerWidth - window.innerWidth; if(testW > 100) { console.log('BAN', testW); location.assign('https://maximumroulette.com'); } } window.addEventListener('keydown', (e) => { if(e.code == "F12") { e.preventDefault(); mb.error(` You are interest in Forest Of Hollow Blood. See <a href='https://github.com/zlatnapirala'>Github Source</a> You can download for free project and test it into localhost. `) console.log(`%c[keydown opened] ${e}`, LOG_MATRIX) return false; } }); let onVisibilityChange = () => { if(document.visibilityState === "visible") { if(hiddenAt !== null) { const now = Date.now(); const hiddenDuration = (now - hiddenAt) / 1000; if(parseFloat(hiddenDuration.toFixed(2)) > 1) { console.log(`🟢⚠️ Tab was hidden for ${hiddenDuration.toFixed(2)} sec.`); document.title = document.title.replace('🟢', '🟡') } hiddenAt = null; // reset } else { console.log("🟢 Tab is visible — first activation."); } } else { hiddenAt = Date.now(); } } document.addEventListener("visibilitychange", onVisibilityChange); } } projectToScreen(worldPos, viewMatrix, projectionMatrix, canvas) { // Convert world position to clip space const world = [worldPos[0], worldPos[1], worldPos[2], 1.0]; // Multiply in correct order: clip = projection * view * world const viewProj = mat4.multiply(projectionMatrix, viewMatrix); const clip = vec4.transformMat4(world, viewProj); // Perform perspective divide const ndcX = clip[0] / clip[3]; const ndcY = clip[1] / clip[3]; // Convert NDC (-1..1) to screen pixels const screenX = (ndcX * 0.5 + 0.5) * canvas.width; const screenY = (1 - (ndcY * 0.5 + 0.5)) * canvas.height; return {x: screenX, y: screenY}; } selectCharactersInRect(start, end) { const xMin = Math.min(start.x, end.x); const xMax = Math.max(start.x, end.x); const yMin = Math.min(start.y, end.y); const yMax = Math.max(start.y, end.y); // const camera = app.cameras.WASD; const camera = app.cameras.RPG; for(const object of app.mainRenderBundle) { if(!object.position) continue; const screen = this.projectToScreen( [object.position.x, object.position.y, object.position.z, 1.0], camera.view, camera.projectionMatrix, this.canvas ); if(screen.x >= xMin && screen.x <= xMax && screen.y >= yMin && screen.y <= yMax) { if(this.ignoreList.some(str => object.name.includes(str))) continue; if(this.selected.includes(object)) continue; object.setSelectedEffect(true); this.selected.push(object); byId('hud-menu').dispatchEvent(new CustomEvent("onSelectCharacter", {detail: object.name})) } else { if(this.selected.indexOf(object) !== -1) { this.selected.splice(this.selected.indexOf(object), 1) // byId('hud-menu').dispatchEvent(new CustomEvent("onSelectCharacter", {detail: object.name} )) } object.setSelectedEffect(false); } } console.log("Selected:", this.selected.map(o => o.name)); } activateVisualRect() { const overlay = document.createElement("canvas"); overlay.width = this.canvas.width; overlay.height = this.canvas.height; overlay.style.position = "absolute"; overlay.style.left = this.canvas.offsetLeft + "px"; overlay.style.top = this.canvas.offsetTop + "px"; this.canvas.parentNode.appendChild(overlay); this.ctx = overlay.getContext("2d"); overlay.style.pointerEvents = "none"; this.overlay = overlay; this.canvas.addEventListener("mousemove", (e) => { if(this.selecting) { this.dragEnd = {x: e.clientX, y: e.clientY}; this.ctx.clearRect(0, 0, overlay.width, overlay.height); this.ctx.strokeStyle = "rgba(0,255,0,0.8)"; this.ctx.lineWidth = 2.5; this.ctx.strokeRect( this.dragStart.x, this.dragStart.y, this.dragEnd.x - this.dragStart.x, this.dragEnd.y - this.dragStart.y ); } }); } distance3D(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; const dz = a.z - b.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); } }