Browse Source

server sync drawing working!

master
Cailean Finn 2 weeks ago
parent
commit
1d8d88895e
  1. 1
      data/drawings.json
  2. 3
      js/ModelLoader.js
  3. 19
      js/Player.js
  4. 235
      js/main.js
  5. 55
      server.js

1
data/drawings.json

File diff suppressed because one or more lines are too long

3
js/ModelLoader.js

@ -16,6 +16,9 @@ export class ModelLoader {
_applyCustomMaterial(object) {
let texture = null;
const objectId = object.name || `obj_${object.position.x}_${object.position.y}_${object.position.z}`;
object.userData.drawingId = objectId; // Store consistent ID
if (object.material && object.material.map) {
texture = object.material.map;
}

19
js/Player.js

@ -3,7 +3,7 @@ import { RigidBodyDesc, ColliderDesc } from '@dimforge/rapier3d-compat';
import { AudioContent, VideoContent } from './Content';
export class Player {
constructor(rapierWorld, renderer, scene, spawnPosition = new THREE.Vector3(0, 1, 0), itemList) {
constructor(rapierWorld, renderer, scene, spawnPosition = new THREE.Vector3(0, 1, 0), itemList, socket) {
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.position = spawnPosition.clone();
this.rotation = new THREE.Euler(0, 0, 0, 'YXZ');
@ -58,6 +58,8 @@ export class Player {
this.minTeleportDistanceFactor = 0.1; // Minimum distance multiplier
this.maxTeleportDistanceFactor = 1.5; // Maximum distance multiplier
this.socket = socket;
this._init();
this._setupInput();
this._bindEvents();
@ -398,6 +400,7 @@ export class Player {
_drawOnTexture(intersect, color = 'red') {
const object = intersect.object;
const objectId = object.userData.drawingId;
const uv = intersect.uv;
const texture = object.material.map;
const canvas = texture.image;
@ -437,6 +440,20 @@ export class Player {
context.fill();
texture.needsUpdate = true;
// Emit drawing data to server
if (this.socket && this.socket.connected) {
this.socket.emit('drawingUpdate', {
objectId: objectId,
drawData: {
x: x,
y: y,
radius: Math.max(1, pixelBrushRadius),
color: color
}
});
}
}
draw(drawableObjects) {

235
js/main.js

@ -30,6 +30,9 @@ let floorGridSize = new THREE.Vector2(10, 200);
const worldScale = 20.0;
let socket;
let pendingDrawings = null;
let drawings = {};
const currentPlayers = {};
let lastPlayerCount = 0;
@ -80,13 +83,13 @@ async function init() {
document.body.appendChild( renderer.domElement );
document.body.appendChild( VRButton.createButton( renderer ) );
socket = setupSocketIO();
player = new Player(rapierWorld, renderer, scene, new THREE.Vector3(0, 1, 0), interactableItems);
player = new Player(rapierWorld, renderer, scene, new THREE.Vector3(0, 1, 0), interactableItems, socket);
player._setupVR(renderer);
scene.add(player.playerRig);;
setupSocketIO();
/* Load Items/Content */
loadWorldModel("models/demo-world-comp.glb");
@ -117,6 +120,7 @@ async function init() {
}, 10);
}
});
window.addEventListener( 'resize', onWindowResize );
window.addEventListener('keydown', function(event) {
if(event.key === 'Escape') {
@ -130,7 +134,7 @@ async function init() {
}
});
console.log(scene)
//console.log(scene)
}
async function animate() {
@ -171,10 +175,11 @@ async function animate() {
if( lastPlayerCount != numberOfPlayers) {
lastPlayerCount = numberOfPlayers;
console.log(lastPlayerCount);
console.log("Player Count:", lastPlayerCount);
}
stats.update();
}
//-- Other functions --//
@ -235,8 +240,10 @@ function onWindowResize() {
function setupSocketIO() {
socket = io();
socket.on('connect', () => {
console.log('Connected to server with ID:', socket.id);
socket.on('connect', async () => {
console.log('Client connected:', socket.id);
console.log('Sending initial drawings:', drawings);
socket.emit('initialDrawings', drawings);
});
socket.on('currentPlayers', (players) => {
@ -266,6 +273,84 @@ function setupSocketIO() {
playerMesh.rotation.set(playerData.rotation.x, playerData.rotation.y, playerData.rotation.z);
}
});
socket.on('initialDrawings', (serverDrawings) => {
console.log("Received initial drawings:", serverDrawings);
drawings = serverDrawings; // Store the drawings
pendingDrawings = drawings;
});
socket.on('drawingUpdated', (data) => {
// Look for matching object in spawnedObjects instead of scene
let object = spawnedObjects.find(obj =>
obj.mesh.userData && obj.mesh.userData.drawingId === data.objectId
)?.mesh;
if (object && object.material && object.material.map) {
const texture = object.material.map;
const canvas = texture.image;
const context = canvas.getContext('2d');
context.fillStyle = data.drawData.color;
context.beginPath();
context.arc(
data.drawData.x,
data.drawData.y,
data.drawData.radius,
0,
2 * Math.PI
);
context.fill();
texture.needsUpdate = true;
}
});
return socket;
}
function applyDrawings(drawings) {
console.log("Applying drawings:", drawings);
Object.entries(drawings).forEach(([objectId, drawingArray]) => {
console.log(`Processing object ${objectId} with ${drawingArray.length} drawings`);
// Look for matching object in spawnedObjects instead of scene
let object = spawnedObjects.find(obj =>
obj.mesh.userData && obj.mesh.userData.drawingId === objectId
)?.mesh;
if (object && object.material && object.material.map) {
//console.log("Found matching spawned object:", objectId);
const texture = object.material.map;
const canvas = texture.image;
const context = canvas.getContext('2d');
drawingArray.forEach(drawData => {
//console.log("Drawing:", drawData);
context.fillStyle = drawData.color || '#ffffff';
context.beginPath();
context.arc(
drawData.x,
drawData.y,
drawData.radius || 5,
0,
2 * Math.PI
);
context.fill();
});
texture.needsUpdate = true;
} else {
console.warn("Could not find or invalid object for ID:", objectId);
if (object) {
console.log("Object details:", {
hasMaterial: !!object.material,
hasMap: !!(object.material && object.material.map)
});
}
}
});
}
function addOtherPlayer(playerData) {
@ -332,6 +417,11 @@ function createTiledFloor(gridSize = 20, tileSize = 50) {
const floorTile = new THREE.Mesh(floorGeometry, floorMaterial);
floorTile.position.set(i * tileSize + tileSize / 2, 1, j * tileSize + tileSize / 2);
// Add unique drawing ID to the floor tile
const drawingId = `floor_${i}_${j}`;
floorTile.userData.drawingId = drawingId;
scene.add(floorTile);
// Make it drawable
@ -351,64 +441,75 @@ function createTiledFloor(gridSize = 20, tileSize = 50) {
}
async function loadWorldModel(modelUrl) {
const modelLoader = new ModelLoader();
const staticWorldGroup = new THREE.Group();
scene.add(staticWorldGroup);
try {
const staticObjects = await modelLoader.loadStaticWorld(modelUrl, worldScale);
staticObjects.forEach(object => {
const item = new Item(
rapierWorld,
staticWorldGroup,
player,
true, // isCollider
object.position,
1, // Scale is already applied to the object
object.name || 'static-world-object',
null, // model URL (not needed)
null, // texture URL (not needed)
spawnedObjects,
null, // content
object // preloadedObject
);
item.loadModel();
});
console.log("Finished loading world");
} catch (error) {
console.error("Failed to load static world objects:", error);
}
try {
const dynamicGroups = await modelLoader.loadDynamicGroups(modelUrl, worldScale);
const preloadedObjectsMap = new Map();
const dynamicGroupsMap = new Map();
dynamicGroups.forEach(groupData => {
const threeGroup = new THREE.Group();
threeGroup.name = groupData.name;
scene.add(threeGroup);
dynamicGroupsMap.set(groupData.name, threeGroup);
groupData.objects.forEach(object => {
preloadedObjectsMap.set(object.name, object);
});
});
console.log(dynamicGroupsMap);
itemManager = new ItemManager(
"json/Items.json",
preloadedObjectsMap,
dynamicGroupsMap,
scene,
rapierWorld,
player,
interactableItems);
} catch (error) {
console.error("Failed to load dynamic world objects:", error);
}
const modelLoader = new ModelLoader();
const staticWorldGroup = new THREE.Group();
scene.add(staticWorldGroup);
try {
// Load and add static objects
const staticObjects = await modelLoader.loadStaticWorld(modelUrl, worldScale);
const staticLoadPromises = staticObjects.map(object => {
return new Promise((resolve) => {
const item = new Item(
rapierWorld,
staticWorldGroup,
player,
true,
object.position,
1,
object.name || 'static-world-object',
null,
null,
spawnedObjects,
null,
object
);
item.loadModel().then(() => resolve());
});
});
// Wait for all static objects to be loaded
await Promise.all(staticLoadPromises);
console.log("Finished loading static world");
// Load dynamic objects
const dynamicGroups = await modelLoader.loadDynamicGroups(modelUrl, worldScale);
const preloadedObjectsMap = new Map();
const dynamicGroupsMap = new Map();
dynamicGroups.forEach(groupData => {
const threeGroup = new THREE.Group();
threeGroup.name = groupData.name;
scene.add(threeGroup);
dynamicGroupsMap.set(groupData.name, threeGroup);
groupData.objects.forEach(object => {
preloadedObjectsMap.set(object.name, object);
});
});
itemManager = new ItemManager(
"json/Items.json",
preloadedObjectsMap,
dynamicGroupsMap,
scene,
rapierWorld,
player,
interactableItems
);
// Wait a frame to ensure all objects are properly initialized
await new Promise(resolve => setTimeout(resolve, 100));
// Now apply any pending drawings after everything is loaded
if (pendingDrawings) {
console.log("All objects loaded, applying pending drawings");
console.log("Current spawnedObjects count:", spawnedObjects.length);
applyDrawings(pendingDrawings);
pendingDrawings = null;
}
} catch (error) {
console.error("Failed to load world:", error);
}
}

55
server.js

@ -1,6 +1,8 @@
const express = require('express');
const http = require('http');
const { Server } = require("socket.io");
const fs = require('fs').promises;
const path = require('path');
const app = express();
const server = http.createServer(app);
@ -9,7 +11,40 @@ const io = new Server(server);
// Serve your existing static files (HTML, JS, CSS)
app.use(express.static(__dirname));
// File path for persistent storage
const DRAWINGS_FILE = path.join(__dirname, 'data', 'drawings.json');
const RATE_LIMIT_MS = 50; // Minimum time between draws
const lastDrawTime = {};
async function loadDrawings() {
try {
await fs.mkdir(path.join(__dirname, 'data'), { recursive: true });
const data = await fs.readFile(DRAWINGS_FILE, 'utf8');
drawings = JSON.parse(data);
console.log('Loaded drawings:', Object.keys(drawings).length);
} catch (error) {
if (error.code !== 'ENOENT') {
console.error('Error loading drawings:', error);
}
drawings = {};
console.log('No existing drawings found, starting fresh');
}
}
// Save drawings to file
async function saveDrawings() {
try {
await fs.writeFile(DRAWINGS_FILE, JSON.stringify(drawings));
} catch (error) {
console.error('Error saving drawings:', error);
}
}
// Initialize drawings from file
loadDrawings();
let players = {};
let drawings = {};
io.on('connection', (socket) => {
console.log('A user connected:', socket.id);
@ -44,6 +79,26 @@ io.on('connection', (socket) => {
socket.broadcast.emit('playerMoved', players[socket.id]);
}
});
// Send existing drawings to new players
socket.emit('initialDrawings', drawings);
socket.on('drawingUpdate', async (drawData) => {
const { objectId, drawData: data } = drawData;
if (!drawings[objectId]) {
drawings[objectId] = [];
}
drawings[objectId].push(data);
await saveDrawings();
socket.broadcast.emit('drawingUpdated', {
objectId: objectId,
drawData: data
});
});
});
const PORT = process.env.PORT || 3000;

Loading…
Cancel
Save