From e8e9778ede777072f9f40204efbb212978f354dc Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 2 Jul 2024 23:15:37 +0200 Subject: [PATCH 1/7] initial refactor --- app/components/CanvasLayer.tsx | 60 +++- app/components/FirstPersonControls.tsx | 354 +---------------------- app/components/TeleportControls.tsx | 2 +- app/components/UserInterfaceRenderer.tsx | 321 ++++++++++++++++++++ 4 files changed, 382 insertions(+), 355 deletions(-) create mode 100644 app/components/UserInterfaceRenderer.tsx diff --git a/app/components/CanvasLayer.tsx b/app/components/CanvasLayer.tsx index af50d25..a42c0a7 100644 --- a/app/components/CanvasLayer.tsx +++ b/app/components/CanvasLayer.tsx @@ -5,8 +5,8 @@ import { StatsGl } from '@react-three/drei'; import { FirstPersonControls } from './FirstPersonControls.tsx' -import TeleportControls from './TeleportControls.tsx'; - +import { TeleportControls } from './TeleportControls.tsx'; +import { UserInterfaceRenderer } from './UserInterfaceRenderer.tsx'; import { Splat } from './Splat.tsx'; import { Leva, useControls } from 'leva'; import { useMemo } from 'react' @@ -55,6 +55,59 @@ const CanvasLayer = () => { } }; + // temporary location for rooms + const roomConfig = [ + { + minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, + slopes: [ + { angle: Math.PI / 3 , position: { x: 0, y: 0, z: 0 }, width: 10 }, + { angle: Math.PI / 3, position: { x: 10, y: 0, z: 0 }, width: 10 } + ], + objects: [ + { minX: 10, maxX: 15, minY: 0, maxY: 15, minZ: 10, maxZ: 15 } + ], + elements: { + arrows: [], + panes: [ + { position: { x: -40, y: -5, z: -40}, verticalRotation: Math.PI / 6, horizontalRotation: Math.PI/4, sizefactor: 10, content: "/images/testbild.png"}, + ], + windowarcs: [ + { position: { x: 40, y: 0, z: -40}, horizontalRotation: Math.PI / 4, arcRadius: 10, arcHeight: 20, content: "/images/testbild.png"} + ] + } + }, + { + minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, + slopes: [ + { angle: Math.PI / 2, position: { x: 10, y: 5, z: 5 }, width: 5, length: 10 }, + { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10, length: 50 }, + { angle: Math.PI / 3, position: { x: 10, y: 5, z: 5 }, width: 5, length: 50 } + + ], + objects: [], + elements: { + arrows: [], + panes: [], + windowarcs: [] + } + }, + { + minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, + slopes: [], + objects: [], + elements: { + arrows: [ + { position: { x: 0, y: -10, z: 0 }, graphName: "P0" }, + { position: { x: 0, y: -10, z: -20 }, graphName: "P1" }, + { position: { x: 0, y: -10, z: -40 }, graphName: "P2" }, + { position: { x: 15, y: -10, z: -20 }, graphName: "P3" }, + { position: { x: 35, y: -10, z: -40 }, graphName: "P4" }, + { position: { x: 0, y: -10, z: -55 }, graphName: "P5" }], + panes: [], + windowarcs: [] + } + }, + ]; return (
@@ -68,7 +121,8 @@ const CanvasLayer = () => { - + + {isPointerLocked && } {splatExists && diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index 3ad54c4..5086f04 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -1,9 +1,8 @@ import React, { useRef, useEffect } from 'react'; import { useThree, useFrame } from '@react-three/fiber'; import * as THREE from 'three'; -import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; -export const FirstPersonControls = (speed) => { +export const FirstPersonControls = ({ speed, rooms }) => { const { camera, scene } = useThree(); const moveForward = useRef(false); const moveBackward = useRef(false); @@ -19,221 +18,6 @@ export const FirstPersonControls = (speed) => { // Desired height above the ground const cameraHeight = 5; - - // temporary location for arrow graphs, TODO: move this outside of the controller - type edge = { - arrow1: THREE.Object3D; - arrow2: THREE.Object3D; - weight: number; - } - - type arrowgraph = { - arrows: THREE.Object3D[]; - edges: edge[]; - } - - class ArrowGraph { - private graph: arrowgraph; - private arrowsShortestPaths: (THREE.Object3D|undefined)[][]; - private scene: THREE.Scene; - - constructor(scene: THREE.Scene) { - this.graph = { arrows: [], edges: [] }; - this.arrowsShortestPaths = []; - this.scene = scene; - } - - addArrow(arrow: THREE.Object3D): void{ - this.graph.arrows.push(arrow); - } - - addEdge(arrow1name: string, arrow2name: string): void { - const arrow1 = this.scene.getObjectByName(arrow1name); - const arrow2 = this.scene.getObjectByName(arrow2name); - if(arrow1 && arrow2){ - const weight: number = Math.sqrt( - Math.pow(arrow1.position.x - arrow2.position.x, 2) + - Math.pow(arrow1.position.y - arrow2.position.y, 2) - ); - const newEdge: edge = { arrow1, arrow2, weight }; - this.graph.edges.push(newEdge); - } - } - - public findShortestPaths(): void { - const arrowCount = this.graph.arrows.length; - this.arrowsShortestPaths = Array.from({ length: arrowCount }, () => - Array(arrowCount).fill(undefined) - ); - - this.graph.arrows.forEach((startArrow, startIndex) => { - const distance = new Map(); - const previous = new Map(); - const remaining = new Set(); - - this.graph.arrows.forEach(arrow => { - distance.set(arrow, Infinity); - previous.set(arrow, []); - remaining.add(arrow); - }); - - distance.set(startArrow, 0); - - while (remaining.size > 0) { - let currentArrow: THREE.Object3D | undefined = undefined; - let minDistance = Infinity; - - remaining.forEach(arrow => { - for (let [key, value] of distance) { - if (key.name == arrow.name) { - if (value < minDistance) { - minDistance = value; - currentArrow = arrow; - } - } - } - }); - - if (currentArrow === undefined) break; - remaining.delete(currentArrow); - - this.graph.edges.forEach(edge => { - let neighbor: THREE.Object3D | undefined = undefined; - if (edge.arrow1.name == currentArrow.name) { - neighbor = edge.arrow2; - } else if (edge.arrow2.name == currentArrow.name) { - neighbor = edge.arrow1; - } - - if (neighbor) { - remaining.forEach(arrow => { - if (arrow.name == neighbor.name) { - let distanceCurrentArrow = Infinity; - for (let [key, value] of distance) { - if (key.name == currentArrow.name){ - distanceCurrentArrow = value; - } - } - for (let [key, value] of distance) { - if (key.name == neighbor.name) { - const alt = distanceCurrentArrow + edge.weight; - if (alt < value) { - distance.delete(key); - distance.set(neighbor, alt); - previous.delete(key); - let currentArrowPrevious; - for (let [key, value] of previous) { - if (key.name == currentArrow.name){ - currentArrowPrevious = value; - } - } - previous.set(neighbor, [...currentArrowPrevious]); - previous.get(neighbor).push(currentArrow); - } - } - } - } - }) - } - }); - } - - this.graph.arrows.forEach((endArrow, endIndex) => { - if (startArrow !== endArrow) { - for (let [nextArrow, value] of previous){ - if (nextArrow.name == endArrow.name){ - value.push(endArrow); - nextArrow = value[1]; - this.arrowsShortestPaths[startIndex][endIndex] = nextArrow; - } - } - } - }); - }); - } - - updateArrowRotations(destinationArrowName: string): void { - const destinationArrow = this.scene.getObjectByName(destinationArrowName); - if (destinationArrow !== undefined) { - const destinationArrowIndex = this.graph.arrows.findIndex(obj => obj.name == destinationArrow.name); - if (this.arrowsShortestPaths.length === 0) { - this.findShortestPaths(); - } - for (let arrowIndex = 0; arrowIndex < this.graph.arrows.length; arrowIndex++) { - let arrow = this.graph.arrows[arrowIndex]; - let arrowDestination = this.arrowsShortestPaths[arrowIndex][destinationArrowIndex]; - if (arrow != undefined) { - if (arrowDestination != undefined) { - arrow.visible = true; - let vectorToNextArrow = [arrow.position.x - arrowDestination.position.x, - arrow.position.z - arrowDestination.position.z]; - arrow.rotation.y = -Math.atan2(vectorToNextArrow[1], vectorToNextArrow[0]); - } else { - arrow.visible = false; - } - } - } - } - } - } - - const graph = new ArrowGraph(scene); - - - // temporary location for rooms, TODO: move this outside of the controller - const rooms = [ - { - minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, - slopes: [ - { angle: Math.PI / 3 , position: { x: 0, y: 0, z: 0 }, width: 10 }, - { angle: Math.PI / 3, position: { x: 10, y: 0, z: 0 }, width: 10 } - ], - objects: [ - { minX: 10, maxX: 15, minY: 0, maxY: 15, minZ: 10, maxZ: 15 } - ], - elements: { - arrows: [], - panes: [ - { position: { x: -40, y: -5, z: -40}, verticalRotation: Math.PI / 6, horizontalRotation: Math.PI/4, sizefactor: 10, content: "/images/testbild.png"}, - ], - windowarcs: [ - { position: { x: 40, y: 0, z: -40}, horizontalRotation: Math.PI / 4, arcRadius: 10, arcHeight: 20, content: "/images/testbild.png"} - ] - } - }, - { - minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, - slopes: [ - { angle: Math.PI / 2, position: { x: 10, y: 5, z: 5 }, width: 5, length: 10 }, - { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10, length: 50 }, - { angle: Math.PI / 3, position: { x: 10, y: 5, z: 5 }, width: 5, length: 50 } - - ], - objects: [], - elements: { - arrows: [], - panes: [], - windowarcs: [] - } - }, - { - minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, - slopes: [], - objects: [], - elements: { - arrows: [ - { position: { x: 0, y: -10, z: 0 }, graphName: "P0" }, - { position: { x: 0, y: -10, z: -20 }, graphName: "P1" }, - { position: { x: 0, y: -10, z: -40 }, graphName: "P2" }, - { position: { x: 15, y: -10, z: -20 }, graphName: "P3" }, - { position: { x: 35, y: -10, z: -40 }, graphName: "P4" }, - { position: { x: 0, y: -10, z: -55 }, graphName: "P5" }], - panes: [], - windowarcs: [] - } - }, - ]; - useEffect(() => { const onKeyDown = (event) => { switch (event.code) { @@ -261,7 +45,9 @@ export const FirstPersonControls = (speed) => { case 'ShiftLeft': case 'ShiftRight': moveDown.current = true; + graph.updateArrowRotations("P3"); break; + } }; @@ -298,140 +84,6 @@ export const FirstPersonControls = (speed) => { window.addEventListener('keydown', onKeyDown); window.addEventListener('keyup', onKeyUp); - const textureLoader = new THREE.TextureLoader(); - const gltfloader = new GLTFLoader(); - - // Add transparent boxes to visualize rooms and slopes - rooms.forEach(room => { - const roomGeometry = new THREE.BoxGeometry( - room.maxX - room.minX, - room.maxY - room.minY, - room.maxZ - room.minZ - ); - const roomMaterial = new THREE.MeshBasicMaterial({ - color: 0x00ff00, - transparent: true, - opacity: 0.25, - wireframe: true - }); - const roomBox = new THREE.Mesh(roomGeometry, roomMaterial); - roomBox.position.set( - (room.minX + room.maxX) / 2, - (room.minY + room.maxY) / 2, - (room.minZ + room.maxZ) / 2 - ); - scene.add(roomBox); - - room.objects.forEach(object => { - - const objectGeometry = new THREE.BoxGeometry( - object.maxX - object.minX, - object.maxY - object.minY, - object.maxZ - object.minZ - ); - const objectMaterial = new THREE.MeshBasicMaterial({ - color: 0xff0000, - transparent: true, - opacity: 0.25, - wireframe: true - }); - const objectBox = new THREE.Mesh(objectGeometry, objectMaterial); - objectBox.position.set( - (room.minX + object.minX - room.maxX + object.maxX) / 2, - (room.minY + object.minY - room.maxY + object.maxY) / 2, - (room.minZ + object.minZ - room.maxZ + object.maxZ) / 2 - ); - objectBox.name = `object-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; - scene.add(objectBox); - }); - - // Add slopes to the scene - room.slopes.forEach((slope, index) => { - const slopeGeometry = new THREE.PlaneGeometry(slope.width, slope.length); - const slopeMaterial = new THREE.MeshBasicMaterial({ - color: 0xcccccc, - side: THREE.DoubleSide, - wireframe: true - }); - const slopeMesh = new THREE.Mesh(slopeGeometry, slopeMaterial); - slopeMesh.rotation.x = -slope.angle; - slopeMesh.position.set( - room.minX + (room.maxX - room.minX) / 2 + slope.position.x, - (room.minY + room.maxY) / 2 + slope.position.y, - room.minZ + (room.maxZ - room.minZ) / 2 + slope.position.z - ); - slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; - scene.add(slopeMesh); - }); - - // Add (interactable) elements to the scene - room.elements.panes.forEach((pane, index) => { - textureLoader.load(pane.content, (texture) => { - const aspectRatio = texture.image.width / texture.image.height; - const paneHeight = pane.sizefactor; // or any desired height - const paneWidth = paneHeight * aspectRatio; - - const paneGeometry = new THREE.PlaneGeometry(paneWidth, paneHeight); - const paneMaterial = new THREE.MeshBasicMaterial({ map: texture }); - const paneMesh = new THREE.Mesh(paneGeometry, paneMaterial); - paneMesh.rotation.set(-pane.verticalRotation, pane.horizontalRotation, 0, 'YXZ'); - paneMesh.position.set( - room.minX + (room.maxX - room.minX) / 2 + pane.position.x, - (room.minY + room.maxY) / 2 + pane.position.y, - room.minZ + (room.maxZ - room.minZ) / 2 + pane.position.z - ); - paneMesh.name = `pane-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; // Naming panes to easily find them later - scene.add(paneMesh); - }); - }); - - room.elements.windowarcs.forEach((arc, index) => { - const texture = textureLoader.load(arc.content); - const arcGeometry = new THREE.CylinderGeometry(arc.arcRadius, arc.arcRadius, arc.arcHeight, 16, 1, true, 0, Math.PI); - const arcMaterial = new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide}); - const arcMesh = new THREE.Mesh(arcGeometry, arcMaterial); - arcMesh.rotation.y = arc.horizontalRotation; - arcMesh.position.set( - room.minX + (room.maxX - room.minX) / 2 + arc.position.x, - (room.minY + room.maxY) / 2 + arc.position.y, - room.minZ + (room.maxZ - room.minZ) / 2 + arc.position.z - ); - arcMesh.name = `arc-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; // Naming panes to easily find them later - scene.add(arcMesh); - }); - - - room.elements.arrows.forEach((arrow, index) => { - const mesh = gltfloader.load("/meshes/arrow.glb", function (gltf){ - const arrowMesh = gltf.scene; - const whiteTexture = new THREE.MeshBasicMaterial({ color: 0xffffff }); - arrowMesh.rotation.y = 0; - arrowMesh.position.set( - room.minX + (room.maxX - room.minX) / 2 + arrow.position.x, - (room.minY + room.maxY) / 2 + arrow.position.y, - room.minZ + (room.maxZ - room.minZ) / 2 + arrow.position.z - ); - const modelscale = 2; - arrowMesh.scale.set(modelscale, modelscale, modelscale); - arrowMesh.visible = false; - arrowMesh.name = arrow.graphName; // Naming panes to easily find them later - graph.addArrow(arrowMesh); - scene.add(arrowMesh); - }, undefined, function (error) { - console.error('An error happened', error); - }); - }) - }); - - //temporary location of Edges, TODO: move them to a better place when refractoring - graph.addEdge("P0", "P1"); - graph.addEdge("P1", "P2"); - graph.addEdge("P1", "P3"); - graph.addEdge("P2", "P3"); - graph.addEdge("P2", "P5"); - graph.addEdge("P3", "P4"); - graph.addEdge("P4", "P5"); - return () => { window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); diff --git a/app/components/TeleportControls.tsx b/app/components/TeleportControls.tsx index 5cdd5dd..cc2caa2 100644 --- a/app/components/TeleportControls.tsx +++ b/app/components/TeleportControls.tsx @@ -2,7 +2,7 @@ import React, { useImperativeHandle, useState } from 'react'; import * as THREE from 'three'; import { useThree, useFrame } from '@react-three/fiber'; -const TeleportControls = React.forwardRef((props, ref) => { +export const TeleportControls = React.forwardRef((props, ref) => { const { camera } = useThree(); const [teleportData, setTeleportData] = useState<{ position: THREE.Vector3; diff --git a/app/components/UserInterfaceRenderer.tsx b/app/components/UserInterfaceRenderer.tsx new file mode 100644 index 0000000..46f7f44 --- /dev/null +++ b/app/components/UserInterfaceRenderer.tsx @@ -0,0 +1,321 @@ +import React, { useRef, useEffect } from 'react'; +import { useThree, useFrame } from '@react-three/fiber'; +import * as THREE from 'three'; +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; + +export const UserInterfaceRenderer = ({rooms}) => { + const { camera, scene } = useThree(); + + type edge = { + arrow1: THREE.Object3D; + arrow2: THREE.Object3D; + weight: number; + } + + type arrowgraph = { + arrows: THREE.Object3D[]; + edges: edge[]; + } + + class ArrowGraph { + private graph: arrowgraph; + private arrowsShortestPaths: (THREE.Object3D|undefined)[][]; + private scene: THREE.Scene; + + constructor(scene: THREE.Scene) { + this.graph = { arrows: [], edges: [] }; + this.arrowsShortestPaths = []; + this.scene = scene; + } + + addArrow(arrow: THREE.Object3D): void{ + this.graph.arrows.push(arrow); + } + + addEdge(arrow1name: string, arrow2name: string): void { + const arrow1 = this.scene.getObjectByName(arrow1name); + const arrow2 = this.scene.getObjectByName(arrow2name); + if(arrow1 && arrow2){ + const weight: number = Math.sqrt( + Math.pow(arrow1.position.x - arrow2.position.x, 2) + + Math.pow(arrow1.position.y - arrow2.position.y, 2) + ); + const newEdge: edge = { arrow1, arrow2, weight }; + this.graph.edges.push(newEdge); + } + } + + public findShortestPaths(): void { + const arrowCount = this.graph.arrows.length; + this.arrowsShortestPaths = Array.from({ length: arrowCount }, () => + Array(arrowCount).fill(undefined) + ); + + this.graph.arrows.forEach((startArrow, startIndex) => { + const distance = new Map(); + const previous = new Map(); + const remaining = new Set(); + + this.graph.arrows.forEach(arrow => { + distance.set(arrow, Infinity); + previous.set(arrow, []); + remaining.add(arrow); + }); + + distance.set(startArrow, 0); + + while (remaining.size > 0) { + let currentArrow: THREE.Object3D | undefined = undefined; + let minDistance = Infinity; + + remaining.forEach(arrow => { + for (let [key, value] of distance) { + if (key.name == arrow.name) { + if (value < minDistance) { + minDistance = value; + currentArrow = arrow; + } + } + } + }); + + if (currentArrow === undefined) break; + remaining.delete(currentArrow); + + this.graph.edges.forEach(edge => { + let neighbor: THREE.Object3D | undefined = undefined; + if (edge.arrow1.name == currentArrow.name) { + neighbor = edge.arrow2; + } else if (edge.arrow2.name == currentArrow.name) { + neighbor = edge.arrow1; + } + + if (neighbor) { + remaining.forEach(arrow => { + if (arrow.name == neighbor.name) { + let distanceCurrentArrow = Infinity; + for (let [key, value] of distance) { + if (key.name == currentArrow.name){ + distanceCurrentArrow = value; + } + } + for (let [key, value] of distance) { + if (key.name == neighbor.name) { + const alt = distanceCurrentArrow + edge.weight; + if (alt < value) { + distance.delete(key); + distance.set(neighbor, alt); + previous.delete(key); + let currentArrowPrevious; + for (let [key, value] of previous) { + if (key.name == currentArrow.name){ + currentArrowPrevious = value; + } + } + previous.set(neighbor, [...currentArrowPrevious]); + previous.get(neighbor).push(currentArrow); + } + } + } + } + }) + } + }); + } + + this.graph.arrows.forEach((endArrow, endIndex) => { + if (startArrow !== endArrow) { + for (let [nextArrow, value] of previous){ + if (nextArrow.name == endArrow.name){ + value.push(endArrow); + nextArrow = value[1]; + this.arrowsShortestPaths[startIndex][endIndex] = nextArrow; + } + } + } + }); + }); + } + + updateArrowRotations(destinationArrowName: string): void { + const destinationArrow = this.scene.getObjectByName(destinationArrowName); + if (destinationArrow !== undefined) { + const destinationArrowIndex = this.graph.arrows.findIndex(obj => obj.name == destinationArrow.name); + if (this.arrowsShortestPaths.length === 0) { + this.findShortestPaths(); + } + for (let arrowIndex = 0; arrowIndex < this.graph.arrows.length; arrowIndex++) { + let arrow = this.graph.arrows[arrowIndex]; + let arrowDestination = this.arrowsShortestPaths[arrowIndex][destinationArrowIndex]; + if (arrow != undefined) { + if (arrowDestination != undefined) { + arrow.visible = true; + let vectorToNextArrow = [arrow.position.x - arrowDestination.position.x, + arrow.position.z - arrowDestination.position.z]; + arrow.rotation.y = -Math.atan2(vectorToNextArrow[1], vectorToNextArrow[0]); + } else { + arrow.visible = false; + } + } + } + } + } + } + + const graph = new ArrowGraph(scene); + + + // for debugging purposes + useEffect(() => { + const onKeyDown = (event) => { + switch (event.code) { + case 'KeyP': + console.log("p") + break; + } + }; + + + window.addEventListener('keydown', onKeyDown); + + const textureLoader = new THREE.TextureLoader(); + const gltfloader = new GLTFLoader(); + + // Add transparent boxes to visualize rooms and slopes + rooms.forEach(room => { + const roomGeometry = new THREE.BoxGeometry( + room.maxX - room.minX, + room.maxY - room.minY, + room.maxZ - room.minZ + ); + const roomMaterial = new THREE.MeshBasicMaterial({ + color: 0x00ff00, + transparent: true, + opacity: 0.25, + wireframe: true + }); + const roomBox = new THREE.Mesh(roomGeometry, roomMaterial); + roomBox.position.set( + (room.minX + room.maxX) / 2, + (room.minY + room.maxY) / 2, + (room.minZ + room.maxZ) / 2 + ); + scene.add(roomBox); + + room.objects.forEach(object => { + + const objectGeometry = new THREE.BoxGeometry( + object.maxX - object.minX, + object.maxY - object.minY, + object.maxZ - object.minZ + ); + const objectMaterial = new THREE.MeshBasicMaterial({ + color: 0xff0000, + transparent: true, + opacity: 0.25, + wireframe: true + }); + const objectBox = new THREE.Mesh(objectGeometry, objectMaterial); + objectBox.position.set( + (room.minX + object.minX - room.maxX + object.maxX) / 2, + (room.minY + object.minY - room.maxY + object.maxY) / 2, + (room.minZ + object.minZ - room.maxZ + object.maxZ) / 2 + ); + objectBox.name = `object-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; + scene.add(objectBox); + }); + + // Add slopes to the scene + room.slopes.forEach((slope, index) => { + const slopeGeometry = new THREE.PlaneGeometry(slope.width, slope.length); + const slopeMaterial = new THREE.MeshBasicMaterial({ + color: 0xcccccc, + side: THREE.DoubleSide, + wireframe: true + }); + const slopeMesh = new THREE.Mesh(slopeGeometry, slopeMaterial); + slopeMesh.rotation.x = -slope.angle; + slopeMesh.position.set( + room.minX + (room.maxX - room.minX) / 2 + slope.position.x, + (room.minY + room.maxY) / 2 + slope.position.y, + room.minZ + (room.maxZ - room.minZ) / 2 + slope.position.z + ); + slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; + scene.add(slopeMesh); + }); + + // Add (interactable) elements to the scene + room.elements.panes.forEach((pane, index) => { + textureLoader.load(pane.content, (texture) => { + const aspectRatio = texture.image.width / texture.image.height; + const paneHeight = pane.sizefactor; // or any desired height + const paneWidth = paneHeight * aspectRatio; + + const paneGeometry = new THREE.PlaneGeometry(paneWidth, paneHeight); + const paneMaterial = new THREE.MeshBasicMaterial({ map: texture }); + const paneMesh = new THREE.Mesh(paneGeometry, paneMaterial); + paneMesh.rotation.set(-pane.verticalRotation, pane.horizontalRotation, 0, 'YXZ'); + paneMesh.position.set( + room.minX + (room.maxX - room.minX) / 2 + pane.position.x, + (room.minY + room.maxY) / 2 + pane.position.y, + room.minZ + (room.maxZ - room.minZ) / 2 + pane.position.z + ); + paneMesh.name = `pane-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; // Naming panes to easily find them later + scene.add(paneMesh); + }); + }); + + room.elements.windowarcs.forEach((arc, index) => { + const texture = textureLoader.load(arc.content); + const arcGeometry = new THREE.CylinderGeometry(arc.arcRadius, arc.arcRadius, arc.arcHeight, 16, 1, true, 0, Math.PI); + const arcMaterial = new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide}); + const arcMesh = new THREE.Mesh(arcGeometry, arcMaterial); + arcMesh.rotation.y = arc.horizontalRotation; + arcMesh.position.set( + room.minX + (room.maxX - room.minX) / 2 + arc.position.x, + (room.minY + room.maxY) / 2 + arc.position.y, + room.minZ + (room.maxZ - room.minZ) / 2 + arc.position.z + ); + arcMesh.name = `arc-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; // Naming panes to easily find them later + scene.add(arcMesh); + }); + + + room.elements.arrows.forEach((arrow, index) => { + const mesh = gltfloader.load("/meshes/arrow.glb", function (gltf){ + const arrowMesh = gltf.scene; + const whiteTexture = new THREE.MeshBasicMaterial({ color: 0xffffff }); + arrowMesh.rotation.y = 0; + arrowMesh.position.set( + room.minX + (room.maxX - room.minX) / 2 + arrow.position.x, + (room.minY + room.maxY) / 2 + arrow.position.y, + room.minZ + (room.maxZ - room.minZ) / 2 + arrow.position.z + ); + const modelscale = 2; + arrowMesh.scale.set(modelscale, modelscale, modelscale); + arrowMesh.visible = false; + arrowMesh.name = arrow.graphName; // Naming panes to easily find them later + graph.addArrow(arrowMesh); + scene.add(arrowMesh); + }, undefined, function (error) { + console.error('An error happened', error); + }); + }) + }); + + //temporary location of Edges, TODO: move them to a better place when refractoring + graph.addEdge("P0", "P1"); + graph.addEdge("P1", "P2"); + graph.addEdge("P1", "P3"); + graph.addEdge("P2", "P3"); + graph.addEdge("P2", "P5"); + graph.addEdge("P3", "P4"); + graph.addEdge("P4", "P5"); + + return () => { + window.removeEventListener('keydown', onKeyDown); + }; + }, [rooms, scene]); + + return null; +}; From 496fe4e27dcbb1000e7a111ba0b73b60be2a8a37 Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 2 Jul 2024 23:23:09 +0200 Subject: [PATCH 2/7] adding debug option --- app/components/CanvasLayer.tsx | 3 +- app/components/UserInterfaceRenderer.tsx | 137 +++++++++++++---------- 2 files changed, 77 insertions(+), 63 deletions(-) diff --git a/app/components/CanvasLayer.tsx b/app/components/CanvasLayer.tsx index a42c0a7..ee67275 100644 --- a/app/components/CanvasLayer.tsx +++ b/app/components/CanvasLayer.tsx @@ -41,6 +41,7 @@ const CanvasLayer = () => { const options = useMemo(() => { return { speed: { value: 100, min: 1, max: 500, step: 10 }, + debug: false, } }, []) @@ -122,7 +123,7 @@ const CanvasLayer = () => { - + {isPointerLocked && } {splatExists && diff --git a/app/components/UserInterfaceRenderer.tsx b/app/components/UserInterfaceRenderer.tsx index 46f7f44..5e55c09 100644 --- a/app/components/UserInterfaceRenderer.tsx +++ b/app/components/UserInterfaceRenderer.tsx @@ -3,7 +3,7 @@ import { useThree, useFrame } from '@react-three/fiber'; import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; -export const UserInterfaceRenderer = ({rooms}) => { +export const UserInterfaceRenderer = ({rooms, debug}) => { const { camera, scene } = useThree(); type edge = { @@ -165,6 +165,77 @@ export const UserInterfaceRenderer = ({rooms}) => { const graph = new ArrowGraph(scene); + function drawRoomGeometry(): void { + if(!debug) { + return + } + + rooms.forEach(room => { + const roomGeometry = new THREE.BoxGeometry( + room.maxX - room.minX, + room.maxY - room.minY, + room.maxZ - room.minZ + ); + const roomMaterial = new THREE.MeshBasicMaterial({ + color: 0x00ff00, + transparent: true, + opacity: 0.25, + wireframe: true + }); + const roomBox = new THREE.Mesh(roomGeometry, roomMaterial); + roomBox.position.set( + (room.minX + room.maxX) / 2, + (room.minY + room.maxY) / 2, + (room.minZ + room.maxZ) / 2 + ); + scene.add(roomBox); + + room.objects.forEach(object => { + + const objectGeometry = new THREE.BoxGeometry( + object.maxX - object.minX, + object.maxY - object.minY, + object.maxZ - object.minZ + ); + const objectMaterial = new THREE.MeshBasicMaterial({ + color: 0xff0000, + transparent: true, + opacity: 0.25, + wireframe: true + }); + const objectBox = new THREE.Mesh(objectGeometry, objectMaterial); + objectBox.position.set( + (room.minX + object.minX - room.maxX + object.maxX) / 2, + (room.minY + object.minY - room.maxY + object.maxY) / 2, + (room.minZ + object.minZ - room.maxZ + object.maxZ) / 2 + ); + objectBox.name = `object-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; + scene.add(objectBox); + }); + + // Add slopes to the scene + room.slopes.forEach((slope, index) => { + const slopeGeometry = new THREE.PlaneGeometry(slope.width, slope.length); + const slopeMaterial = new THREE.MeshBasicMaterial({ + color: 0xcccccc, + side: THREE.DoubleSide, + wireframe: true + }); + const slopeMesh = new THREE.Mesh(slopeGeometry, slopeMaterial); + slopeMesh.rotation.x = -slope.angle; + slopeMesh.position.set( + room.minX + (room.maxX - room.minX) / 2 + slope.position.x, + (room.minY + room.maxY) / 2 + slope.position.y, + room.minZ + (room.maxZ - room.minZ) / 2 + slope.position.z + ); + slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; + scene.add(slopeMesh); + }); + + }) + } + + // for debugging purposes useEffect(() => { const onKeyDown = (event) => { @@ -182,68 +253,10 @@ export const UserInterfaceRenderer = ({rooms}) => { const gltfloader = new GLTFLoader(); // Add transparent boxes to visualize rooms and slopes - rooms.forEach(room => { - const roomGeometry = new THREE.BoxGeometry( - room.maxX - room.minX, - room.maxY - room.minY, - room.maxZ - room.minZ - ); - const roomMaterial = new THREE.MeshBasicMaterial({ - color: 0x00ff00, - transparent: true, - opacity: 0.25, - wireframe: true - }); - const roomBox = new THREE.Mesh(roomGeometry, roomMaterial); - roomBox.position.set( - (room.minX + room.maxX) / 2, - (room.minY + room.maxY) / 2, - (room.minZ + room.maxZ) / 2 - ); - scene.add(roomBox); - - room.objects.forEach(object => { - - const objectGeometry = new THREE.BoxGeometry( - object.maxX - object.minX, - object.maxY - object.minY, - object.maxZ - object.minZ - ); - const objectMaterial = new THREE.MeshBasicMaterial({ - color: 0xff0000, - transparent: true, - opacity: 0.25, - wireframe: true - }); - const objectBox = new THREE.Mesh(objectGeometry, objectMaterial); - objectBox.position.set( - (room.minX + object.minX - room.maxX + object.maxX) / 2, - (room.minY + object.minY - room.maxY + object.maxY) / 2, - (room.minZ + object.minZ - room.maxZ + object.maxZ) / 2 - ); - objectBox.name = `object-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; - scene.add(objectBox); - }); - - // Add slopes to the scene - room.slopes.forEach((slope, index) => { - const slopeGeometry = new THREE.PlaneGeometry(slope.width, slope.length); - const slopeMaterial = new THREE.MeshBasicMaterial({ - color: 0xcccccc, - side: THREE.DoubleSide, - wireframe: true - }); - const slopeMesh = new THREE.Mesh(slopeGeometry, slopeMaterial); - slopeMesh.rotation.x = -slope.angle; - slopeMesh.position.set( - room.minX + (room.maxX - room.minX) / 2 + slope.position.x, - (room.minY + room.maxY) / 2 + slope.position.y, - room.minZ + (room.maxZ - room.minZ) / 2 + slope.position.z - ); - slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; - scene.add(slopeMesh); - }); + drawRoomGeometry(); + // draw the rest of the UI (arrows, planes etc.) + rooms.forEach(room => { // Add (interactable) elements to the scene room.elements.panes.forEach((pane, index) => { textureLoader.load(pane.content, (texture) => { From fb46915e3e27c4c5d934aceace85ce15ca404a3f Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 2 Jul 2024 23:44:28 +0200 Subject: [PATCH 3/7] remove debug line --- app/components/FirstPersonControls.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index 5086f04..4983c13 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -45,7 +45,6 @@ export const FirstPersonControls = ({ speed, rooms }) => { case 'ShiftLeft': case 'ShiftRight': moveDown.current = true; - graph.updateArrowRotations("P3"); break; } From b77b349bdb7e1201897f58b6aff6550dfdc0ad14 Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 2 Jul 2024 23:49:07 +0200 Subject: [PATCH 4/7] implement passing room between canvas and first person controller --- app/components/CanvasLayer.tsx | 10 +++++++++- app/components/FirstPersonControls.tsx | 5 +++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/components/CanvasLayer.tsx b/app/components/CanvasLayer.tsx index ee67275..6b5eb00 100644 --- a/app/components/CanvasLayer.tsx +++ b/app/components/CanvasLayer.tsx @@ -20,6 +20,14 @@ const CanvasLayer = () => { const teleportControlsRef = useRef(); + const [currentRoom, setCurrentRoom] = useState(''); + + const updateCurrentRoom = (newRoom) => { + setCurrentRoom(newRoom); + } + + + useEffect(() => { const checkFileExists = async () => { try { @@ -122,7 +130,7 @@ const CanvasLayer = () => { - + {isPointerLocked && } diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index 4983c13..7fcd9b9 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react'; import { useThree, useFrame } from '@react-three/fiber'; import * as THREE from 'three'; -export const FirstPersonControls = ({ speed, rooms }) => { +export const FirstPersonControls = ({ speed, rooms, updateCurrentRoom }) => { const { camera, scene } = useThree(); const moveForward = useRef(false); const moveBackward = useRef(false); @@ -46,7 +46,6 @@ export const FirstPersonControls = ({ speed, rooms }) => { case 'ShiftRight': moveDown.current = true; break; - } }; @@ -122,6 +121,8 @@ export const FirstPersonControls = ({ speed, rooms }) => { camera.position.z >= room.minZ && camera.position.z <= room.maxZ ); + updateCurrentRoom(currentRoom); + // find the room the user might be going into const nextRoom = rooms.find(room => newPosition.x >= room.minX && newPosition.x <= room.maxX && From f67790df020ec85294fc7b4eb6125f0bed35f457 Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 3 Jul 2024 11:56:03 +0200 Subject: [PATCH 5/7] remove debug --- app/components/Splat.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/Splat.tsx b/app/components/Splat.tsx index d76db52..9cc298b 100644 --- a/app/components/Splat.tsx +++ b/app/components/Splat.tsx @@ -708,7 +708,6 @@ export function Splat({ loader.chunkSize = chunkSize loader.splats = splats }) as SharedState - console.log(shared); // Listen to worker results, apply them to the target mesh React.useLayoutEffect(() => shared.connect(ref.current), [src]) // Update the worker From de2bc0b2891463783bab4bb5655a783d3364679e Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 3 Jul 2024 11:56:30 +0200 Subject: [PATCH 6/7] add functionality to get list of currently to be rendered rooms --- app/components/CanvasLayer.tsx | 82 +++++++++++--------------- app/components/FirstPersonControls.tsx | 9 ++- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/app/components/CanvasLayer.tsx b/app/components/CanvasLayer.tsx index 6b5eb00..3ae5ac2 100644 --- a/app/components/CanvasLayer.tsx +++ b/app/components/CanvasLayer.tsx @@ -21,13 +21,28 @@ const CanvasLayer = () => { const teleportControlsRef = useRef(); const [currentRoom, setCurrentRoom] = useState(''); + const [currentSplats, setCurrentSplats] = useState([]); + const [currentKey, setCurrentKey] = useState(''); const updateCurrentRoom = (newRoom) => { + if(currentRoom == newRoom || newRoom == '' || newRoom == undefined) { + return; + } setCurrentRoom(newRoom); + // get adjacent splats from currentRoom + if(newRoom.adjacent.length == 0) { + setCurrentSplats(newRoom.splat); + } else { + setCurrentSplats([...newRoom.adjacent.map(adjacentName => { + const adjacentRoom = roomConfig.find(room => room.name === adjacentName); + return adjacentRoom ? adjacentRoom.splat : null; + }).filter(splat => splat !== null), newRoom.splat]); + } + console.log(newRoom); + console.log(currentSplats); + setCurrentKey(`${newRoom.name}${newRoom.adjacent.join('')}`) } - - useEffect(() => { const checkFileExists = async () => { try { @@ -45,7 +60,6 @@ const CanvasLayer = () => { checkFileExists(); }, []); - const options = useMemo(() => { return { speed: { value: 100, min: 1, max: 500, step: 10 }, @@ -57,24 +71,22 @@ const CanvasLayer = () => { const handleTeleport = () => { if (teleportControlsRef.current) { - // Test cooordinates + // Test coordinates // (posX, posY, posZ, lookAtX, lookAtY, lookAtZ) teleportControlsRef.current.teleport(0, 0, 0, 0, 2, 1); - // here you teleprot to pos 0,0,0 and look at pos 0,2,1 + // here you teleport to pos 0,0,0 and look at pos 0,2,1 } }; - // temporary location for rooms - const roomConfig = [ + // temporary location for rooms + const roomConfig = [ { - minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, - slopes: [ - { angle: Math.PI / 3 , position: { x: 0, y: 0, z: 0 }, width: 10 }, - { angle: Math.PI / 3, position: { x: 10, y: 0, z: 0 }, width: 10 } - ], - objects: [ - { minX: 10, maxX: 15, minY: 0, maxY: 15, minZ: 10, maxZ: 15 } - ], + splat: "nuke_2.splat", + name: "raum2", + adjacent: ["raum1"], + minX: -2, maxX: 1, minY: 0, maxY: 5, minZ: 5, maxZ: 5.65, + slopes: [], + objects: [], elements: { arrows: [], panes: [ @@ -86,38 +98,19 @@ const CanvasLayer = () => { } }, { - minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, - slopes: [ - { angle: Math.PI / 2, position: { x: 10, y: 5, z: 5 }, width: 5, length: 10 }, - { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10, length: 50 }, - { angle: Math.PI / 3, position: { x: 10, y: 5, z: 5 }, width: 5, length: 50 } - - ], - objects: [], - elements: { - arrows: [], - panes: [], - windowarcs: [] - } - }, - { - minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, + splat: "nuke_1.splat", + name: "raum1", + adjacent: [], + minX: -2, maxX: 1, minY: 0, maxY: 5, minZ: 2, maxZ: 5, slopes: [], objects: [], elements: { - arrows: [ - { position: { x: 0, y: -10, z: 0 }, graphName: "P0" }, - { position: { x: 0, y: -10, z: -20 }, graphName: "P1" }, - { position: { x: 0, y: -10, z: -40 }, graphName: "P2" }, - { position: { x: 15, y: -10, z: -20 }, graphName: "P3" }, - { position: { x: 35, y: -10, z: -40 }, graphName: "P4" }, - { position: { x: 0, y: -10, z: -55 }, graphName: "P5" }], + arrows: [], panes: [], windowarcs: [] } - }, - ]; - + } + ]; return (
@@ -134,12 +127,7 @@ const CanvasLayer = () => { {isPointerLocked && } - {splatExists && - } - {!splatExists && - } + {currentSplats.length > 0 && }
); diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index 7fcd9b9..d0c41ad 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -16,7 +16,7 @@ export const FirstPersonControls = ({ speed, rooms, updateCurrentRoom }) => { const forward = new THREE.Vector3(); // Desired height above the ground - const cameraHeight = 5; + const cameraHeight = 0.13; useEffect(() => { const onKeyDown = (event) => { @@ -89,7 +89,8 @@ export const FirstPersonControls = ({ speed, rooms, updateCurrentRoom }) => { }, [rooms, scene]); useFrame((_, delta) => { - const movementSpeed = speed.speed ?? 300; + + const movementSpeed = speed ?? 300; // Get the camera's forward and left direction camera.getWorldDirection(forward); @@ -121,6 +122,10 @@ export const FirstPersonControls = ({ speed, rooms, updateCurrentRoom }) => { camera.position.z >= room.minZ && camera.position.z <= room.maxZ ); + if(currentRoom == null || currentRoom == undefined) { + return; + } + updateCurrentRoom(currentRoom); // find the room the user might be going into From 578623745aa21956d655c860ccb7d5b2d7c878b0 Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 9 Jul 2024 23:30:36 +0200 Subject: [PATCH 7/7] change initial splat --- app/components/CanvasLayer.tsx | 31 ++++++-------------------- app/components/FirstPersonControls.tsx | 2 +- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/app/components/CanvasLayer.tsx b/app/components/CanvasLayer.tsx index 3ae5ac2..17ed45d 100644 --- a/app/components/CanvasLayer.tsx +++ b/app/components/CanvasLayer.tsx @@ -25,6 +25,7 @@ const CanvasLayer = () => { const [currentKey, setCurrentKey] = useState(''); const updateCurrentRoom = (newRoom) => { + if(currentRoom == newRoom || newRoom == '' || newRoom == undefined) { return; } @@ -38,8 +39,6 @@ const CanvasLayer = () => { return adjacentRoom ? adjacentRoom.splat : null; }).filter(splat => splat !== null), newRoom.splat]); } - console.log(newRoom); - console.log(currentSplats); setCurrentKey(`${newRoom.name}${newRoom.adjacent.join('')}`) } @@ -73,7 +72,7 @@ const CanvasLayer = () => { if (teleportControlsRef.current) { // Test coordinates // (posX, posY, posZ, lookAtX, lookAtY, lookAtZ) - teleportControlsRef.current.teleport(0, 0, 0, 0, 2, 1); + teleportControlsRef.current.teleport(0, 0, 5.3, 0, 1, 1); // here you teleport to pos 0,0,0 and look at pos 0,2,1 } }; @@ -81,27 +80,10 @@ const CanvasLayer = () => { // temporary location for rooms const roomConfig = [ { - splat: "nuke_2.splat", - name: "raum2", - adjacent: ["raum1"], - minX: -2, maxX: 1, minY: 0, maxY: 5, minZ: 5, maxZ: 5.65, - slopes: [], - objects: [], - elements: { - arrows: [], - panes: [ - { position: { x: -40, y: -5, z: -40}, verticalRotation: Math.PI / 6, horizontalRotation: Math.PI/4, sizefactor: 10, content: "/images/testbild.png"}, - ], - windowarcs: [ - { position: { x: 40, y: 0, z: -40}, horizontalRotation: Math.PI / 4, arcRadius: 10, arcHeight: 20, content: "/images/testbild.png"} - ] - } - }, - { - splat: "nuke_1.splat", - name: "raum1", + splat: "https://huggingface.co/cakewalk/splat-data/resolve/main/nike.splat", + name: "room1", adjacent: [], - minX: -2, maxX: 1, minY: 0, maxY: 5, minZ: 2, maxZ: 5, + minX: -100, maxX: 100, minY: 0, maxY: 5, minZ: -100, maxZ: 100, slopes: [], objects: [], elements: { @@ -110,6 +92,7 @@ const CanvasLayer = () => { windowarcs: [] } } + ]; return (
@@ -127,7 +110,7 @@ const CanvasLayer = () => { {isPointerLocked && } - {currentSplats.length > 0 && } + {currentSplats.length > 0 && }
); diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index d0c41ad..6cbffde 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -16,7 +16,7 @@ export const FirstPersonControls = ({ speed, rooms, updateCurrentRoom }) => { const forward = new THREE.Vector3(); // Desired height above the ground - const cameraHeight = 0.13; + const cameraHeight = 0.5; useEffect(() => { const onKeyDown = (event) => {