Skip to content

Commit

Permalink
feat: 3d badge using r3f
Browse files Browse the repository at this point in the history
  • Loading branch information
hyamero committed Aug 2, 2024
1 parent eb92e83 commit c13b52f
Show file tree
Hide file tree
Showing 2 changed files with 338 additions and 6 deletions.
335 changes: 335 additions & 0 deletions src/app/badge/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
"use client";

import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { Canvas, extend, useThree, useFrame } from "@react-three/fiber";
import {
useGLTF,
useTexture,
Environment,
Lightformer,
RenderTexture,
Text3D,
Resize,
Center,
PerspectiveCamera,
} from "@react-three/drei";
import {
BallCollider,
CuboidCollider,
Physics,
RigidBody,
useRopeJoint,
useSphericalJoint,
} from "@react-three/rapier";
import { MeshLineGeometry, MeshLineMaterial } from "meshline";
import { useControls } from "leva";

extend({ MeshLineGeometry, MeshLineMaterial });

useTexture.preload(
"https://github.com/user-attachments/assets/999b5d58-ac8a-4c20-8fc6-74e8ab7876e7",
);

export default function App() {
const { debug } = useControls({ debug: false });
return (
<Canvas camera={{ position: [0, 0, 13], fov: 25 }}>
<ambientLight intensity={Math.PI} />
<Physics
debug={debug}
interpolate
gravity={[0, -40, 0]}
timeStep={1 / 60}
>
<Band />
</Physics>
<Environment background blur={0.75}>
<color attach="background" args={["black"]} />
<Lightformer
intensity={2}
color="white"
position={[0, -1, 5]}
rotation={[0, 0, Math.PI / 3]}
scale={[100, 0.1, 1]}
/>
<Lightformer
intensity={3}
color="white"
position={[-1, -1, 1]}
rotation={[0, 0, Math.PI / 3]}
scale={[100, 0.1, 1]}
/>
<Lightformer
intensity={3}
color="white"
position={[1, 1, 1]}
rotation={[0, 0, Math.PI / 3]}
scale={[100, 0.1, 1]}
/>
<Lightformer
intensity={10}
color="white"
position={[-10, 0, 14]}
rotation={[0, Math.PI / 2, Math.PI / 3]}
scale={[100, 10, 1]}
/>
</Environment>
</Canvas>
);
}

function BadgeTexture() {
// const viewport = useThree((state) => state.viewport);

return (
<>
<PerspectiveCamera
makeDefault
manual
aspect={1.05}
position={[0.49, 0.22, 2]}
/>
{/* <mesh>
<planeGeometry args={[viewport, -viewport / 1.05]} />
<meshBasicMaterial
transparent
alphaMap={texture}
side={THREE.BackSide}
/>
</mesh> */}
<Center>
<Resize maxHeight={2000} maxWidth={2000}>
<Text3D
bevelEnabled={false}
bevelSize={0}
font="/Geist_Regular.json"
height={0}
position={[0.9, 2, 0]}
rotation={[0, Math.PI, Math.PI]}
size={0.2}
letterSpacing={-0.03}
>
hyamero
</Text3D>
<Text3D
bevelEnabled={false}
bevelSize={0}
font="/Geist_Regular.json"
height={0}
position={[1.13, 3.1, 0]}
rotation={[0, Math.PI, Math.PI]}
size={0.07}
letterSpacing={-0.008}
>
omsimos.com
</Text3D>
</Resize>
</Center>
</>
);
}

function Band({ maxSpeed = 50, minSpeed = 10 }) {
const band = useRef(), fixed = useRef(), j1 = useRef(), j2 = useRef(), j3 = useRef(), card = useRef() // prettier-ignore
const vec = new THREE.Vector3(), ang = new THREE.Vector3(), rot = new THREE.Vector3(), dir = new THREE.Vector3() // prettier-ignore
const segmentProps = {
type: "dynamic",
canSleep: true,
colliders: false,
angularDamping: 2,
linearDamping: 2,
};
useGLTF.preload(
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/5huRVDzcoDwnbgrKUo1Lzs/53b6dd7d6b4ffcdbd338fa60265949e1/tag.glb",
);

const { nodes, materials } = useGLTF(
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/5huRVDzcoDwnbgrKUo1Lzs/53b6dd7d6b4ffcdbd338fa60265949e1/tag.glb",
);

// const texture = useTexture(
// "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/SOT1hmCesOHxEYxL7vkoZ/c57b29c85912047c414311723320c16b/band.jpg",
// );

const texture = useTexture("/band.jpg");
const { width, height } = useThree((state) => state.size);
const [curve] = useState(
() =>
new THREE.CatmullRomCurve3([
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3(),
]),
);
const [dragged, drag] = useState(false);
const [hovered, hover] = useState(false);

useRopeJoint(fixed, j1, [[0, 0, 0], [0, 0, 0], 1]) // prettier-ignore
useRopeJoint(j1, j2, [[0, 0, 0], [0, 0, 0], 1]) // prettier-ignore
useRopeJoint(j2, j3, [[0, 0, 0], [0, 0, 0], 1]) // prettier-ignore
useSphericalJoint(j3, card, [[0, 0, 0], [0, 1.45, 0]]) // prettier-ignore

useEffect(() => {
if (hovered) {
document.body.style.cursor = dragged ? "grabbing" : "grab";
return () => void (document.body.style.cursor = "auto");
}
}, [hovered, dragged]);

useFrame((state, delta) => {
if (dragged) {
vec.set(state.pointer.x, state.pointer.y, 0.5).unproject(state.camera);
dir.copy(vec).sub(state.camera.position).normalize();
vec.add(dir.multiplyScalar(state.camera.position.length()));
[card, j1, j2, j3, fixed].forEach((ref) => ref.current?.wakeUp());
card.current?.setNextKinematicTranslation({
x: vec.x - dragged.x,
y: vec.y - dragged.y,
z: vec.z - dragged.z,
});
}
if (fixed.current) {
// Fix most of the jitter when over pulling the card
[j1, j2].forEach((ref) => {
if (!ref.current.lerped)
ref.current.lerped = new THREE.Vector3().copy(
ref.current.translation(),
);
const clampedDistance = Math.max(
0.1,
Math.min(1, ref.current.lerped.distanceTo(ref.current.translation())),
);
ref.current.lerped.lerp(
ref.current.translation(),
delta * (minSpeed + clampedDistance * (maxSpeed - minSpeed)),
);
});
// Calculate catmul curve
curve.points[0].copy(j3.current.translation());
curve.points[1].copy(j2.current.lerped);
curve.points[2].copy(j1.current.lerped);
curve.points[3].copy(fixed.current.translation());
band.current.geometry.setPoints(curve.getPoints(32));
// Tilt it back towards the screen
ang.copy(card.current.angvel());
rot.copy(card.current.rotation());
card.current.setAngvel({ x: ang.x, y: ang.y - rot.y * 0.25, z: ang.z });
}
});

curve.curveType = "chordal";
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;

return (
<>
<group position={[0, 4, 0]}>
<RigidBody ref={fixed} {...segmentProps} type="fixed" />
<RigidBody position={[0.5, 0, 0]} ref={j1} {...segmentProps}>
<BallCollider args={[0.1]} />
</RigidBody>
<RigidBody position={[1, 0, 0]} ref={j2} {...segmentProps}>
<BallCollider args={[0.1]} />
</RigidBody>
<RigidBody position={[1.5, 0, 0]} ref={j3} {...segmentProps}>
<BallCollider args={[0.1]} />
</RigidBody>
<RigidBody
position={[2, 0, 0]}
ref={card}
{...segmentProps}
type={dragged ? "kinematicPosition" : "dynamic"}
>
<CuboidCollider args={[0.8, 1.125, 0.01]} />
<group
scale={2.25}
position={[0, -1.2, -0.05]}
onPointerOver={() => hover(true)}
onPointerOut={() => hover(false)}
onPointerUp={(e) => (
e.target.releasePointerCapture(e.pointerId), drag(false)
)}
onPointerDown={(e) => (
e.target.setPointerCapture(e.pointerId),
drag(
new THREE.Vector3()
.copy(e.point)
.sub(vec.copy(card.current.translation())),
)
)}
>
{/* <mesh geometry={nodes.card.geometry}>
<meshPhysicalMaterial
map={materials.base.map}
map-anisotropy={16}
clearcoat={1}
clearcoatRoughness={0.15}
roughness={0.3}
metalness={0.5}
/>
</mesh> */}
<mesh geometry={nodes.card.geometry}>
<meshPhysicalMaterial
clearcoat={1}
clearcoatRoughness={0.15}
iridescence={1}
iridescenceIOR={1}
iridescenceThicknessRange={[0, 2400]}
metalness={0.5}
roughness={0.3}
>
<RenderTexture attach="map" height={2000} width={2000}>
{/* <BadgeTexture user={user} /> */}
<BadgeTexture />
</RenderTexture>
</meshPhysicalMaterial>
</mesh>

{/* <Center bottom right>
<Resize maxHeight={0.45} maxWidth={0.925}>
<Text3D
bevelEnabled={false}
bevelSize={0}
font="/Geist_Regular.json"
height={0}
rotation={[0, Math.PI, Math.PI]}
>
Joseph Dale
</Text3D>
<Text3D
bevelEnabled={false}
bevelSize={0}
font="/Geist_Regular.json"
height={0}
position={[0, 1.4, 0]}
rotation={[0, Math.PI, Math.PI]}
>
Omsimos
</Text3D>
</Resize>
</Center> */}
<mesh
geometry={nodes.clip.geometry}
material={materials.metal}
material-roughness={0.3}
/>
<mesh geometry={nodes.clamp.geometry} material={materials.metal} />
</group>
</RigidBody>
</group>
<mesh ref={band}>
<meshLineGeometry />
<meshLineMaterial
color="white"
depthTest={false}
resolution={[width, height]}
useMap
map={texture}
repeat={[-3, 1]}
lineWidth={1}
/>
</mesh>
</>
);
}
9 changes: 3 additions & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ export default async function Home() {
)}
>
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 transition ease-out hover:text-neutral-600 hover:duration-300 hover:dark:text-neutral-400">
<Link
href="https://github.com/hyamero/certificate-generator"
target="_blank"
rel="noopener noreferrer"
>
✨ Contribute to GitHub
<Link href="/badge">
✨ Experimental:
<span className="font-semibold"> Visit 3D Badge</span>
</Link>
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
</AnimatedShinyText>
Expand Down

0 comments on commit c13b52f

Please sign in to comment.