Reading messages from DB + clicking to place new messages

This commit is contained in:
Silas Bartha 2025-02-10 16:50:19 -05:00
parent 55a0a3de65
commit 89b411863b
10 changed files with 143 additions and 69 deletions

2
api/.env Normal file
View File

@ -0,0 +1,2 @@
DB_URI=mongodb://127.0.0.1
DB_NAME=forum

View File

@ -1,8 +1,17 @@
import time
from flask import Flask
from flask import Flask, config
from dotenv import dotenv_values
from pymongo import MongoClient
from bson.json_util import dumps
config = dotenv_values(".env")
app = Flask(__name__)
@app.route('/api/time')
def get_current_time():
return {'time': time.time()}
app.mongoclient = MongoClient(config["DB_URI"])
app.db = app.mongoclient[config["DB_NAME"]]
print("Connected to MongoDB database")
@app.route('/api/message')
def get_messages():
messages = dumps(list(app.db["message"].find(limit=100)))
return messages

View File

@ -3,30 +3,32 @@ 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 React, { Suspense, useContext, useEffect, useRef, useState } from 'react';
import { Physics } from '@react-three/rapier';
import Sun from './components/sun';
import { Fog } from 'three';
import * as everforest from './_everforest.module.scss'
export const AppContext = React.createContext(null);
function KeyPressedClearer() {
const { setKeysPressed } = useContext(AppContext);
useFrame((_s, _d) => setKeysPressed([]));
const { keysPressed } = useContext(AppContext);
useFrame((_s, _d) => keysPressed.current = [], -1);
return <></>
}
function App() {
const [keys, setKeys] = useState([]);
const [keysPressed, setKeysPressed] = useState([]);
const keys = useRef([]);
const keysPressed = useRef([]);
const [messages, setMessages] = useState();
const playerKeyDown = (event) => {
setKeys(keys => {
if(!keys.includes(event.code)) {
setKeysPressed(keysPressed => [...keysPressed, event.code]);
}
return [...keys, event.code]
});
if (!keys.current.includes(event.code)) {
keysPressed.current.push(event.code);
}
keys.current.push(event.code);
}
const playerKeyUp = (event) => {
setKeys(keys => keys.filter(k => k != event.code));
keys.current = keys.current.filter(k => k != event.code);
}
useEffect(() => {
window.addEventListener('keydown', playerKeyDown);
@ -36,28 +38,34 @@ function App() {
window.removeEventListener('keyup', playerKeyUp);
};
}, []);
useEffect(() => {
fetch('/api/message').then((res) => res.json()).then((data) => {
console.log(data);
setMessages(data);
});
}, []);
return (
<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} />
<>
<div className='dot' />
<p className='unselectable hint'>
E - read<br/>
LMB - write
</p>
<AppContext.Provider value={{ keys: keys, keysPressed: keysPressed, messages: messages, setMessages: setMessages }}>
<Canvas shadows>
<KeyPressedClearer />
<Suspense>
<fog color={everforest.bg0} attach="fog" far={500}/>
<Sun />
<Notes />
<Player />
<Ground />
</Physics>
</Suspense>
</Canvas>
</AppContext.Provider>
<Physics timeStep={1 / 60}>
<Player />
<Ground />
</Physics>
</Suspense>
</Canvas>
</AppContext.Provider>
</>
)
}

View File

@ -1,7 +1,7 @@
import { useContext, useRef, useState } from 'react'
import * as everforest from '../_everforest.module.scss'
import { useGLTF } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber';
import { useFrame } from '@react-three/fiber';
import { Html } from '@react-three/drei';
import { AppContext } from '../App';
import Color from 'color';
@ -13,25 +13,29 @@ export default function ChatBubble({ position, text }) {
const [activatable, setActivatable] = useState(false);
const [active, setActive] = useState(false);
const { keysPressed } = useContext(AppContext);
const { camera } = useThree();
useFrame((_, delta) => {
useFrame((state, delta) => {
if (active) {
meshRef.current.rotation.y += delta;
let cameraPos = new Vector3();
state.camera.getWorldPosition(cameraPos);
if (cameraPos.distanceToSquared(meshRef.current.position) > 900) {
setActive(false);
}
}
if(hovered) {
let cameraPos = new Vector3();
camera.getWorldPosition(cameraPos);
setActivatable(cameraPos.distanceToSquared(meshRef.current.position) < 9);
state.camera.getWorldPosition(cameraPos);
setActivatable(cameraPos.distanceToSquared(meshRef.current.position) < 25);
} else {
setActivatable(false);
}
if (keysPressed.includes('KeyE') && activatable) {
if (keysPressed.current.includes('KeyE') && activatable) {
setActive(!active);
}
});
}, -2);
const { nodes } = useGLTF('../assets/message-bubble.glb');
let color = Color(active ? everforest.blue : everforest.orange);
let color = Color(active ? everforest.blue : everforest.purple);
if (activatable) {
color = color.lighten(.1);
}
@ -41,10 +45,9 @@ export default function ChatBubble({ position, text }) {
ref={meshRef}
scale={active ? 3 : 2}
geometry={nodes.Curve.geometry}
castShadow
onPointerOver={(_) => setHovered(true)}
onPointerOut={(_) => setHovered(false)}>
<meshStandardMaterial color={color.toString()} />
<meshStandardMaterial color={color.toString()}/>
{active && <Html center position={[0, .5, 0]} className='unselectable textPopup'><span>{text}</span></Html>}
</mesh>
)

View File

@ -1,16 +1,16 @@
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 ref={meshRef} position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} geometry={nodes.Plane.geometry} name='ground'>
<meshStandardMaterial color={everforest.yellow}/>
</mesh>
</RigidBody>
);

View File

@ -1,14 +1,12 @@
import { useContext } from "react";
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?"},
];
import { AppContext } from "../App";
export default function Notes() {
const { messages } = useContext(AppContext);
return (<>
{chatbubbles.map((chatbubble, index) =>
<ChatBubble key={index} position={chatbubble.position} text={chatbubble.text}/>
{messages.map((chatbubble, index) =>
<ChatBubble key={index} position={chatbubble.position} text={chatbubble.message}/>
)}
</>);
}

View File

@ -1,29 +1,47 @@
import { Capsule, PerspectiveCamera, PointerLockControls } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { PerspectiveCamera, PointerLockControls } from "@react-three/drei";
import { useFrame, useThree } 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 { CapsuleCollider, RigidBody, useRapier, vec3 } from "@react-three/rapier";
import { Vector3 } from "three";
import { useBeforePhysicsStep } from "@react-three/rapier";
import { Raycaster } from "three";
const _movespeed = 3.0;
export default function Player() {
const controlsRef = useRef();
const { keys } = useContext(AppContext);
const { keys, setMessages } = useContext(AppContext);
const rapier = useRapier();
const controller = useRef();
const collider = useRef();
const rigidbody = useRef();
const camera = useRef();
const raycaster = useRef(new Raycaster());
const { scene, pointer } = useThree();
const refState = useRef({
grounded: false,
jumping: false,
velocity: vec3(),
});
const onClick = (_) => {
raycaster.current.setFromCamera(pointer, camera.current);
const intersect = raycaster.current.intersectObjects(scene.children).at(0);
if (intersect && intersect.object.name === 'ground' && intersect.distance < 10 && controlsRef.current.isLocked) {
const message = prompt();
if (message) {
setMessages(messages => [...messages, { position: intersect.point, message: message}]);
}
controlsRef.current.lock();
}
};
useEffect(() => {
window.addEventListener("click", onClick);
return () => window.removeEventListener("click", onClick);
}, []);
useEffect(() => {
const c = rapier.world.createCharacterController(0.1);
c.setApplyImpulsesToDynamicBodies(true);
@ -31,10 +49,18 @@ export default function Player() {
controller.current = c;
}, [rapier]);
useFrame((_, delta) => {
const fov_axis = +(keys.current.includes('Minus')) - +(keys.current.includes('Equal'));
if (fov_axis != 0) {
camera.current.fov += 45 * fov_axis * delta;
camera.current.updateProjectionMatrix();
}
});
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 move_axis_x = +(keys.current.includes('KeyD')) - +(keys.current.includes('KeyA'));
const move_axis_z = +(keys.current.includes('KeyW')) - +(keys.current.includes('KeyS'));
const { velocity } = refState.current;
const position = vec3(rigidbody.current.translation());
@ -69,7 +95,7 @@ export default function Player() {
return (
<RigidBody type="kinematicPosition" colliders={false} ref={rigidbody} position={[0, 2, 0]}>
<PerspectiveCamera makeDefault position={[0, .9, 0]} fov={90} ref={camera} />
<PerspectiveCamera makeDefault position={[0, .9, 0]} ref={camera} fov={80}/>
<PointerLockControls ref={controlsRef} />
<CapsuleCollider ref={collider} args={[1, 0.5]} />
</RigidBody>

18
src/components/sun.jsx Normal file
View File

@ -0,0 +1,18 @@
import { Matrix4 } from 'three';
import * as everforest from '../_everforest.module.scss'
export default function Sun() {
return (<>
<directionalLight
position={[1000, 1000, 1000]}
lookAt={[0, 0, 0]}
intensity={Math.PI / 2}
color={everforest.red}
/>
<ambientLight intensity={Math.PI / 4} />
<mesh position={[1000,1000,1000]}>
<icosahedronGeometry args={[100,100]}/>
<meshStandardMaterial color={everforest.red} emissive={everforest.red} emissiveIntensity={1} fog={false}/>
</mesh>
</>);
}

View File

@ -5,6 +5,5 @@ import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
<div className='dot'/>
</StrictMode>,
)

View File

@ -31,7 +31,17 @@
height: 5px;
border-radius: 50%;
transform: translate3d(-50%, -50%, 0);
border: 2px solid white;
border: 2px solid everforest.$fg;
z-index: 999999;
}
.hint {
position: fixed;
bottom: 0%;
left: 0%;
color: everforest.$bg2;
z-index: 999999;
margin: 5px;
}
html,
@ -47,6 +57,7 @@ h1 {
html {
background-color: everforest.$bg0;
color: everforest.$fg;
height: 100%;
}