Skip to content

Instantly share code, notes, and snippets.

@kiritocode1
Last active July 23, 2025 05:40
Show Gist options
  • Save kiritocode1/20fdf85eac388f8d785770d7e3716b69 to your computer and use it in GitHub Desktop.
Save kiritocode1/20fdf85eac388f8d785770d7e3716b69 to your computer and use it in GitHub Desktop.
3d scene in VR
"use client";
import React, { useRef, useState, useMemo } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { XR, VRButton, useXR, createXRStore } from "@react-three/xr";
import { OrbitControls, Text } from "@react-three/drei";
import { Mesh } from "three";
// Animated cube component
function AnimatedCube({ position, color = "orange" }: { position: [number, number, number]; color: string }) {
const meshRef = useRef<Mesh>(null);
const [hovered, setHovered] = useState(false);
const [clicked, setClicked] = useState(false);
useFrame((state, delta) => {
if (meshRef.current) {
meshRef.current.rotation.x += delta * 0.5;
meshRef.current.rotation.y += delta * 0.3;
meshRef.current.scale.setScalar(clicked ? 1.5 : hovered ? 1.2 : 1);
}
});
return (
<mesh
ref={meshRef}
position={position}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
onClick={() => setClicked(!clicked)}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? "hotpink" : color} />
</mesh>
);
}
// Interactive sphere component
function InteractiveSphere({ position }: { position: [number, number, number] }) {
const [color, setColor] = useState("lightblue");
const colors = ["lightblue", "lightgreen", "lightyellow", "lightcoral", "lightpink"];
const changeColor = () => {
const randomColor = colors[Math.floor(Math.random() * colors.length)];
setColor(randomColor);
};
return (
<mesh
position={position}
onClick={changeColor}
>
<sphereGeometry args={[0.8, 32, 32]} />
<meshStandardMaterial color={color} />
</mesh>
);
}
// VR Scene component
function VRScene() {
const { session } = useXR();
return (
<>
{/* Lighting */}
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
<directionalLight
position={[-10, -10, -5]}
intensity={0.5}
/>
{/* Interactive objects */}
<AnimatedCube
position={[-2, 0, -2]}
color="orange"
/>
<AnimatedCube
position={[2, 0, -2]}
color="purple"
/>
<InteractiveSphere position={[0, 2, -3]} />
{/* Ground plane */}
<mesh
rotation={[-Math.PI / 2, 0, 0]}
position={[0, -2, 0]}
>
<planeGeometry args={[20, 20]} />
<meshStandardMaterial color="lightgray" />
</mesh>
{/* Welcome text */}
<Text
position={[0, 3, -4]}
fontSize={0.5}
color="white"
anchorX="center"
anchorY="middle"
>
Welcome to Next.js XR!
</Text>
<Text
position={[0, 2.5, -4]}
fontSize={0.3}
color="lightblue"
anchorX="center"
anchorY="middle"
>
{session ? "You are in VR mode!" : "Click VR button to enter VR"}
</Text>
{/* VR Controllers */}
{/* <Controllers /> */}
{/* Desktop orbit controls (disabled in VR) */}
{!session && <OrbitControls />}
</>
);
}
// Main XR App component
export default function XRApp() {
const store = useMemo(() => createXRStore(), []);
return (
<div style={{ width: "100vw", height: "100vh", background: "#1a1a1a" }}>
{/* VR Button */}
<div
style={{
position: "absolute",
top: "20px",
left: "20px",
zIndex: 100,
color: "white",
fontFamily: "Arial, sans-serif",
}}
>
<h2>Next.js XR Demo</h2>
<p>Use mouse to look around, click objects to interact</p>
<VRButton
store={store}
style={{
background: "#6366f1",
color: "white",
border: "none",
padding: "12px 24px",
borderRadius: "8px",
cursor: "pointer",
fontSize: "16px",
fontWeight: "bold",
}}
/>
</div>
{/* Instructions */}
<div
style={{
position: "absolute",
bottom: "20px",
left: "20px",
color: "white",
fontFamily: "Arial, sans-serif",
fontSize: "14px",
maxWidth: "300px",
}}
>
<p>
<strong>Desktop:</strong> Click and drag to orbit, scroll to zoom
</p>
<p>
<strong>VR:</strong> Use controllers to interact with objects
</p>
<p>
<strong>Interactions:</strong> Click cubes to scale, click sphere to change color
</p>
</div>
{/* Canvas with XR support */}
<Canvas camera={{ position: [0, 0, 5], fov: 60 }}>
<XR store={store}>
<VRScene />
</XR>
</Canvas>
</div>
);
}
@kiritocode1
Copy link
Author

package.json

{
	"name": "three-js-webxr-game",
	"version": "0.1.0",
	"private": true,
	"scripts": {
		"dev": "next dev --turbopack",
		"build": "next build",
		"start": "next start",
		"lint": "next lint"
	},
	"dependencies": {
		"@bufbuild/protobuf": "^2.6.1",
		"@react-spring/three": "^10.0.1",
		"@react-three/drei": "^10.5.2",
		"@react-three/fiber": "^9.2.0",
		"@react-three/rapier": "^2.1.0",
		"@react-three/uikit": "^0.8.21",
		"@react-three/uikit-apfel": "^0.8.21",
		"@react-three/xr": "^6.6.20",
		"@tonejs/midi": "^2.0.28",
		"@types/three": "^0.170.0",
		"@wavesurfer/react": "^1.0.11",
		"next": "15.4.2",
		"react": "19.1.0",
		"react-dom": "19.1.0",
		"three": "^0.170.0",
		"three-stdlib": "^2.36.0",
		"wavesurfer.js": "^7.10.0",
		"zustand": "^5.0.6"
	},
	"devDependencies": {
		"@eslint/eslintrc": "^3.3.1",
		"@tailwindcss/postcss": "^4.1.11",
		"@types/node": "^20.19.9",
		"@types/react": "^19.1.8",
		"@types/react-dom": "^19.1.6",
		"eslint": "^9.31.0",
		"eslint-config-next": "15.4.2",
		"tailwindcss": "^4.1.11",
		"typescript": "^5.8.3"
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment