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.
198 lines
7.2 KiB
198 lines
7.2 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';
|
|
|
|
export class ItemManager {
|
|
constructor(filePath, 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._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._createLinkLines();
|
|
|
|
} catch (error) {
|
|
console.error("Could not load or process content JSON:", error);
|
|
}
|
|
}
|
|
|
|
async _processItemData(itemArray) {
|
|
for (const itemDef of itemArray) {
|
|
let contentObject = null;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Create the Item instance
|
|
const position = new THREE.Vector3(itemDef.position.x, itemDef.position.y, itemDef.position.z);
|
|
|
|
// The Item constructor expects a single number for scale, which matches your JSON.
|
|
const scale = itemDef.scale;
|
|
|
|
const newItem = new Item(
|
|
this.rapierWorld,
|
|
this.scene,
|
|
this.player,
|
|
itemDef.isCollider,
|
|
position,
|
|
scale,
|
|
itemDef.name,
|
|
itemDef.model,
|
|
itemDef.texture,
|
|
[], // spawnedObjects placeholder
|
|
contentObject
|
|
);
|
|
|
|
newItem.id = itemDef.id;
|
|
|
|
// Set links
|
|
if (itemDef.links) {
|
|
newItem.links = itemDef.links;
|
|
}
|
|
|
|
if (itemDef.isCollider) {
|
|
this.interactableItems.push(newItem);
|
|
}
|
|
|
|
// Setting a key and value pair in the Map (id, item)
|
|
this.itemData.set(itemDef.id, newItem);
|
|
}
|
|
}
|
|
|
|
_createLinkLines() {
|
|
|
|
this.itemData.forEach(item => {
|
|
if (!item.links) return;
|
|
|
|
item.links.forEach(linkedItemId => {
|
|
// Goes through each connection; links = [0, 1, 2..]
|
|
const linkedItem = this.itemData.get(linkedItemId);
|
|
if (!linkedItem) return;
|
|
|
|
// Creates a key for the Set; a Set can only have unique values, so used for no overlap
|
|
const key = [item.id, linkedItemId].sort().join('-');
|
|
|
|
if (this.drawnLinks.has(key)) return;
|
|
|
|
const material = new THREE.LineDashedMaterial({
|
|
color: 0x0000ff,
|
|
dashSize: 3,
|
|
gapSize: 1,
|
|
});
|
|
|
|
const sourcePos = item.spawnPosition.clone();
|
|
const terminalPos = linkedItem.spawnPosition.clone();
|
|
sourcePos.y = 1; terminalPos.y = 1;
|
|
|
|
const points = [item.spawnPosition.clone(), linkedItem.spawnPosition.clone()];
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
|
|
const line = new THREE.Line(geometry, material);
|
|
line.computeLineDistances();
|
|
|
|
this.scene.add(line);
|
|
|
|
// The Set is used as the key "0-1", then the item and the linkedItem are set as values in the Map?
|
|
this.linkLines.set(key, { line, item1: item, item2: linkedItem });
|
|
// Make sure it is not drawn again, adding the unique pair here
|
|
this.drawnLinks.add(key);
|
|
});
|
|
});
|
|
}
|
|
|
|
_updateLinkLines() {
|
|
this.linkLines.forEach(link => {
|
|
const { line, item1, item2 } = link;
|
|
|
|
// Only update/show the line if both items are loaded
|
|
if (item1.loadState === 'loaded' && item2.loadState === 'loaded') {
|
|
line.visible = true;
|
|
const positions = line.geometry.attributes.position;
|
|
positions.setXYZ(0, item1.object.position.x, 1, item1.object.position.z);
|
|
positions.setXYZ(1, item2.object.position.x, 1, item2.object.position.z);
|
|
positions.needsUpdate = true;
|
|
} else {
|
|
line.visible = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
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.loadState === 'unloaded') {
|
|
item.loadModel();
|
|
}
|
|
// Check if item should be unloaded
|
|
else if (distance > this.unloadDistance && item.loadState === 'loaded') {
|
|
item.unloadModel();
|
|
}
|
|
});
|
|
|
|
this._updateLinkLines();
|
|
}
|
|
}
|