|
1 |
| -import "./styles.css" |
2 |
| -import { Canvas, useFrame } from "react-three-fiber" |
3 |
| -import React, { Suspense, useRef, useEffect, useState } from "react" |
4 |
| -import { useGLTF, OrbitControls, ContactShadows } from "drei" |
| 1 | +import * as THREE from "three" |
| 2 | +import { Canvas, useFrame, useThree } from "react-three-fiber" |
| 3 | +import React, { Suspense, useRef } from "react" |
| 4 | +import { ContactShadows } from "@react-three/drei" |
5 | 5 | import { A11y, useA11y, A11yAnnouncer } from "../../"
|
6 | 6 | import { ResizeObserver } from "@juggle/resize-observer"
|
7 |
| -import { a as animated } from "@react-spring/web" |
8 |
| -import { useTransition, useSpring } from "@react-spring/core" |
9 |
| -import diamond from "../public/diamond.glb" |
10 |
| -import rupee from "../public/rupee.glb" |
11 |
| -import { a } from "@react-spring/three" |
12 |
| - |
13 |
| -const SimpleToggleButton = props => { |
14 |
| - const mesh = useRef() |
15 |
| - const a11yContext = useA11y() |
16 |
| - const [prevA11yContextValue, saveA11yContextValue] = useState(a11yContext) |
17 |
| - |
18 |
| - const { color } = useSpring({ color: a11yContext.focus ? "#5dc8dc" : a11yContext.hover ? "#239db4" : "lightblue" }) |
19 |
| - const [scale, setScale] = useSpring(() => ({ |
20 |
| - scale: [1, 1, 1], |
21 |
| - })) |
22 |
| - useEffect(() => { |
23 |
| - if (!prevA11yContextValue.focus && a11yContext.focus) { |
24 |
| - setScale({ |
25 |
| - to: [{ scale: [1.2, 1.2, 1.2] }, { scale: [1, 1, 1] }], |
26 |
| - config: { duration: 200 }, |
27 |
| - }) |
28 |
| - } |
29 |
| - saveA11yContextValue(a11yContext) |
30 |
| - }, [a11yContext]) |
31 |
| - |
| 7 | +import { proxy, useProxy } from "valtio" |
| 8 | +import { EffectComposer, SSAO, SMAA } from "@react-three/postprocessing" |
| 9 | +import { Badge } from "@pmndrs/branding" |
| 10 | + |
| 11 | +const state = proxy({ dark: false, active: 0, rotation: 0, disabled: true }) |
| 12 | +const geometries = [ |
| 13 | + new THREE.SphereBufferGeometry(1, 32, 32), |
| 14 | + new THREE.TetrahedronBufferGeometry(1.5), |
| 15 | + new THREE.TorusBufferGeometry(1, 0.35, 16, 32), |
| 16 | + new THREE.OctahedronGeometry(1.5), |
| 17 | + new THREE.IcosahedronBufferGeometry(1.5), |
| 18 | +] |
| 19 | + |
| 20 | +function ToggleButton(props) { |
| 21 | + const a11y = useA11y() |
32 | 22 | return (
|
33 |
| - <a.mesh {...props} ref={mesh} scale={scale.scale}> |
34 |
| - <a.torusGeometry args={a11yContext.pressed ? [0.5, 1, 10, 20] : [1, 0.5, 10, 20]} /> |
35 |
| - <a.meshStandardMaterial color={color} /> |
36 |
| - </a.mesh> |
| 23 | + <mesh {...props}> |
| 24 | + <torusGeometry args={[0.5, a11y.pressed ? 0.28 : 0.25, 16, 32]} /> |
| 25 | + <meshStandardMaterial color={a11y.focus ? "lightsalmon" : a11y.hover ? "lightpink" : "lightblue"} /> |
| 26 | + </mesh> |
37 | 27 | )
|
38 | 28 | }
|
39 | 29 |
|
40 |
| -function Diamonds({ targetRotation, setTargetRotation, setActiveRupee, activeRupee }) { |
41 |
| - const group = useRef() |
42 |
| - const { nodes, materials } = useGLTF(diamond) |
43 |
| - const a11yContext = useA11y() |
44 |
| - |
| 30 | +function SwitchButton(props) { |
| 31 | + const a11y = useA11y() |
45 | 32 | return (
|
46 |
| - <group ref={group} dispose={null}> |
47 |
| - <A11y |
48 |
| - role="button" |
49 |
| - description="Press to show the left rupee" |
50 |
| - activationMsg="rupee showing" |
51 |
| - actionCall={() => { |
52 |
| - setActiveRupee(activeRupee === 1 ? 5 : activeRupee - 1) |
53 |
| - setTargetRotation(targetRotation - Math.PI / 2) |
54 |
| - }}> |
55 |
| - <Diamond geometry={nodes.Cylinder.geometry} position={[-10, 0, 0]} rotation={[0, Math.PI / 4, -Math.PI / 2]} /> |
56 |
| - </A11y> |
57 |
| - <A11y |
58 |
| - role="button" |
59 |
| - description="Press to show the right rupee" |
60 |
| - activationMsg="rupee showing" |
61 |
| - actionCall={() => { |
62 |
| - setActiveRupee(activeRupee === 5 ? 1 : activeRupee + 1) |
63 |
| - setTargetRotation(targetRotation + Math.PI / 2) |
64 |
| - }}> |
65 |
| - <Diamond geometry={nodes.Cylinder.geometry} position={[10, 0, 0]} rotation={[0, -Math.PI / 4, Math.PI / 2]} /> |
66 |
| - </A11y> |
67 |
| - </group> |
| 33 | + <> |
| 34 | + <mesh {...props} rotation={[0, 0, a11y.pressed ? Math.PI / 4 : -Math.PI / 4]}> |
| 35 | + <boxBufferGeometry args={[0.3, 2, 0.3]} /> |
| 36 | + <meshStandardMaterial color={a11y.focus ? "lightsalmon" : a11y.hover ? "lightpink" : "lightblue"} /> |
| 37 | + </mesh> |
| 38 | + <mesh {...props}> |
| 39 | + <torusGeometry args={[0.3, 0.2, 16, 20]} /> |
| 40 | + <meshStandardMaterial color={a11y.focus ? "lightsalmon" : a11y.hover ? "lightpink" : "lightblue"} /> |
| 41 | + </mesh> |
| 42 | + </> |
68 | 43 | )
|
69 | 44 | }
|
70 | 45 |
|
71 |
| -const Diamond = props => { |
72 |
| - const a11yContext = useA11y() |
73 |
| - |
| 46 | +function Floor(props) { |
74 | 47 | return (
|
75 |
| - <mesh {...props}> |
76 |
| - <meshStandardMaterial color={a11yContext.focus ? "#0fffcc" : a11yContext.hover ? "#71f8db" : "#87d4f7"} /> |
77 |
| - </mesh> |
| 48 | + <> |
| 49 | + <ContactShadows rotation-x={Math.PI / 2} position={[0, -5, 0]} opacity={0.4} width={30} height={30} blur={1} far={15} /> |
| 50 | + <mesh {...props} position={[0, -5.1, 0]} rotation={[-Math.PI / 2, 0, 0]}> |
| 51 | + <planeBufferGeometry args={[30, 30, 1]} /> |
| 52 | + <meshStandardMaterial color={"#eef5f7"} /> |
| 53 | + </mesh> |
| 54 | + </> |
78 | 55 | )
|
79 | 56 | }
|
80 | 57 |
|
81 |
| -const Rupee = props => { |
82 |
| - const rupeeRef = useRef() |
83 |
| - const a11yContext = useA11y() |
84 |
| - |
85 |
| - useFrame(() => { |
86 |
| - if (rupeeRef.current) { |
87 |
| - rupeeRef.current.rotation.y += 0.01 |
88 |
| - } |
89 |
| - }) |
| 58 | +function Nav({ left }) { |
| 59 | + const snap = useProxy(state) |
| 60 | + const { viewport } = useThree() |
| 61 | + const radius = Math.min(12, viewport.width / 2.5) |
| 62 | + return ( |
| 63 | + <A11y |
| 64 | + role="button" |
| 65 | + description={`Press to show the ${left ? "left" : "right"} shape`} |
| 66 | + activationMsg="shape showing" |
| 67 | + actionCall={() => { |
| 68 | + state.rotation = snap.rotation + ((Math.PI * 2) / 5) * (left ? -1 : 1) |
| 69 | + state.active = left ? (snap.active === 0 ? 4 : snap.active - 1) : snap.active === 4 ? 0 : snap.active + 1 |
| 70 | + }} |
| 71 | + disabled={snap.disabled}> |
| 72 | + <Diamond position={[left ? -radius : radius, 0, 0]} rotation={[0, 0, -Math.PI / 4]} /> |
| 73 | + </A11y> |
| 74 | + ) |
| 75 | +} |
90 | 76 |
|
| 77 | +function Diamond({ position, rotation }) { |
| 78 | + const a11y = useA11y() |
91 | 79 | return (
|
92 |
| - <mesh ref={rupeeRef} {...props} scale={a11yContext.focus ? [1.2, 1.2, 1.2] : [1, 1, 1]}> |
93 |
| - <meshStandardMaterial color={props.color} /> |
| 80 | + <mesh position={position} rotation={rotation}> |
| 81 | + <tetrahedronBufferGeometry /> |
| 82 | + <meshPhongMaterial color={a11y.focus ? "lightsalmon" : a11y.hover ? "lightpink" : "lightblue"} /> |
94 | 83 | </mesh>
|
95 | 84 | )
|
96 | 85 | }
|
97 | 86 |
|
98 |
| -function Rupees({ targetRotation, activeRupee }) { |
99 |
| - const group = useRef() |
100 |
| - const { nodes, materials } = useGLTF(rupee) |
101 |
| - |
102 |
| - useFrame(() => { |
103 |
| - if (group.current) { |
104 |
| - group.current.rotation.y = (1 - 0.1) * group.current.rotation.y + 0.1 * targetRotation |
| 87 | +function Shape({ index, active, ...props }) { |
| 88 | + const snap = useProxy(state) |
| 89 | + const vec = new THREE.Vector3() |
| 90 | + const ref = useRef() |
| 91 | + useFrame((state, delta) => { |
| 92 | + if (snap.disabled) { |
| 93 | + return |
105 | 94 | }
|
| 95 | + const s = active ? 2 : 1 |
| 96 | + ref.current.scale.lerp(vec.set(s, s, s), 0.1) |
| 97 | + ref.current.rotation.y = ref.current.rotation.x += delta / (active ? 1.5 : 4) |
| 98 | + ref.current.position.y = active ? Math.sin(state.clock.elapsedTime) / 2 : 0 |
106 | 99 | })
|
107 |
| - |
108 | 100 | return (
|
109 |
| - <group ref={group} dispose={null}> |
110 |
| - <A11y role="content" description="a red rupee" tabIndex={activeRupee === 1 ? 0 : -1}> |
111 |
| - <Rupee |
112 |
| - position={[Math.round(5 * Math.cos(0)), 0, Math.round(5 * Math.sin(0))]} |
113 |
| - material={materials.Material} |
114 |
| - color="red" |
115 |
| - geometry={nodes.Cube.geometry} |
116 |
| - /> |
117 |
| - </A11y> |
118 |
| - <A11y role="content" description="a green rupee" tabIndex={activeRupee === 2 ? 0 : -1}> |
119 |
| - <Rupee |
120 |
| - position={[Math.round(5 * Math.cos((1 * 2 * Math.PI) / 5)), 0, Math.round(5 * Math.sin((1 * 2 * Math.PI) / 5))]} |
121 |
| - material={materials.Material} |
122 |
| - color="green" |
123 |
| - geometry={nodes.Cube.geometry} |
124 |
| - /> |
125 |
| - </A11y> |
126 |
| - <A11y role="content" description="a silver rupee" tabIndex={activeRupee === 3 ? 0 : -1}> |
127 |
| - <Rupee |
128 |
| - position={[Math.round(5 * Math.cos((2 * 2 * Math.PI) / 5)), 0, Math.round(5 * Math.sin((2 * 2 * Math.PI) / 5))]} |
129 |
| - material={materials.Material} |
130 |
| - color="silver" |
131 |
| - geometry={nodes.Cube.geometry} |
132 |
| - /> |
133 |
| - </A11y> |
134 |
| - <A11y role="content" description="a purple rupee" tabIndex={activeRupee === 4 ? 0 : -1}> |
135 |
| - <Rupee |
136 |
| - position={[Math.round(5 * Math.cos((3 * 2 * Math.PI) / 5)), 0, Math.round(5 * Math.sin((3 * 2 * Math.PI) / 5))]} |
137 |
| - material={materials.Material} |
138 |
| - color="purple" |
139 |
| - geometry={nodes.Cube.geometry} |
140 |
| - /> |
141 |
| - </A11y> |
142 |
| - <A11y role="content" description="a yellow rupee" tabIndex={activeRupee === 5 ? 0 : -1}> |
143 |
| - <Rupee |
144 |
| - position={[Math.round(5 * Math.cos((4 * 2 * Math.PI) / 5)), 0, Math.round(5 * Math.sin((4 * 2 * Math.PI) / 5))]} |
145 |
| - material={materials.Material} |
146 |
| - color="yellow" |
147 |
| - geometry={nodes.Cube.geometry} |
148 |
| - /> |
149 |
| - </A11y> |
150 |
| - </group> |
| 101 | + <mesh rotation-y={index * 2000} ref={ref} {...props} geometry={geometries[index]}> |
| 102 | + <meshPhongMaterial /> |
| 103 | + </mesh> |
151 | 104 | )
|
152 | 105 | }
|
153 | 106 |
|
154 |
| -const Carroussel = () => { |
155 |
| - const [targetRotation, setTargetRotation] = useState(0) |
156 |
| - const [activeRupee, setActiveRupee] = useState(1) |
157 |
| - |
| 107 | +function Carroussel() { |
| 108 | + const { viewport } = useThree() |
| 109 | + const snap = useProxy(state) |
| 110 | + const group = useRef() |
| 111 | + const radius = Math.min(6, viewport.width / 5) |
| 112 | + useFrame(() => (group.current.rotation.y = THREE.MathUtils.lerp(group.current.rotation.y, snap.rotation - Math.PI / 2, 0.1))) |
158 | 113 | return (
|
159 |
| - <Suspense fallback={null}> |
160 |
| - <group position={[0, 0, 0]}> |
161 |
| - <Diamonds |
162 |
| - targetRotation={targetRotation} |
163 |
| - setTargetRotation={setTargetRotation} |
164 |
| - activeRupee={activeRupee} |
165 |
| - setActiveRupee={setActiveRupee} |
166 |
| - /> |
167 |
| - <Rupees targetRotation={targetRotation} activeRupee={activeRupee} /> |
168 |
| - |
169 |
| - <ContactShadows rotation-x={Math.PI / 2} position={[0, -10, 0]} opacity={0.25} width={100} height={100} blur={2} far={50} /> |
170 |
| - </group> |
171 |
| - </Suspense> |
| 114 | + <group ref={group}> |
| 115 | + {["sphere", "pyramid", "donut", "octahedron", "icosahedron"].map((name, i) => ( |
| 116 | + <A11y key={name} role="content" description={`a ${name}`} tabIndex={-1}> |
| 117 | + <Shape |
| 118 | + index={i} |
| 119 | + position={[radius * Math.cos(i * ((Math.PI * 2) / 5)), 0, radius * Math.sin(i * ((Math.PI * 2) / 5))]} |
| 120 | + active={snap.active === i} |
| 121 | + color={name} |
| 122 | + /> |
| 123 | + </A11y> |
| 124 | + ))} |
| 125 | + </group> |
172 | 126 | )
|
173 | 127 | }
|
174 | 128 |
|
175 |
| -useGLTF.preload(diamond) |
176 |
| -useGLTF.preload(rupee) |
177 |
| - |
178 |
| -const App = () => { |
179 |
| - const [darktheme, setDarktheme] = useState(false) |
180 |
| - const mainStyle = useSpring({ |
181 |
| - background: darktheme ? "#1c1c1c" : "#f4f4f4", |
182 |
| - }) |
183 |
| - |
| 129 | +export default function App() { |
| 130 | + const snap = useProxy(state) |
184 | 131 | return (
|
185 |
| - <animated.main style={mainStyle}> |
186 |
| - <Canvas resize={{ polyfill: ResizeObserver }} camera={{ position: [0, 0, 15] }} pixelRatio={[1, 2]}> |
187 |
| - <pointLight position={[100, 100, 100]} intensity={0.5} /> |
188 |
| - <hemisphereLight color="#ffffff" groundColor="#b9b9b9" position={[2, -25, 10]} intensity={0.85} /> |
189 |
| - <Carroussel /> |
190 |
| - <A11y |
191 |
| - role="button" |
192 |
| - description="Dark mode button theme" |
193 |
| - pressedDescription="Dark mode button theme, activated" |
194 |
| - actionCall={() => { |
195 |
| - setDarktheme(!darktheme) |
196 |
| - }} |
197 |
| - activationMsg="Theme Dark enabled" |
198 |
| - deactivationMsg="Theme Dark disabled"> |
199 |
| - <SimpleToggleButton position={[0, -8, 0]} /> |
200 |
| - </A11y> |
| 132 | + <main className={snap.dark ? "dark" : "bright"}> |
| 133 | + <Canvas resize={{ polyfill: ResizeObserver }} camera={{ position: [0, 0, 15], near: 4, far: 30 }} pixelRatio={[1, 1.5]}> |
| 134 | + <pointLight position={[100, 100, 100]} intensity={snap.disabled ? 0.2 : 0.5} /> |
| 135 | + <pointLight position={[-100, -100, -100]} intensity={1.5} color="red" /> |
| 136 | + <ambientLight intensity={snap.disabled ? 0.2 : 0.8} /> |
| 137 | + <group position-y={2}> |
| 138 | + <Nav left /> |
| 139 | + <Nav /> |
| 140 | + <Carroussel /> |
| 141 | + <Floor /> |
| 142 | + <A11y |
| 143 | + role="button" |
| 144 | + description="Light lowering button" |
| 145 | + pressedDescription="Light lowering button, activated" |
| 146 | + actionCall={() => (state.dark = !snap.dark)} |
| 147 | + activationMsg="Lower light enabled" |
| 148 | + deactivationMsg="Lower light disabled" |
| 149 | + disabled={snap.disabled} |
| 150 | + debug={true} |
| 151 | + a11yElStyle={{ marginLeft: "-40px" }}> |
| 152 | + <ToggleButton position={[0, -3, 9]} /> |
| 153 | + </A11y> |
| 154 | + <A11y |
| 155 | + role="button" |
| 156 | + pressed={true} |
| 157 | + description="Power button, click to disable the scene" |
| 158 | + pressedDescription="Power button, click to turn on the scene" |
| 159 | + actionCall={() => (state.disabled = !snap.disabled)} |
| 160 | + activationMsg="Scene activated" |
| 161 | + deactivationMsg="Scene disabled"> |
| 162 | + <SwitchButton position={[-3, -5, 7]} /> |
| 163 | + </A11y> |
| 164 | + </group> |
| 165 | + {/* <Suspense fallback={null}> |
| 166 | + <EffectComposer multisampling={0}> |
| 167 | + <SSAO radius={20} intensity={50} luminanceInfluence={0.1} color="#154073" /> |
| 168 | + <SMAA /> |
| 169 | + </EffectComposer> |
| 170 | + </Suspense> */} |
201 | 171 | </Canvas>
|
| 172 | + <Badge /> |
202 | 173 | <A11yAnnouncer />
|
203 |
| - </animated.main> |
| 174 | + </main> |
204 | 175 | )
|
205 | 176 | }
|
206 |
| - |
207 |
| -export default App |
0 commit comments