aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.jsx64
-rw-r--r--src/components/chatbubble.jsx42
-rw-r--r--src/components/ground.jsx17
-rw-r--r--src/components/notes.jsx14
-rw-r--r--src/components/player.jsx78
-rw-r--r--src/main.jsx1
-rw-r--r--src/style.scss41
7 files changed, 232 insertions, 25 deletions
diff --git a/src/App.jsx b/src/App.jsx
index ebd5d36..25fe834 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,15 +1,63 @@
import './style.scss'
-import { Canvas } from '@react-three/fiber';
-import ChatBubble from './components/chatbubble';
+import { Canvas, useFrame } from '@react-three/fiber';
+import Notes from './components/notes';
+import Player from './components/player';
+import Ground from './components/ground';
+import React, { Suspense, useContext, useEffect, useState } from 'react';
+import { Physics } from '@react-three/rapier';
+
+export const AppContext = React.createContext(null);
+
+function KeyPressedClearer() {
+ const { setKeysPressed } = useContext(AppContext);
+ useFrame((_s, _d) => setKeysPressed([]));
+ return <></>
+}
function App() {
+ const [keys, setKeys] = useState([]);
+ const [keysPressed, setKeysPressed] = useState([]);
+ const playerKeyDown = (event) => {
+ setKeys(keys => {
+ if(!keys.includes(event.code)) {
+ setKeysPressed(keysPressed => [...keysPressed, event.code]);
+ }
+ return [...keys, event.code]
+ });
+ }
+ const playerKeyUp = (event) => {
+ setKeys(keys => keys.filter(k => k != event.code));
+ }
+ useEffect(() => {
+ window.addEventListener('keydown', playerKeyDown);
+ window.addEventListener('keyup', playerKeyUp);
+ return () => {
+ window.removeEventListener('keydown', playerKeyDown);
+ window.removeEventListener('keyup', playerKeyUp);
+ };
+ }, []);
return (
- <Canvas>
- <ambientLight intensity={Math.PI / 2} />
- <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />
- <pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />
- <ChatBubble position={[0, 0, 0]} />
- </Canvas>
+ <AppContext.Provider value={{ keys: keys, keysPressed: keysPressed, setKeysPressed: setKeysPressed }}>
+ <Canvas shadows>
+ <KeyPressedClearer />
+ <Suspense>
+ <Physics timeStep={1/60}>
+ <directionalLight
+ castShadow
+ position={[100, 100, 100]}
+ lookAt={[0, 0, 0]}
+ intensity={Math.PI / 2}
+ shadow-mapSize-height={2048}
+ shadow-mapSize-width={2048}
+ />
+ <ambientLight intensity={Math.PI / 4} />
+ <Notes />
+ <Player />
+ <Ground />
+ </Physics>
+ </Suspense>
+ </Canvas>
+ </AppContext.Provider>
)
}
diff --git a/src/components/chatbubble.jsx b/src/components/chatbubble.jsx
index b3f4ad5..a1ef114 100644
--- a/src/components/chatbubble.jsx
+++ b/src/components/chatbubble.jsx
@@ -1,25 +1,51 @@
-import { useRef, useState } from 'react'
+import { useContext, useRef, useState } from 'react'
import * as everforest from '../_everforest.module.scss'
import { useGLTF } from '@react-three/drei';
-import { useFrame } from '@react-three/fiber';
+import { useFrame, useThree } from '@react-three/fiber';
+import { Html } from '@react-three/drei';
+import { AppContext } from '../App';
+import Color from 'color';
+import { Vector3 } from 'three';
-export default function ChatBubble(props) {
+export default function ChatBubble({ position, text }) {
const meshRef = useRef();
const [hovered, setHovered] = useState(false);
+ const [activatable, setActivatable] = useState(false);
const [active, setActive] = useState(false);
- useFrame((_, delta) => (meshRef.current.rotation.y += delta));
- const {nodes} = useGLTF('../assets/message-bubble.glb');
+ const { keysPressed } = useContext(AppContext);
+ const { camera } = useThree();
+ useFrame((_, delta) => {
+ if (active) {
+ meshRef.current.rotation.y += delta;
+ }
+ if(hovered) {
+ let cameraPos = new Vector3();
+ camera.getWorldPosition(cameraPos);
+ setActivatable(cameraPos.distanceToSquared(meshRef.current.position) < 9);
+ } else {
+ setActivatable(false);
+ }
+ if (keysPressed.includes('KeyE') && activatable) {
+ setActive(!active);
+ }
+ });
+ const { nodes } = useGLTF('../assets/message-bubble.glb');
+ let color = Color(active ? everforest.blue : everforest.orange);
+ if (activatable) {
+ color = color.lighten(.1);
+ }
return (
<mesh
- {...props}
+ position={position}
ref={meshRef}
scale={active ? 3 : 2}
- onClick={(_) => setActive(!active)}
geometry={nodes.Curve.geometry}
+ castShadow
onPointerOver={(_) => setHovered(true)}
onPointerOut={(_) => setHovered(false)}>
- <meshStandardMaterial color={active ? everforest.blue : everforest.orange} />
+ <meshStandardMaterial color={color.toString()} />
+ {active && <Html center position={[0, .5, 0]} className='unselectable textPopup'><span>{text}</span></Html>}
</mesh>
)
}
diff --git a/src/components/ground.jsx b/src/components/ground.jsx
new file mode 100644
index 0000000..4042538
--- /dev/null
+++ b/src/components/ground.jsx
@@ -0,0 +1,17 @@
+import { useRef } from 'react';
+import * as everforest from '../_everforest.module.scss'
+import { RigidBody } from '@react-three/rapier';
+import { DoubleSide } from 'three';
+import { useGLTF } from '@react-three/drei';
+
+export default function Ground() {
+ const meshRef = useRef();
+ const { nodes } = useGLTF('../assets/terrain.glb');
+ return (
+ <RigidBody type='fixed' colliders="trimesh">
+ <mesh ref={meshRef} position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow geometry={nodes.Plane.geometry}>
+ <meshStandardMaterial color={everforest.yellow} side={DoubleSide}/>
+ </mesh>
+ </RigidBody>
+ );
+}
diff --git a/src/components/notes.jsx b/src/components/notes.jsx
new file mode 100644
index 0000000..0dd267a
--- /dev/null
+++ b/src/components/notes.jsx
@@ -0,0 +1,14 @@
+import ChatBubble from "./chatbubble";
+
+const chatbubbles = [
+ {position: [0,0,-3], text: "ugh. really struggling with double bleeds in my ankles. makes it hard to do very much of anything, let alone focus for my hobbies"},
+ {position: [-1,0,-5], text: "reddit is everywhere on google and i am sick of it... why can't there be a good forum site?"},
+];
+
+export default function Notes() {
+ return (<>
+ {chatbubbles.map((chatbubble, index) =>
+ <ChatBubble key={index} position={chatbubble.position} text={chatbubble.text}/>
+ )}
+ </>);
+}
diff --git a/src/components/player.jsx b/src/components/player.jsx
new file mode 100644
index 0000000..bd8e392
--- /dev/null
+++ b/src/components/player.jsx
@@ -0,0 +1,78 @@
+import { Capsule, PerspectiveCamera, PointerLockControls } from "@react-three/drei";
+import { useFrame } from "@react-three/fiber";
+import { useContext, useEffect, useRef } from "react";
+import { AppContext } from "../App";
+import { CapsuleCollider, RapierCollider, RapierRigidBody, RigidBody, useRapier, vec3 } from "@react-three/rapier";
+import { quat } from "@react-three/rapier";
+import { Euler, Object3D, Vector3 } from "three";
+import { useBeforePhysicsStep } from "@react-three/rapier";
+
+const _movespeed = 3.0;
+
+export default function Player() {
+ const controlsRef = useRef();
+ const { keys } = useContext(AppContext);
+ const rapier = useRapier();
+ const controller = useRef();
+ const collider = useRef();
+ const rigidbody = useRef();
+ const camera = useRef();
+
+ const refState = useRef({
+ grounded: false,
+ jumping: false,
+ velocity: vec3(),
+ });
+
+ useEffect(() => {
+ const c = rapier.world.createCharacterController(0.1);
+ c.setApplyImpulsesToDynamicBodies(true);
+ c.setCharacterMass(0.2);
+ controller.current = c;
+ }, [rapier]);
+
+ useBeforePhysicsStep((world) => {
+ if (controller.current && rigidbody.current && collider.current) {
+ const move_axis_x = +(keys.includes('KeyD')) - +(keys.includes('KeyA'));
+ const move_axis_z = +(keys.includes('KeyW')) - +(keys.includes('KeyS'));
+
+ const { velocity } = refState.current;
+ const position = vec3(rigidbody.current.translation());
+ const movement = vec3();
+
+ const forward = new Vector3();
+ camera.current.getWorldDirection(forward);
+ const left = new Vector3().crossVectors(forward, camera.current.up);
+
+ movement.x += move_axis_z * world.timestep * _movespeed * forward.x;
+ movement.z += move_axis_z * world.timestep * _movespeed * forward.z;
+ movement.x += move_axis_x * world.timestep * _movespeed * left.x;
+ movement.z += move_axis_x * world.timestep * _movespeed * left.z;
+
+ if (refState.current.grounded) {
+ velocity.y = 0;
+ } else {
+ velocity.y -= 9.81 * world.timestep * world.timestep;
+ }
+
+ movement.add(velocity);
+
+ controller.current.computeColliderMovement(collider.current, movement);
+ refState.current.grounded = controller.current.computedGrounded();
+
+ let correctedMovement = controller.current.computedMovement();
+ position.add(vec3(correctedMovement));
+
+ rigidbody.current.setNextKinematicTranslation(position);
+ }
+ });
+
+ return (
+ <RigidBody type="kinematicPosition" colliders={false} ref={rigidbody} position={[0, 2, 0]}>
+ <PerspectiveCamera makeDefault position={[0, .9, 0]} fov={90} ref={camera} />
+ <PointerLockControls ref={controlsRef} />
+ <CapsuleCollider ref={collider} args={[1, 0.5]} />
+ </RigidBody>
+ );
+}
+
diff --git a/src/main.jsx b/src/main.jsx
index 3d9da8a..97bc548 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -5,5 +5,6 @@ import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
+ <div className='dot'/>
</StrictMode>,
)
diff --git a/src/style.scss b/src/style.scss
index 1bc4d97..23c3679 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -23,6 +23,17 @@
/// }}}
+.dot {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ transform: translate3d(-50%, -50%, 0);
+ border: 2px solid white;
+}
+
html,
body,
#root {
@@ -39,15 +50,6 @@ html {
height: 100%;
}
-/* body { */
-/* color: everforest.$fg; */
-/* background-color: everforest.$bg1; */
-/* margin: 0 auto; */
-/* max-width: 800px; */
-/* padding: 10px; */
-/* min-height: 100%; */
-/* } */
-
a {
color: everforest.$blue;
}
@@ -93,3 +95,24 @@ pre table {
color: everforest.$orange;
}
}
+
+.textPopup {
+ /* text-align: center; */
+ pointer-events: none;
+ background-color: everforest.$bg1;
+ color: everforest.$fg;
+ border-radius: 5px;
+ font-size: 15px;
+ width: 200px;
+ max-width: 50vw;
+ padding: 5px;
+}
+
+.unselectable {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}