From a4e5c48f9231aa970cfbd4abc2846266e9782f86 Mon Sep 17 00:00:00 2001 From: v4ltages Date: Sun, 27 Jul 2025 17:27:47 +0300 Subject: [PATCH] Add threejs and messiala map --- package.json | 2 + src/app/messiala/page.tsx | 366 +++++++++++++++++++++++++++++++++++++- 2 files changed, 363 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4f43362..8da4a06 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", "@tanstack/react-table": "^8.21.3", + "@types/three": "^0.178.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "drizzle-orm": "^0.44.2", @@ -34,6 +35,7 @@ "react-dom": "^19.1.0", "react-icons": "^5.5.0", "tailwind-merge": "^3.3.1", + "three": "^0.178.0", "tw-animate-css": "^1.3.4" }, "devDependencies": { diff --git a/src/app/messiala/page.tsx b/src/app/messiala/page.tsx index 23ecc61..d322c5d 100644 --- a/src/app/messiala/page.tsx +++ b/src/app/messiala/page.tsx @@ -1,10 +1,366 @@ +"use client"; + import { vipnagorgialla } from "@/components/Vipnagorgialla"; +import * as THREE from "three"; +import { useEffect, useRef, useState } from "react"; export default function Expo() { - return ( -
-

Messiala

-

Koostööpartneritega arutamisel. Rohkem infot teel!

-
+ const mountRef = useRef(null); + const [hoveredRoom, setHoveredRoom] = useState(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + + useEffect(() => { + if (!mountRef.current) return; + + // Scene setup + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x0e0f19); + + // Isometric camera setup + const aspect = 800 / 600; + const frustumSize = 14; + const camera = new THREE.OrthographicCamera( + (frustumSize * aspect) / -2, + (frustumSize * aspect) / 2, + frustumSize / 2, + frustumSize / -2, + 1, + 1000, ); + + // Position camera for isometric view + camera.position.set(10, 10, 14); + camera.lookAt(-1.4, 0, 0); + + // Renderer + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setSize(800, 600); + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFSoftShadowMap; + mountRef.current.appendChild(renderer.domElement); + + // Raycaster for mouse interactions + const raycaster = new THREE.Raycaster(); + const mouse = new THREE.Vector2(); + + // Lighting + const ambientLight = new THREE.AmbientLight(0x404040, 1.2); + scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); + directionalLight.position.set(10, 10, 5); + directionalLight.castShadow = false; + directionalLight.shadow.mapSize.width = 2048; + directionalLight.shadow.mapSize.height = 2048; + scene.add(directionalLight); + + // Room colors and names + const roomColors = [ + 0xff6b35, // Orange - Mänguklubi + 0x4ecdc4, // Turquoise - Baariala + 0xffe66d, // Yellow - EVAL + 0xe74c3c, // Red - Redbull + 0x9b59b6, // Purple - Võitlusmängu ala + 0x3498db, // Blue - Sony + 0x2ecc71, // Green - Chillimisala + ]; + + const roomNames = [ + "Mänguklubi", + "Baariala", + "EVAL", + "Redbull", + "Võitlusmängu ala", + "Sony", + "Chillimisala", + ]; + + // Create individual rooms as rectangles with custom positions + const rooms: THREE.Mesh[] = []; + const roomData: Array<{ + mesh: THREE.Mesh; + name: string; + originalColor: number; + originalScale: THREE.Vector3; + }> = []; + + // Define rooms with custom positions, sizes and colors + const roomDefinitions = [ + { + width: 7, + height: 0.7, + depth: 3, + x: 2.5, + z: 4, + color: roomColors[0], + name: roomNames[0], + }, // Mänguklubi + { + width: 2.5, + height: 0.7, + depth: 0.7, + x: 1, + z: 0, + color: roomColors[1], + name: roomNames[1], + }, // Baariala + { + width: 1.8, + height: 0.7, + depth: 1.5, + x: 2.5, + z: -3.5, + color: roomColors[2], + name: roomNames[2], + }, // EVAL + { + width: 2, + height: 0.7, + depth: 4, + x: 5, + z: -1.7, + color: roomColors[3], + name: roomNames[3], + }, // Redbull + { + width: 3, + height: 0.7, + depth: 1.3, + x: 0, + z: -3.5, + color: roomColors[4], + name: roomNames[4], + }, // Võitlusmängu ala + { + width: 1.8, + height: 0.7, + depth: 1.5, + x: -2.55, + z: -3.5, + color: roomColors[5], + name: roomNames[5], + }, // Sony + { + width: 4, + height: 0.7, + depth: 4, + x: -5.5, + z: -2.3, + color: roomColors[6], + name: roomNames[6], + }, // Chillimisala + ]; + + roomDefinitions.forEach((roomDef) => { + const geometry = new THREE.BoxGeometry( + roomDef.width, + roomDef.height, + roomDef.depth, + ); + const material = new THREE.MeshLambertMaterial({ + color: roomDef.color, + }); + + const room = new THREE.Mesh(geometry, material); + room.position.set(roomDef.x, roomDef.height / 2, roomDef.z); + room.castShadow = true; + room.receiveShadow = true; + room.userData = { name: roomDef.name, originalColor: roomDef.color }; + + scene.add(room); + rooms.push(room); + roomData.push({ + mesh: room, + name: roomDef.name, + originalColor: roomDef.color, + originalScale: room.scale.clone(), + }); + }); + + // Ground plane + const groundGeometry = new THREE.PlaneGeometry(14, 10.5); + const groundMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc }); + const ground = new THREE.Mesh(groundGeometry, groundMaterial); + ground.rotation.x = -Math.PI / 2; + ground.position.x = -1.1; + ground.position.y = -0.5; + ground.receiveShadow = true; + scene.add(ground); + + // Second ground plane + const groundGeometry2 = new THREE.PlaneGeometry(2, 7); + const groundMaterial2 = new THREE.MeshLambertMaterial({ color: 0xcccccc }); + const ground2 = new THREE.Mesh(groundGeometry2, groundMaterial2); + ground2.rotation.x = -Math.PI / 2; + ground2.position.x = -12.2; + ground2.position.y = -5; + ground2.receiveShadow = true; + scene.add(ground2); + + // Mouse event handlers + const onMouseMove = (event: MouseEvent) => { + const rect = renderer.domElement.getBoundingClientRect(); + mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + + // Update mouse position for tooltip + setMousePosition({ x: event.clientX, y: event.clientY }); + + // Update raycaster + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(rooms); + + // Reset all rooms to original state + roomData.forEach(({ mesh, originalColor, originalScale }) => { + (mesh.material as THREE.MeshLambertMaterial).color.setHex( + originalColor, + ); + mesh.scale.copy(originalScale); + }); + + if (intersects.length > 0) { + const hoveredMesh = intersects[0].object as THREE.Mesh; + const roomInfo = roomData.find((r) => r.mesh === hoveredMesh); + + if (roomInfo) { + // Apply hover effects + (hoveredMesh.material as THREE.MeshLambertMaterial).color.setHex( + 0xffffff, + ); + hoveredMesh.scale.multiplyScalar(1.02); + setHoveredRoom(roomInfo.name); + } + } else { + setHoveredRoom(null); + } + }; + + // Add mouse event listener + renderer.domElement.addEventListener("mousemove", onMouseMove); + + // Animation loop + const animate = () => { + requestAnimationFrame(animate); + + // Gentle floating animation for rooms + rooms.forEach((room, index) => { + const originalY = 0.25; // height / 2 for the room height of 0.5 + const baseY = originalY + Math.sin(Date.now() * 0.001 + index) * 0.05; + + // Maintain current scale while updating Y position + room.position.y = baseY; + }); + + renderer.render(scene, camera); + }; + + animate(); + + // Cleanup + return () => { + renderer.domElement.removeEventListener("mousemove", onMouseMove); + if (mountRef.current && renderer.domElement) { + mountRef.current.removeChild(renderer.domElement); + } + renderer.dispose(); + }; + }, []); + + return ( +
+

+ Messiala +

+
+

+ Tudengimaja +

+
+
+
+ + Mänguklubi + +
+
+
+ + Baariala + +
+
+
+ + EVAL + +
+
+
+ + Redbull + +
+
+
+ + Võitlusmängu ala + +
+
+
+ + Sony + +
+
+
+ + Chillimisala + +
+
+
+
+
+
+
+ + {/* Tooltip */} + {hoveredRoom && ( +
+ {hoveredRoom} +
+ )} +
+
+ ); }