Fun little 3D scene in React. Made with v0.dev.
31 August 2024 • azxyc
I saw some pretty cool examples generated by Vercel's v0 chat recently, so I decided to try it out for myself. Here's a 3D scene created using React Three Fiber. Click the cube!
"use client"
import { Canvas, useFrame, useThree } from "@react-three/fiber"
import { OrbitControls, PerspectiveCamera, Box, useHelper } from "@react-three/drei"
import { Physics, usePlane, useBox } from "@react-three/cannon"
import * as THREE from "three"
import { useRef, useState } from "react"
import { DragControls } from './DragControls'
const GRAVITY_PRESETS = {
earth: [0, -9.81, 0],
}
function Cube({ position, size, onSplit }) {
const [ref, api] = useBox(() => ({
mass: 1,
position,
args: [size, size, size],
}))
const handleClick = (event) => {
event.stopPropagation()
if (size > 0.1) {
onSplit(position, size / 2)
api.position.set(1000, 1000, 1000)
}
}
return (
<Box
ref={ref}
args={[size, size, size]}
castShadow
receiveShadow
onClick={handleClick}
>
<meshPhysicalMaterial
color="white"
emissive="white"
emissiveIntensity={0.2}
roughness={0.1}
metalness={0.1}
/>
</Box>
)
}
function CeilingLight() {
const lightRef = useRef(null)
useHelper(lightRef, THREE.DirectionalLightHelper, 1, "red")
return (
<directionalLight
ref={lightRef}
position={[0, 5, 0]}
intensity={1}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
)
}
function DragController({ children }) {
const { camera, gl } = useThree()
const groupRef = useRef(null)
const controlsRef = useRef(null)
useFrame(() => {
if (controlsRef.current) {
controlsRef.current.enabled = true
}
})
return (
<group ref={groupRef}>
<primitive object={new DragControls([], camera, gl.domElement)} ref={controlsRef} />
{children}
</group>
)
}
function Floor() {
const [ref] = usePlane(() => ({
rotation: [-Math.PI / 2, 0, 0],
position: [0, -2, 0],
}))
return (
<mesh ref={ref} receiveShadow>
<planeGeometry args={[100, 100]} />
<shadowMaterial transparent opacity={0.4} />
</mesh>
)
}
export default function Scene() {
const [cubes, setCubes] = useState([{ position: [0, 0, 0], size: 1 }])
const gravity = GRAVITY_PRESETS.earth
const handleSplit = (position, newSize) => {
const offsets = [
[1, 1, 1], [-1, 1, 1], [1, -1, 1], [-1, -1, 1],
[1, 1, -1], [-1, 1, -1], [1, -1, -1], [-1, -1, -1]
]
const newCubes = offsets.map(offset => ({
position: position.map((p, i) => p + offset[i] * newSize / 2),
size: newSize
}))
setCubes(prev => [...prev, ...newCubes])
}
return (
<div className="w-[500px] h-[500px] border border-primary/10 shadow-2xl shadow-white/5 rounded-2xl bg-black">
<Canvas shadows>
<color attach="background" args={["black"]} />
<PerspectiveCamera makeDefault position={[3, 3, 3]} />
<OrbitControls enableDamping dampingFactor={0.05} />
<ambientLight intensity={0.1} />
<CeilingLight />
<Physics gravity={gravity}>
<DragController>
{cubes.map((cube, index) => (
<Cube key={index} position={cube.position} size={cube.size} onSplit={handleSplit} />
))}
</DragController>
<Floor />
</Physics>
</Canvas>
</div>
)
}