()
| 2624 | } |
| 2625 | |
| 2626 | buildConnections() { |
| 2627 | this.maxConnectionWeightMagnitude = 0; |
| 2628 | const connectionRadius = this.options.connectionRadius ?? 0.005; |
| 2629 | const baseGeometry = new THREE.CylinderGeometry(connectionRadius, connectionRadius, 1, 10, 1, true); |
| 2630 | const material = new THREE.MeshLambertMaterial(); |
| 2631 | // Do not set vertexColors explicitly; instancing color works independently |
| 2632 | |
| 2633 | this.mlp.layers.forEach((layer, layerIndex) => { |
| 2634 | const { selected, maxAbsWeight } = this.findImportantConnections(layer); |
| 2635 | if (Number.isFinite(maxAbsWeight) && maxAbsWeight > this.maxConnectionWeightMagnitude) { |
| 2636 | this.maxConnectionWeightMagnitude = maxAbsWeight; |
| 2637 | } |
| 2638 | if (!selected.length) return; |
| 2639 | |
| 2640 | // Clone geometry per mesh so instanceColor can be bound independently |
| 2641 | const mesh = new THREE.InstancedMesh(baseGeometry.clone(), material.clone(), selected.length); |
| 2642 | mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); |
| 2643 | const colorAttribute = new THREE.InstancedBufferAttribute(new Float32Array(selected.length * 3), 3); |
| 2644 | colorAttribute.setUsage(THREE.DynamicDrawUsage); |
| 2645 | mesh.instanceColor = colorAttribute; |
| 2646 | |
| 2647 | selected.forEach((connection, instanceIndex) => { |
| 2648 | const sourcePosition = this.layerMeshes[layerIndex].positions[connection.sourceIndex]; |
| 2649 | const targetPosition = this.layerMeshes[layerIndex + 1].positions[connection.targetIndex]; |
| 2650 | const direction = targetPosition.clone().sub(sourcePosition); |
| 2651 | const length = direction.length(); |
| 2652 | const midpoint = sourcePosition.clone().addScaledVector(direction, 0.5); |
| 2653 | |
| 2654 | this.tempObject.position.copy(midpoint); |
| 2655 | const quaternion = new THREE.Quaternion().setFromUnitVectors( |
| 2656 | new THREE.Vector3(0, 1, 0), |
| 2657 | direction.clone().normalize(), |
| 2658 | ); |
| 2659 | this.tempObject.scale.set(1, length, 1); |
| 2660 | this.tempObject.quaternion.copy(quaternion); |
| 2661 | this.tempObject.updateMatrix(); |
| 2662 | mesh.setMatrixAt(instanceIndex, this.tempObject.matrix); |
| 2663 | mesh.setColorAt(instanceIndex, this.tempColor.setRGB(1, 1, 1)); |
| 2664 | }); |
| 2665 | |
| 2666 | mesh.instanceMatrix.needsUpdate = true; |
| 2667 | mesh.instanceColor.needsUpdate = true; |
| 2668 | this.scene.add(mesh); |
| 2669 | this.connectionGroups.push({ |
| 2670 | mesh, |
| 2671 | connections: selected, |
| 2672 | sourceLayer: layerIndex, |
| 2673 | maxAbsWeight, |
| 2674 | }); |
| 2675 | }); |
| 2676 | } |
| 2677 | |
| 2678 | disposeConnectionMeshes() { |
| 2679 | this.connectionGroups.forEach((group) => { |
no test coverage detected