From 3b39bb19956721c8dba135c380bf78fe1b1b2f76 Mon Sep 17 00:00:00 2001 From: Pedro Almeida Date: Fri, 12 Jun 2026 10:52:26 -0300 Subject: [PATCH] Remove any refs from Lanyard Use RapierRigidBody and typed meshline refs in both TypeScript Lanyard variants. --- src/ts-default/Components/Lanyard/Lanyard.tsx | 74 +++++++++++-------- .../Components/Lanyard/Lanyard.tsx | 74 +++++++++++-------- 2 files changed, 90 insertions(+), 58 deletions(-) diff --git a/src/ts-default/Components/Lanyard/Lanyard.tsx b/src/ts-default/Components/Lanyard/Lanyard.tsx index 41235a7e2..6ee929f44 100644 --- a/src/ts-default/Components/Lanyard/Lanyard.tsx +++ b/src/ts-default/Components/Lanyard/Lanyard.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/no-unknown-property */ 'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; -import { Canvas, extend, useFrame } from '@react-three/fiber'; +import { Canvas, extend, useFrame, type ThreeElement, type ThreeEvent } from '@react-three/fiber'; import { useGLTF, useTexture, Environment, Lightformer } from '@react-three/drei'; import { BallCollider, @@ -10,7 +10,8 @@ import { RigidBody, useRopeJoint, useSphericalJoint, - RigidBodyProps + type RapierRigidBody, + type RigidBodyProps } from '@react-three/rapier'; import { MeshLineGeometry, MeshLineMaterial } from 'meshline'; import * as THREE from 'three'; @@ -23,6 +24,13 @@ import './Lanyard.css'; extend({ MeshLineGeometry, MeshLineMaterial }); +declare module '@react-three/fiber' { + interface ThreeElements { + meshLineGeometry: ThreeElement; + meshLineMaterial: ThreeElement; + } +} + // 1x1 transparent pixel — lets useTexture be called unconditionally when a // front/back image isn't supplied. const BLANK_PIXEL = @@ -131,6 +139,10 @@ interface BandProps { lanyardWidth?: number; } +type LanyardRigidBody = RapierRigidBody & { + lerped?: THREE.Vector3; +}; + function Band({ maxSpeed = 50, minSpeed = 0, @@ -141,27 +153,34 @@ function Band({ lanyardImage = null, lanyardWidth = 1 }: BandProps) { - // Using "any" for refs since the exact types depend on Rapier's internals - const band = useRef(null); - const fixed = useRef(null); - const j1 = useRef(null); - const j2 = useRef(null); - const j3 = useRef(null); - const card = useRef(null); + const band = useRef, InstanceType>>(null!); + const fixed = useRef(null!); + const j1 = useRef(null!); + const j2 = useRef(null!); + const j3 = useRef(null!); + const card = useRef(null!); const vec = new THREE.Vector3(); const ang = new THREE.Vector3(); const rot = new THREE.Vector3(); const dir = new THREE.Vector3(); - const segmentProps: any = { - type: 'dynamic' as RigidBodyProps['type'], + const segmentProps: RigidBodyProps = { + type: 'dynamic', canSleep: true, colliders: false, angularDamping: 4, linearDamping: 4 }; + const getLerped = (body: LanyardRigidBody): THREE.Vector3 => { + if (!body.lerped) { + body.lerped = new THREE.Vector3().copy(body.translation()); + } + + return body.lerped; + }; + const { nodes, materials } = useGLTF(cardGLB) as any; const texture = useTexture(lanyardImage || lanyard); // useTexture must be called unconditionally; use a blank pixel when an image @@ -253,21 +272,18 @@ function Band({ } if (fixed.current) { [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)) - ); + const lerped = getLerped(ref.current); + const clampedDistance = Math.max(0.1, Math.min(1, lerped.distanceTo(ref.current.translation()))); + lerped.lerp(ref.current.translation(), delta * (minSpeed + clampedDistance * (maxSpeed - minSpeed))); }); curve.points[0].copy(j3.current.translation()); - curve.points[1].copy(j2.current.lerped); - curve.points[2].copy(j1.current.lerped); + curve.points[1].copy(getLerped(j2.current)); + curve.points[2].copy(getLerped(j1.current)); curve.points[3].copy(fixed.current.translation()); band.current.geometry.setPoints(curve.getPoints(isMobile ? 16 : 32)); 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 }); + card.current.setAngvel({ x: ang.x, y: ang.y - rot.y * 0.25, z: ang.z }, true); } }); @@ -277,21 +293,21 @@ function Band({ return ( <> - - + + - + - + hover(true)} onPointerOut={() => hover(false)} - onPointerUp={(e: any) => { - e.target.releasePointerCapture(e.pointerId); + onPointerUp={(e: ThreeEvent) => { + (e.target as Element).releasePointerCapture(e.pointerId); drag(false); }} - onPointerDown={(e: any) => { - e.target.setPointerCapture(e.pointerId); + onPointerDown={(e: ThreeEvent) => { + (e.target as Element).setPointerCapture(e.pointerId); drag(new THREE.Vector3().copy(e.point).sub(vec.copy(card.current.translation()))); }} > diff --git a/src/ts-tailwind/Components/Lanyard/Lanyard.tsx b/src/ts-tailwind/Components/Lanyard/Lanyard.tsx index bf2a8fdc6..730ad7f9d 100644 --- a/src/ts-tailwind/Components/Lanyard/Lanyard.tsx +++ b/src/ts-tailwind/Components/Lanyard/Lanyard.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/no-unknown-property */ 'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; -import { Canvas, extend, useFrame } from '@react-three/fiber'; +import { Canvas, extend, useFrame, type ThreeElement, type ThreeEvent } from '@react-three/fiber'; import { useGLTF, useTexture, Environment, Lightformer } from '@react-three/drei'; import { BallCollider, @@ -10,7 +10,8 @@ import { RigidBody, useRopeJoint, useSphericalJoint, - RigidBodyProps + type RapierRigidBody, + type RigidBodyProps } from '@react-three/rapier'; import { MeshLineGeometry, MeshLineMaterial } from 'meshline'; import * as THREE from 'three'; @@ -21,6 +22,13 @@ import lanyard from './lanyard.png'; extend({ MeshLineGeometry, MeshLineMaterial }); +declare module '@react-three/fiber' { + interface ThreeElements { + meshLineGeometry: ThreeElement; + meshLineMaterial: ThreeElement; + } +} + // 1x1 transparent pixel — lets useTexture be called unconditionally when a // front/back image isn't supplied. const BLANK_PIXEL = @@ -129,6 +137,10 @@ interface BandProps { lanyardWidth?: number; } +type LanyardRigidBody = RapierRigidBody & { + lerped?: THREE.Vector3; +}; + function Band({ maxSpeed = 50, minSpeed = 0, @@ -139,27 +151,34 @@ function Band({ lanyardImage = null, lanyardWidth = 1 }: BandProps) { - // Using "any" for refs since the exact types depend on Rapier's internals - const band = useRef(null); - const fixed = useRef(null); - const j1 = useRef(null); - const j2 = useRef(null); - const j3 = useRef(null); - const card = useRef(null); + const band = useRef, InstanceType>>(null!); + const fixed = useRef(null!); + const j1 = useRef(null!); + const j2 = useRef(null!); + const j3 = useRef(null!); + const card = useRef(null!); const vec = new THREE.Vector3(); const ang = new THREE.Vector3(); const rot = new THREE.Vector3(); const dir = new THREE.Vector3(); - const segmentProps: any = { - type: 'dynamic' as RigidBodyProps['type'], + const segmentProps: RigidBodyProps = { + type: 'dynamic', canSleep: true, colliders: false, angularDamping: 4, linearDamping: 4 }; + const getLerped = (body: LanyardRigidBody): THREE.Vector3 => { + if (!body.lerped) { + body.lerped = new THREE.Vector3().copy(body.translation()); + } + + return body.lerped; + }; + const { nodes, materials } = useGLTF(cardGLB) as any; const texture = useTexture(lanyardImage || lanyard); // useTexture must be called unconditionally; use a blank pixel when an image @@ -251,21 +270,18 @@ function Band({ } if (fixed.current) { [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)) - ); + const lerped = getLerped(ref.current); + const clampedDistance = Math.max(0.1, Math.min(1, lerped.distanceTo(ref.current.translation()))); + lerped.lerp(ref.current.translation(), delta * (minSpeed + clampedDistance * (maxSpeed - minSpeed))); }); curve.points[0].copy(j3.current.translation()); - curve.points[1].copy(j2.current.lerped); - curve.points[2].copy(j1.current.lerped); + curve.points[1].copy(getLerped(j2.current)); + curve.points[2].copy(getLerped(j1.current)); curve.points[3].copy(fixed.current.translation()); band.current.geometry.setPoints(curve.getPoints(isMobile ? 16 : 32)); 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 }); + card.current.setAngvel({ x: ang.x, y: ang.y - rot.y * 0.25, z: ang.z }, true); } }); @@ -275,21 +291,21 @@ function Band({ return ( <> - - + + - + - + hover(true)} onPointerOut={() => hover(false)} - onPointerUp={(e: any) => { - e.target.releasePointerCapture(e.pointerId); + onPointerUp={(e: ThreeEvent) => { + (e.target as Element).releasePointerCapture(e.pointerId); drag(false); }} - onPointerDown={(e: any) => { - e.target.setPointerCapture(e.pointerId); + onPointerDown={(e: ThreeEvent) => { + (e.target as Element).setPointerCapture(e.pointerId); drag(new THREE.Vector3().copy(e.point).sub(vec.copy(card.current.translation()))); }} >