diff --git a/assets/terrain.glb b/assets/terrain.glb
new file mode 100644
index 0000000..4ffde3c
Binary files /dev/null and b/assets/terrain.glb differ
diff --git a/package.json b/package.json
index 3c4945c..94e77cb 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,10 @@
"dependencies": {
"@react-three/drei": "^9.121.4",
"@react-three/fiber": "^9.0.0-rc.7",
+ "@react-three/rapier": "^1.5.0",
"@types/three": "^0.173.0",
+ "color": "^4.2.3",
+ "lodash.debounce": "^4.0.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sass": "^1.84.0",
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
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 (
-
+
+
+
)
}
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 (
setActive(!active)}
geometry={nodes.Curve.geometry}
+ castShadow
onPointerOver={(_) => setHovered(true)}
onPointerOut={(_) => setHovered(false)}>
-
+
+ {active && {text}}
)
}
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 (
+
+
+
+
+
+ );
+}
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) =>
+
+ )}
+ >);
+}
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 (
+
+
+
+
+
+ );
+}
+
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(
+
,
)
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;
+}
diff --git a/yarn.lock b/yarn.lock
index 8ca67f3..da6f679 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -171,6 +171,11 @@
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
+"@dimforge/rapier3d-compat@0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.14.0.tgz#c6148f743aa99de320231527c466c1a48ada4c9f"
+ integrity sha512-/uHrUzS+CRQ+NQrrJCEDUkhwHlNsAAexbNXgbN9sHY+GwR+SFFAFrxRr8Llf5/AJZzqiLANdQIfJ63Cw4gJVqw==
+
"@esbuild/aix-ppc64@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461"
@@ -615,6 +620,15 @@
suspend-react "^0.1.3"
zustand "^4.1.2"
+"@react-three/rapier@^1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@react-three/rapier/-/rapier-1.5.0.tgz#f6af5b1dd6895a73df0d09e15576eed1f0398379"
+ integrity sha512-gylk2KyCer9EoymFyTyc+g2IqyAq4mTbZgaHoSJi6gHoXlJsC2LVeN4jedvegvjUsXPExdE60wHjCPa+DS4iXw==
+ dependencies:
+ "@dimforge/rapier3d-compat" "0.14.0"
+ suspend-react "^0.1.3"
+ three-stdlib "^2.29.4"
+
"@rollup/rollup-android-arm-eabi@4.34.6":
version "4.34.6"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz#9b726b4dcafb9332991e9ca49d54bafc71d9d87f"
@@ -1073,11 +1087,27 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
-color-name@~1.1.4:
+color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+color-string@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
+ integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
+ dependencies:
+ color-name "^1.0.0"
+ simple-swizzle "^0.2.2"
+
+color@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
+ integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
+ dependencies:
+ color-convert "^2.0.1"
+ color-string "^1.9.0"
+
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -1763,6 +1793,11 @@ is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
call-bound "^1.0.3"
get-intrinsic "^1.2.6"
+is-arrayish@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+ integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
+
is-async-function@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523"
@@ -2041,6 +2076,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
+lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -2545,6 +2585,13 @@ side-channel@^1.1.0:
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
+simple-swizzle@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+ integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
+ dependencies:
+ is-arrayish "^0.3.1"
+
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -2649,7 +2696,7 @@ three-mesh-bvh@^0.7.8:
resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz#83156e4d3945734db076de1c94809331481b3fdd"
integrity sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==
-three-stdlib@^2.35.6:
+three-stdlib@^2.29.4, three-stdlib@^2.35.6:
version "2.35.13"
resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.35.13.tgz#477fc5ffdef8a8923395ae2d7f26b0aeb6d60689"
integrity sha512-AbXVObkM0OFCKX0r4VmHguGTdebiUQA+Yl+4VNta1wC158gwY86tCkjp2LFfmABtjYJhdK6aP13wlLtxZyLMAA==