177 lines
6.5 KiB
JavaScript
177 lines
6.5 KiB
JavaScript
import * as THREE from "three";
|
|
|
|
const CABLE_STYLE_TEXTURE_MAX_WIDTH = 1024;
|
|
|
|
// Keep the original Line/Sprite objects for picking and selection. Only their
|
|
// drawing is replaced: every segment and landing point remains in the batch.
|
|
function ownBatch(object, disposeExtra = () => {}) {
|
|
object.frustumCulled = false;
|
|
object.raycast = () => {};
|
|
return {
|
|
object,
|
|
dispose() {
|
|
object.parent?.remove(object);
|
|
object.geometry.dispose();
|
|
object.material.dispose();
|
|
disposeExtra();
|
|
},
|
|
};
|
|
}
|
|
|
|
export function createCableLineBatch(lines) {
|
|
if (!lines.length) return null;
|
|
const vertexCount = lines.reduce((count, line) =>
|
|
count + Math.max(0, line.geometry.attributes.position.count - 1) * 2, 0);
|
|
const positions = new Float32Array(vertexCount * 3);
|
|
const styleIndices = new Float32Array(vertexCount);
|
|
const width = Math.min(lines.length, CABLE_STYLE_TEXTURE_MAX_WIDTH);
|
|
const height = Math.ceil(lines.length / width);
|
|
const styles = new Float32Array(width * height * 4);
|
|
const styleTexture = new THREE.DataTexture(styles, width, height, THREE.RGBAFormat, THREE.FloatType);
|
|
styleTexture.needsUpdate = true;
|
|
let cursor = 0;
|
|
lines.forEach((line, index) => {
|
|
const source = line.geometry.attributes.position;
|
|
for (let segment = 0; segment < source.count - 1; segment += 1) {
|
|
for (const endpoint of [segment, segment + 1]) {
|
|
positions[cursor * 3] = source.getX(endpoint);
|
|
positions[cursor * 3 + 1] = source.getY(endpoint);
|
|
positions[cursor * 3 + 2] = source.getZ(endpoint);
|
|
styleIndices[cursor] = index;
|
|
cursor += 1;
|
|
}
|
|
}
|
|
line.material.visible = false;
|
|
line.updateMatrix();
|
|
line.matrixAutoUpdate = false;
|
|
});
|
|
const geometry = new THREE.BufferGeometry();
|
|
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
geometry.setAttribute("styleIndex", new THREE.BufferAttribute(styleIndices, 1));
|
|
const material = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
styles: { value: styleTexture },
|
|
styleSize: { value: new THREE.Vector2(width, height) },
|
|
},
|
|
transparent: true,
|
|
depthTest: true,
|
|
depthWrite: true,
|
|
vertexShader: `
|
|
uniform sampler2D styles;
|
|
uniform vec2 styleSize;
|
|
attribute float styleIndex;
|
|
varying vec4 vStyle;
|
|
void main() {
|
|
vec2 uv = (vec2(mod(styleIndex, styleSize.x), floor(styleIndex / styleSize.x)) + 0.5) / styleSize;
|
|
vStyle = texture2D(styles, uv);
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
}
|
|
`,
|
|
fragmentShader: `
|
|
varying vec4 vStyle;
|
|
void main() {
|
|
if (vStyle.a <= 0.0) discard;
|
|
gl_FragColor = vStyle;
|
|
}
|
|
`,
|
|
});
|
|
material.linewidth = lines[0].material.linewidth;
|
|
const object = new THREE.LineSegments(geometry, material);
|
|
object.name = "cable-line-batch";
|
|
object.renderOrder = lines[0].renderOrder;
|
|
object.onBeforeRender = () => {
|
|
let changed = false;
|
|
lines.forEach((line, index) => {
|
|
const { color, opacity } = line.material;
|
|
const offset = index * 4;
|
|
const alpha = line.visible ? opacity : 0;
|
|
if (styles[offset] !== Math.fround(color.r) || styles[offset + 1] !== Math.fround(color.g)
|
|
|| styles[offset + 2] !== Math.fround(color.b) || styles[offset + 3] !== Math.fround(alpha)) {
|
|
styles[offset] = color.r;
|
|
styles[offset + 1] = color.g;
|
|
styles[offset + 2] = color.b;
|
|
styles[offset + 3] = alpha;
|
|
changed = true;
|
|
}
|
|
});
|
|
if (changed) styleTexture.needsUpdate = true;
|
|
};
|
|
return ownBatch(object, () => styleTexture.dispose());
|
|
}
|
|
|
|
export function createLandingPointBatch(markers, texture) {
|
|
if (!markers.length) return null;
|
|
const geometry = new THREE.InstancedBufferGeometry();
|
|
const quad = new THREE.PlaneGeometry(1, 1);
|
|
geometry.index = quad.index;
|
|
geometry.attributes.position = quad.attributes.position;
|
|
geometry.attributes.uv = quad.attributes.uv;
|
|
const centers = new Float32Array(markers.length * 3);
|
|
const sizes = new THREE.InstancedBufferAttribute(new Float32Array(markers.length * 2), 2)
|
|
.setUsage(THREE.DynamicDrawUsage);
|
|
const styles = new THREE.InstancedBufferAttribute(new Float32Array(markers.length * 4), 4)
|
|
.setUsage(THREE.DynamicDrawUsage);
|
|
markers.forEach((marker, index) => {
|
|
marker.position.toArray(centers, index * 3);
|
|
marker.material.visible = false;
|
|
});
|
|
geometry.setAttribute("center", new THREE.InstancedBufferAttribute(centers, 3));
|
|
geometry.setAttribute("size", sizes);
|
|
geometry.setAttribute("style", styles);
|
|
geometry.instanceCount = markers.length;
|
|
const material = new THREE.ShaderMaterial({
|
|
uniforms: { map: { value: texture } },
|
|
transparent: true,
|
|
depthTest: false,
|
|
depthWrite: false,
|
|
vertexShader: `
|
|
attribute vec3 center;
|
|
attribute vec2 size;
|
|
attribute vec4 style;
|
|
varying vec2 vUv;
|
|
varying vec4 vStyle;
|
|
void main() {
|
|
vUv = uv;
|
|
vStyle = style;
|
|
vec2 scale = vec2(length(modelMatrix[0].xyz), length(modelMatrix[1].xyz));
|
|
vec4 viewPosition = modelViewMatrix * vec4(center, 1.0);
|
|
viewPosition.xy += position.xy * size * scale;
|
|
gl_Position = style.a > 0.0 ? projectionMatrix * viewPosition : vec4(2.0, 2.0, 2.0, 1.0);
|
|
}
|
|
`,
|
|
fragmentShader: `
|
|
uniform sampler2D map;
|
|
varying vec2 vUv;
|
|
varying vec4 vStyle;
|
|
void main() {
|
|
vec4 color = texture2D(map, vUv) * vStyle;
|
|
if (color.a < 0.01) discard;
|
|
gl_FragColor = color;
|
|
}
|
|
`,
|
|
});
|
|
const object = new THREE.Mesh(geometry, material);
|
|
object.name = "landing-point-batch";
|
|
object.renderOrder = markers[0].renderOrder;
|
|
object.onBeforeRender = () => {
|
|
let sizeChanged = false;
|
|
let styleChanged = false;
|
|
markers.forEach((marker, index) => {
|
|
const { color, opacity } = marker.material;
|
|
const alpha = marker.visible ? opacity : 0;
|
|
if (sizes.getX(index) !== Math.fround(marker.scale.x) || sizes.getY(index) !== Math.fround(marker.scale.y)) {
|
|
sizes.setXY(index, marker.scale.x, marker.scale.y);
|
|
sizeChanged = true;
|
|
}
|
|
if (styles.getX(index) !== Math.fround(color.r) || styles.getY(index) !== Math.fround(color.g)
|
|
|| styles.getZ(index) !== Math.fround(color.b) || styles.getW(index) !== Math.fround(alpha)) {
|
|
styles.setXYZW(index, color.r, color.g, color.b, alpha);
|
|
styleChanged = true;
|
|
}
|
|
});
|
|
if (sizeChanged) sizes.needsUpdate = true;
|
|
if (styleChanged) styles.needsUpdate = true;
|
|
};
|
|
return ownBatch(object);
|
|
}
|