Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental: 3d badge #1

Merged
merged 3 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-three/drei": "^9.109.2",
"@react-three/fiber": "^8.16.8",
"@react-three/rapier": "^1.4.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"firebase": "^10.10.0",
"html-to-image": "^1.11.11",
"leva": "^0.9.35",
"lucide-react": "^0.365.0",
"modern-screenshot": "^4.4.39",
"nanoid": "^5.0.7",
Expand All @@ -38,13 +42,15 @@
"sonner": "^1.4.41",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"three": "^0.167.1",
"vaul": "^0.9.0",
"zod": "^3.22.5"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/three": "^0.167.1",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.4",
Expand Down
1 change: 1 addition & 0 deletions public/Geist_Regular.json

Large diffs are not rendered by default.

Binary file added public/band.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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: 9 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,13 @@
body {
@apply bg-background text-foreground;
}

html,
body,
#root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
}
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