You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
10 KiB
281 lines
10 KiB
import * as THREE from 'three';
|
|
import { Player } from './Player';
|
|
import { Item } from './Item';
|
|
import { TextContent, ImageContent, VideoContent, AudioContent } from './Content';
|
|
import { GeometryUtils } from 'three/examples/jsm/Addons.js';
|
|
import { Line2 } from 'three/examples/jsm/lines/Line2.js';
|
|
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
|
|
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
|
|
|
|
|
|
export class ItemManager {
|
|
constructor(filePath, preloadedObjects, dynamicGroups, scene, rapierWorld, player, interactableItems) {
|
|
this.filePath = filePath;
|
|
this.scene = scene;
|
|
this.rapierWorld = rapierWorld;
|
|
this.player = player,
|
|
this.interactableItems = interactableItems;
|
|
this.audioListener = player.audioListener;
|
|
this.itemData = new Map();
|
|
this.loadDistance = 250;
|
|
this.unloadDistance = 300;
|
|
this.linkLines = new Map();
|
|
this.drawnLinks = new Set();
|
|
this.preloadedObjects = preloadedObjects;
|
|
this.dynamicGroups = dynamicGroups;
|
|
this.groupCentres = new Map();
|
|
this.groupCenterLinks = new Map();
|
|
this.interGroupLinks = new Map();
|
|
this._init();
|
|
}
|
|
|
|
async _init() {
|
|
await this._loadFromJSON();
|
|
}
|
|
|
|
async _loadFromJSON() {
|
|
try {
|
|
const resp = await fetch(this.filePath);
|
|
if (!resp.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await resp.json();
|
|
await this._processItemData(data.items);
|
|
this._createGroupCenterLinks();
|
|
|
|
if (data.groupLinks) this._createInterGroupLinks(data.groupLinks);
|
|
|
|
} catch (error) {
|
|
console.error("Could not load or process content JSON:", error);
|
|
}
|
|
}
|
|
|
|
async _processItemData(itemArray) {
|
|
for (const itemDef of itemArray) {
|
|
let contentObject = null;
|
|
|
|
const preloadedObject = this.preloadedObjects.get(itemDef.name);
|
|
|
|
if (!preloadedObject) {
|
|
console.warn(`Could not find a preloaded 3D object for item name: "${itemDef.name}"`);
|
|
continue;
|
|
}
|
|
|
|
// Find the correct THREE.Group for this item
|
|
const parentGroup = this.dynamicGroups.get(itemDef.groupId);
|
|
if (!parentGroup) {
|
|
console.warn(`Could not find a group for groupId: "${itemDef.groupId}"`);
|
|
continue;
|
|
}
|
|
|
|
// Create content object based on contentType and file
|
|
if (itemDef.contentType && itemDef.file) {
|
|
switch (itemDef.contentType) {
|
|
case 'text':
|
|
// Fetch the text content from the specified file
|
|
try {
|
|
const textResponse = await fetch(itemDef.file);
|
|
if (!textResponse.ok) throw new Error(`Failed to fetch text: ${textResponse.status}`);
|
|
const textData = await textResponse.text();
|
|
contentObject = new TextContent(textData);
|
|
} catch (e) {
|
|
console.error(`Could not load text content for item "${itemDef.name}" from ${itemDef.file}`, e);
|
|
}
|
|
break;
|
|
case 'image':
|
|
contentObject = new ImageContent(itemDef.file);
|
|
break;
|
|
case 'video':
|
|
contentObject = new VideoContent(itemDef.file);
|
|
break;
|
|
case 'audio':
|
|
if (!this.audioListener) {
|
|
console.error("AudioListener not provided. Cannot create AudioContent.");
|
|
continue; // Skip this item
|
|
}
|
|
contentObject = new AudioContent(itemDef.file, this.audioListener);
|
|
break;
|
|
default:
|
|
console.warn(`Unknown content type: ${itemDef.contentType}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const newItem = new Item(
|
|
this.rapierWorld,
|
|
parentGroup,
|
|
this.player,
|
|
true,
|
|
preloadedObject.position,
|
|
1,
|
|
itemDef.name,
|
|
null,
|
|
null,
|
|
[], // spawnedObjects placeholder
|
|
contentObject,
|
|
preloadedObject
|
|
);
|
|
|
|
// Set item id and group id
|
|
newItem.id = itemDef.id;
|
|
newItem.groupId = parentGroup.name;
|
|
|
|
// Item is already loaded from world glb, but this func creates collider etc.
|
|
await newItem.loadModel();
|
|
newItem.show();
|
|
|
|
this.interactableItems.push(newItem);
|
|
|
|
// Setting a key and value pair in the Map (id, item)
|
|
this.itemData.set(itemDef.id, newItem);
|
|
}
|
|
}
|
|
|
|
_createGroupCenterLinks() {
|
|
this.itemData.forEach(item => {
|
|
const groupCenter = this._getGroupCentre(item.groupId);
|
|
if (!groupCenter) return;
|
|
|
|
const material = new THREE.LineBasicMaterial({
|
|
color: 0xFF0000,
|
|
transparent: false,
|
|
opacity: 1
|
|
});
|
|
|
|
const points = [groupCenter.clone(), item.spawnPosition.clone()];
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
|
|
const line = new THREE.Line(geometry, material);
|
|
line.visible = false; // Initially hidden
|
|
|
|
this.scene.add(line);
|
|
|
|
// Store the line and the group it belongs to, keyed by the item's ID
|
|
this.groupCenterLinks.set(item.id, { line, groupId: item.groupId });
|
|
});
|
|
}
|
|
|
|
_createInterGroupLinks(groupLinks) {
|
|
groupLinks.forEach(linkPair => {
|
|
const [groupA_ID, groupB_ID] = linkPair;
|
|
|
|
const centerA = this._getGroupCentre(groupA_ID);
|
|
const centerB = this._getGroupCentre(groupB_ID);
|
|
|
|
if (!centerA || !centerB) {
|
|
console.warn(`Could not find centers for group link: ${groupA_ID} to ${groupB_ID}`);
|
|
return;
|
|
}
|
|
|
|
const lineGeometry = new LineGeometry();
|
|
lineGeometry.setPositions([
|
|
centerA.x, centerA.y, centerA.z,
|
|
centerB.x, centerB.y, centerB.z
|
|
]);
|
|
|
|
const lineMaterial = new LineMaterial({
|
|
color: 0xffa500, // Orange color for distinction
|
|
linewidth: 2, // in pixels
|
|
resolution: new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
fog:true
|
|
});
|
|
|
|
const line = new Line2(lineGeometry, lineMaterial);
|
|
this.scene.add(line);
|
|
|
|
const linkKey = `${groupA_ID}-${groupB_ID}`;
|
|
this.interGroupLinks.set(linkKey, { line, groupA_ID, groupB_ID });
|
|
});
|
|
}
|
|
|
|
_getGroupCentre(groupId) {
|
|
const group = this.dynamicGroups.get(groupId);
|
|
if (!group) {
|
|
console.warn(`Group with id ${groupId} not found`);
|
|
return null;
|
|
}
|
|
|
|
const bbox = new THREE.Box3().setFromObject(group);
|
|
const centre = new THREE.Vector3();
|
|
bbox.getCenter(centre);
|
|
|
|
return centre;
|
|
}
|
|
|
|
_updateGroupCentres() {
|
|
this.dynamicGroups.forEach((group, groupId) => {
|
|
const center = this._getGroupCentre(groupId);
|
|
if (center) {
|
|
let helper = this.groupCentres.get(groupId);
|
|
if (!helper) {
|
|
helper = new THREE.AxesHelper(3); // The number defines the size of the helper
|
|
this.scene.add(helper);
|
|
this.groupCentres.set(groupId, helper);
|
|
}
|
|
helper.position.copy(center);
|
|
}
|
|
});
|
|
}
|
|
|
|
_updateGroupCenterLinks() {
|
|
this.groupCenterLinks.forEach((linkData, itemId) => {
|
|
const { line, groupId } = linkData;
|
|
const item = this.itemData.get(itemId);
|
|
|
|
if (item && item.isVisible) {
|
|
const groupCenter = this._getGroupCentre(groupId);
|
|
if (!groupCenter) return;
|
|
|
|
line.visible = true;
|
|
const positions = line.geometry.attributes.position;
|
|
positions.setXYZ(0, groupCenter.x, groupCenter.y, groupCenter.z);
|
|
positions.setXYZ(1, item.object.position.x, item.object.position.y, item.object.position.z);
|
|
positions.needsUpdate = true;
|
|
} else {
|
|
line.visible = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
_updateInterGroupLinks() {
|
|
this.interGroupLinks.forEach(link => {
|
|
const centerA = this._getGroupCentre(link.groupA_ID);
|
|
const centerB = this._getGroupCentre(link.groupB_ID);
|
|
|
|
if (centerA && centerB) {
|
|
link.line.geometry.setPositions([
|
|
centerA.x, centerA.y, centerA.z,
|
|
centerB.x, centerB.y, centerB.z
|
|
]);
|
|
}
|
|
});
|
|
}
|
|
|
|
update() {
|
|
const playerPosition = this.player.camera.position;
|
|
this.itemData.forEach(item => {
|
|
let distance;
|
|
|
|
if (item.lastPosition === null) {
|
|
const initPositon = item.spawnPosition;
|
|
distance = initPositon.distanceTo(playerPosition);
|
|
} else {
|
|
distance = item.lastPosition.distanceTo(playerPosition);
|
|
}
|
|
|
|
// Check if item should be loaded
|
|
if (distance < this.loadDistance && !item.isVisible) {
|
|
item.show();
|
|
}
|
|
// Check if item should be unloaded
|
|
else if (distance > this.unloadDistance && item.isVisible) {
|
|
item.hide();
|
|
}
|
|
});
|
|
|
|
this._updateGroupCenterLinks();
|
|
this._updateGroupCentres();
|
|
this._updateInterGroupLinks();
|
|
}
|
|
}
|