Co-authored-by: Claude (Opus 4.6) <claude@hermes.local> Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
This commit was merged in pull request #464.
This commit is contained in:
@@ -51,10 +51,8 @@ const GLASS_TILE_STEP = GLASS_TILE_SIZE + GLASS_TILE_GAP;
|
||||
export const GLASS_RADIUS = 4.55;
|
||||
|
||||
const tileGeo = new THREE.PlaneGeometry(GLASS_TILE_SIZE, GLASS_TILE_SIZE);
|
||||
const tileEdgeGeo = new THREE.EdgesGeometry(tileGeo);
|
||||
|
||||
/** @type {Array<{mat: THREE.LineBasicMaterial, distFromCenter: number}>} */
|
||||
export const glassEdgeMaterials = [];
|
||||
export const glassEdgeMaterials = []; // kept for API compat; no longer populated
|
||||
|
||||
const _tileDummy = new THREE.Object3D();
|
||||
/** @type {Array<{x: number, z: number, distFromCenter: number}>} */
|
||||
@@ -81,14 +79,26 @@ for (let i = 0; i < _tileSlots.length; i++) {
|
||||
glassTileIM.instanceMatrix.needsUpdate = true;
|
||||
glassPlatformGroup.add(glassTileIM);
|
||||
|
||||
for (const { x, z, distFromCenter } of _tileSlots) {
|
||||
const mat = glassEdgeBaseMat.clone();
|
||||
const edges = new THREE.LineSegments(tileEdgeGeo, mat);
|
||||
edges.rotation.x = -Math.PI / 2;
|
||||
edges.position.set(x, 0.002, z);
|
||||
glassPlatformGroup.add(edges);
|
||||
glassEdgeMaterials.push({ mat, distFromCenter });
|
||||
// Merge all tile edge geometry into a single LineSegments draw call.
|
||||
// Each tile contributes 4 edges (8 vertices). Previously this was 69 separate
|
||||
// LineSegments objects with cloned materials — a significant draw-call overhead.
|
||||
const _HS = GLASS_TILE_SIZE / 2;
|
||||
const _edgeVerts = new Float32Array(_tileSlots.length * 8 * 3);
|
||||
let _evi = 0;
|
||||
for (const { x, z } of _tileSlots) {
|
||||
const y = 0.002;
|
||||
_edgeVerts[_evi++]=x-_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z-_HS;
|
||||
_edgeVerts[_evi++]=x+_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z-_HS;
|
||||
_edgeVerts[_evi++]=x+_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z-_HS;
|
||||
_edgeVerts[_evi++]=x+_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z+_HS;
|
||||
_edgeVerts[_evi++]=x+_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z+_HS;
|
||||
_edgeVerts[_evi++]=x-_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z+_HS;
|
||||
_edgeVerts[_evi++]=x-_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z+_HS;
|
||||
_edgeVerts[_evi++]=x-_HS; _edgeVerts[_evi++]=y; _edgeVerts[_evi++]=z-_HS;
|
||||
}
|
||||
const _mergedEdgeGeo = new THREE.BufferGeometry();
|
||||
_mergedEdgeGeo.setAttribute('position', new THREE.BufferAttribute(_edgeVerts, 3));
|
||||
glassPlatformGroup.add(new THREE.LineSegments(_mergedEdgeGeo, glassEdgeBaseMat));
|
||||
|
||||
export const voidLight = new THREE.PointLight(NEXUS.colors.accent, 0.5, 14);
|
||||
voidLight.position.set(0, -3.5, 0);
|
||||
|
||||
@@ -10,32 +10,57 @@ scene.add(portalGroup);
|
||||
|
||||
export let portals = [];
|
||||
|
||||
// Shared geometry and material for all portal tori — populated in createPortals()
|
||||
const _portalGeo = new THREE.TorusGeometry(3.0, 0.2, 16, 100);
|
||||
const _portalMat = new THREE.MeshBasicMaterial({
|
||||
color: 0xffffff, // instance color provides per-portal tint
|
||||
transparent: true,
|
||||
opacity: 1.0, // online/offline brightness encoded into instance color
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
});
|
||||
const _portalDummy = new THREE.Object3D();
|
||||
const _portalColor = new THREE.Color();
|
||||
|
||||
/** @type {THREE.InstancedMesh|null} */
|
||||
let _portalIM = null;
|
||||
|
||||
function createPortals() {
|
||||
const portalGeo = new THREE.TorusGeometry(3.0, 0.2, 16, 100);
|
||||
// One InstancedMesh for all portal tori — N portals = 1 draw call.
|
||||
_portalIM = new THREE.InstancedMesh(_portalGeo, _portalMat, portals.length);
|
||||
_portalIM.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
|
||||
_portalIM.userData.zoomLabel = 'Portal';
|
||||
_portalIM.userData.portals = portals; // for instanceId look-up on click
|
||||
|
||||
portals.forEach(portal => {
|
||||
portals.forEach((portal, i) => {
|
||||
const isOnline = portal.status === 'online';
|
||||
// Encode online/offline brightness into the instance color so we need
|
||||
// only one shared material (AdditiveBlending: output = bg + color).
|
||||
_portalColor.set(portal.color).convertSRGBToLinear()
|
||||
.multiplyScalar(isOnline ? 0.7 : 0.15);
|
||||
_portalIM.setColorAt(i, _portalColor);
|
||||
|
||||
const portalMat = new THREE.MeshBasicMaterial({
|
||||
color: new THREE.Color(portal.color).convertSRGBToLinear(),
|
||||
transparent: true,
|
||||
opacity: isOnline ? 0.7 : 0.15,
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const portalMesh = new THREE.Mesh(portalGeo, portalMat);
|
||||
|
||||
portalMesh.position.set(portal.position.x, portal.position.y + 0.5, portal.position.z);
|
||||
portalMesh.rotation.y = portal.rotation.y;
|
||||
portalMesh.rotation.x = Math.PI / 2;
|
||||
|
||||
portalMesh.name = `portal-${portal.id}`;
|
||||
portalMesh.userData.destinationUrl = portal.destination?.url || null;
|
||||
portalMesh.userData.portalColor = new THREE.Color(portal.color).convertSRGBToLinear();
|
||||
|
||||
portalGroup.add(portalMesh);
|
||||
_portalDummy.position.set(portal.position.x, portal.position.y + 0.5, portal.position.z);
|
||||
_portalDummy.rotation.set(Math.PI / 2, portal.rotation.y || 0, 0);
|
||||
_portalDummy.updateMatrix();
|
||||
_portalIM.setMatrixAt(i, _portalDummy.matrix);
|
||||
});
|
||||
|
||||
_portalIM.instanceColor.needsUpdate = true;
|
||||
_portalIM.instanceMatrix.needsUpdate = true;
|
||||
portalGroup.add(_portalIM);
|
||||
}
|
||||
|
||||
/** Update per-instance colors after a portal health check. */
|
||||
export function refreshPortalInstanceColors() {
|
||||
if (!_portalIM) return;
|
||||
portals.forEach((portal, i) => {
|
||||
const brightness = portal.status === 'online' ? 0.7 : 0.15;
|
||||
_portalColor.set(portal.color).convertSRGBToLinear().multiplyScalar(brightness);
|
||||
_portalIM.setColorAt(i, _portalColor);
|
||||
});
|
||||
_portalIM.instanceColor.needsUpdate = true;
|
||||
}
|
||||
|
||||
// rebuildGravityZones forward ref
|
||||
|
||||
@@ -4,6 +4,7 @@ import { scene, ambientLight } from './scene-setup.js';
|
||||
import { cloudMaterial } from './platform.js';
|
||||
import { rebuildRuneRing } from './effects.js';
|
||||
import { S } from './state.js';
|
||||
import { refreshPortalInstanceColors } from './portals.js';
|
||||
|
||||
// === PORTAL HEALTH CHECKS ===
|
||||
const PORTAL_HEALTH_CHECK_MS = 5 * 60 * 1000;
|
||||
@@ -41,16 +42,8 @@ export async function runPortalHealthChecks() {
|
||||
rebuildRuneRing();
|
||||
if (_rebuildGravityZonesFn) _rebuildGravityZonesFn();
|
||||
|
||||
if (_portalGroupRef) {
|
||||
for (const child of _portalGroupRef.children) {
|
||||
const portalId = child.name.replace('portal-', '');
|
||||
const portalData = _portalsRef.find(p => p.id === portalId);
|
||||
if (portalData) {
|
||||
const isOnline = portalData.status === 'online';
|
||||
child.material.opacity = isOnline ? 0.7 : 0.15;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Refresh portal InstancedMesh colors to reflect new online/offline statuses.
|
||||
refreshPortalInstanceColors();
|
||||
}
|
||||
|
||||
export function initPortalHealthChecks() {
|
||||
|
||||
Reference in New Issue
Block a user