|
|
@ -3,9 +3,13 @@ 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, scene, rapierWorld, player, interactableItems) { |
|
|
|
constructor(filePath, preloadedObjects, dynamicGroups, scene, rapierWorld, player, interactableItems) { |
|
|
|
this.filePath = filePath; |
|
|
|
this.scene = scene; |
|
|
|
this.rapierWorld = rapierWorld; |
|
|
@ -17,6 +21,11 @@ export class ItemManager { |
|
|
|
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(); |
|
|
|
} |
|
|
|
|
|
|
@ -33,7 +42,9 @@ export class ItemManager { |
|
|
|
|
|
|
|
const data = await resp.json(); |
|
|
|
await this._processItemData(data.items); |
|
|
|
this._createLinkLines(); |
|
|
|
this._createGroupCenterLinks(); |
|
|
|
|
|
|
|
if (data.groupLinks) this._createInterGroupLinks(data.groupLinks); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error("Could not load or process content JSON:", error); |
|
|
@ -44,6 +55,20 @@ export class ItemManager { |
|
|
|
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) { |
|
|
@ -77,97 +102,140 @@ export class ItemManager { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 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, |
|
|
|
parentGroup, |
|
|
|
this.player, |
|
|
|
itemDef.isCollider, |
|
|
|
position, |
|
|
|
scale, |
|
|
|
true, |
|
|
|
preloadedObject.position, |
|
|
|
1, |
|
|
|
itemDef.name, |
|
|
|
itemDef.model, |
|
|
|
itemDef.texture, |
|
|
|
null, |
|
|
|
null, |
|
|
|
[], // spawnedObjects placeholder
|
|
|
|
contentObject |
|
|
|
contentObject, |
|
|
|
preloadedObject |
|
|
|
); |
|
|
|
|
|
|
|
// Set item id and group id
|
|
|
|
newItem.id = itemDef.id; |
|
|
|
newItem.groupId = itemDef.groupId; |
|
|
|
newItem.groupId = parentGroup.name; |
|
|
|
|
|
|
|
// Set links
|
|
|
|
if (itemDef.links) { |
|
|
|
newItem.links = itemDef.links; |
|
|
|
} |
|
|
|
|
|
|
|
if (itemDef.isCollider) { |
|
|
|
// 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); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
_createLinkLines() { |
|
|
|
|
|
|
|
_createGroupCenterLinks() { |
|
|
|
this.itemData.forEach(item => { |
|
|
|
if (!item.links) return; |
|
|
|
const groupCenter = this._getGroupCentre(item.groupId); |
|
|
|
if (!groupCenter) return; |
|
|
|
|
|
|
|
item.links.forEach(linkedItemId => { |
|
|
|
// Goes through each connection; links = [0, 1, 2..]
|
|
|
|
const linkedItem = this.itemData.get(linkedItemId); |
|
|
|
if (!linkedItem) return; |
|
|
|
const material = new THREE.LineBasicMaterial({ |
|
|
|
color: 0xFF0000, |
|
|
|
transparent: false, |
|
|
|
opacity: 1 |
|
|
|
}); |
|
|
|
|
|
|
|
if (item.groupId !== linkedItem.groupId) return; |
|
|
|
const points = [groupCenter.clone(), item.spawnPosition.clone()]; |
|
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(points); |
|
|
|
|
|
|
|
// 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('-'); |
|
|
|
const line = new THREE.Line(geometry, material); |
|
|
|
line.visible = false; // Initially hidden
|
|
|
|
|
|
|
|
if (this.drawnLinks.has(key)) return; |
|
|
|
this.scene.add(line); |
|
|
|
|
|
|
|
const material = new THREE.LineDashedMaterial({ |
|
|
|
color: 0x0000ff, |
|
|
|
dashSize: 3, |
|
|
|
gapSize: 1, |
|
|
|
// Store the line and the group it belongs to, keyed by the item's ID
|
|
|
|
this.groupCenterLinks.set(item.id, { line, groupId: item.groupId }); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
const sourcePos = item.spawnPosition.clone(); |
|
|
|
const terminalPos = linkedItem.spawnPosition.clone(); |
|
|
|
sourcePos.y = 1; terminalPos.y = 1; |
|
|
|
_createInterGroupLinks(groupLinks) { |
|
|
|
groupLinks.forEach(linkPair => { |
|
|
|
const [groupA_ID, groupB_ID] = linkPair; |
|
|
|
|
|
|
|
const points = [item.spawnPosition.clone(), linkedItem.spawnPosition.clone()]; |
|
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(points); |
|
|
|
const centerA = this._getGroupCentre(groupA_ID); |
|
|
|
const centerB = this._getGroupCentre(groupB_ID); |
|
|
|
|
|
|
|
const line = new THREE.Line(geometry, material); |
|
|
|
line.computeLineDistances(); |
|
|
|
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); |
|
|
|
|
|
|
|
// 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); |
|
|
|
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); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
_updateLinkLines() { |
|
|
|
this.linkLines.forEach(link => { |
|
|
|
const { line, item1, item2 } = link; |
|
|
|
_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; |
|
|
|
|
|
|
|
// 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.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; |
|
|
@ -175,6 +243,20 @@ export class ItemManager { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
_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 => { |
|
|
@ -188,15 +270,78 @@ export class ItemManager { |
|
|
|
} |
|
|
|
|
|
|
|
// Check if item should be loaded
|
|
|
|
if (distance < this.loadDistance && item.loadState === 'unloaded') { |
|
|
|
item.loadModel(); |
|
|
|
if (distance < this.loadDistance && !item.isVisible) { |
|
|
|
item.show(); |
|
|
|
} |
|
|
|
// Check if item should be unloaded
|
|
|
|
else if (distance > this.unloadDistance && item.loadState === 'loaded') { |
|
|
|
item.unloadModel(); |
|
|
|
else if (distance > this.unloadDistance && item.isVisible) { |
|
|
|
item.hide(); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
this._updateLinkLines(); |
|
|
|
this._updateGroupCenterLinks(); |
|
|
|
this._updateGroupCentres(); |
|
|
|
this._updateInterGroupLinks(); |
|
|
|
} |
|
|
|
|
|
|
|
// _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;
|
|
|
|
|
|
|
|
// if (item.groupId !== linkedItem.groupId) 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: 0xffa500,
|
|
|
|
// dashSize: 10,
|
|
|
|
// 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')
|
|
|
|
// && (item1.isVisible && item2.isVisible)) {
|
|
|
|
// 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;
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
} |