import * as THREE from 'three/webgpu';
import { Lines } from './lines';
import { LineSegments2 } from '../three/line/LineSegments2';
//import { LineMaterial } from '../three/line/LineMaterial';
import { LineSegmentsGeometry } from '../three/line/LineSegmentsGeometry';
/**
* Provides an object which stores lines.
* This is created when a valid json file containing lines is read into a {@link Zinc.Scene}
* object.
*
* @class
* @author Alan Wu
* @return {Lines}
*/
const Lines2 = function () {
Lines.call(this);
this.isLines2 = true;
//Shared with the GPU buffer of the mesh, edits are made in place
let positions = new Float32Array(300);
//Grow the positions array if required, returns true if it has
//been reallocated.
const ensureCapacity = (length) => {
if (length > positions.length) {
//Each segment has two vertices, six values
const capacity = Math.ceil(Math.max(positions.length * 2, length) / 6) * 6;
const newPositions = new Float32Array(capacity);
newPositions.set(positions);
positions = newPositions;
return true;
}
return false;
};
//Fill the unused part of the array with degenerate segments at coord
const fillUnused = (start, coord) => {
for (let index = start; index + 2 < positions.length; index += 3) {
positions[index] = coord[0];
positions[index + 1] = coord[1];
positions[index + 2] = coord[2];
}
};
//Update the line distances without allocating new buffers
const updateLineDistances = (mesh) => {
const geometry = mesh.geometry;
const distanceStart = geometry.attributes.instanceDistanceStart;
const instanceStart = geometry.attributes.instanceStart;
if (!distanceStart || distanceStart.data.array.length !== instanceStart.count * 2) {
mesh.computeLineDistances();
return;
}
const distances = distanceStart.data.array;
let total = 0;
for (let i = 0; i < instanceStart.count; i++) {
const offset = i * 6;
const dx = positions[offset + 3] - positions[offset];
const dy = positions[offset + 4] - positions[offset + 1];
const dz = positions[offset + 5] - positions[offset + 2];
distances[i * 2] = total;
total += Math.sqrt(dx * dx + dy * dy + dz * dz);
distances[i * 2 + 1] = total;
}
distanceStart.data.needsUpdate = true;
};
//Upload the positions after they have been changed
const updatePositions = (mesh, reallocated) => {
const instanceStart = mesh.geometry.getAttribute('instanceStart');
if (reallocated || !instanceStart || instanceStart.data.array !== positions) {
//Only happens when the capacity has been increased
mesh.geometry.setPositions(positions);
mesh.computeLineDistances();
} else {
instanceStart.data.needsUpdate = true;
updateLineDistances(mesh);
mesh.geometry.computeBoundingBox();
mesh.geometry.computeBoundingSphere();
}
this.boundingBoxUpdateRequired = true;
};
/**
* Create the line segements using geometry and material.
*
* @param {Array} arrayIn - Geometry of lines to be rendered.
* @param {THREE.Material} materialIn - Material to be set for the lines.
* @param {Object} options - Provide various options
* @param {Boolean} options.localTimeEnabled - A flag to indicate either the lines is
* time dependent.
* @param {Boolean} options.localMorphColour - A flag to indicate either the colour is
* time dependent.
*/
this.createLineSegment = (arrayIn, materialIn, options) => {
if (arrayIn && materialIn) {
const linesGeometry = new LineSegmentsGeometry();
linesGeometry.setPositions(arrayIn);
linesGeometry.colorsNeedUpdate = true;
const line = new LineSegments2(linesGeometry, materialIn);
line.scale.set(1, 1, 1);
line.computeLineDistances();
this.setMesh(line, options.localTimeEnabled, options.localMorphColour);
}
};
/**
* Set the width for the lines.
*
* @param {Number} width - Width of the lines.
*/
this.setWidth = (width) => {
this.requestRender();
if (this.morph && this.morph.material) {
this.morph.material.linewidth = width;
this.morph.material.needsUpdate = true;
}
};
/**
* Add new vertices into the array
*/
this.addVertices = function (coords) {
this.requestRender();
if (coords && coords.length) {
let mesh = this.getMorph();
if (!mesh) {
this.drawRange = 0;
}
let index = this.drawRange * 3;
const reallocated = ensureCapacity(index + coords.length * 3);
coords.forEach((coord) => {
positions[index++] = coord[0];
positions[index++] = coord[1];
positions[index++] = coord[2];
this.drawRange++;
});
//fill the rest of the array.
if (!mesh) {
fillUnused(index, coords[0]);
} else {
if (reallocated) {
fillUnused(index, coords[0]);
}
updatePositions(mesh, reallocated);
}
}
return positions;
};
/**
* Get the vertices by face index
*/
this.getVerticesByFaceIndex = function (faceIndex) {
let vIndex = faceIndex * 2 * 3;
const mesh = this.getMorph();
if (mesh && this.drawRange * 3 > vIndex) {
const position = mesh.geometry.getAttribute('instanceStart');
return [
[position.data.array[vIndex], position.data.array[++vIndex], position.data.array[++vIndex]],
[
position.data.array[++vIndex],
position.data.array[++vIndex],
position.data.array[++vIndex],
],
];
}
return [];
};
/**
* Edit Vertice in index.
*/
this.editVertices = function (coords, i) {
this.requestRender();
if (coords && coords.length) {
let mesh = this.getMorph();
const maxIndex = i + coords.length - 1;
if (!mesh || 0 > i || maxIndex >= this.drawRange) {
return;
} else {
let index = i * 3;
coords.forEach((coord) => {
positions[index++] = coord[0];
positions[index++] = coord[1];
positions[index++] = coord[2];
});
fillUnused(this.drawRange * 3, coords[0]);
updatePositions(mesh, false);
}
}
return positions;
};
/**
* Add new lines to existing lines if it exists, otherwise
* create a new one and add to it.
* @param {Array} coords -An array of three components coordinates.
* @param {Number} colour - A hex value of the colour for the points
*/
this.addLines = (coords, colour) => {
this.requestRender();
if (coords && coords.length > 0) {
this.addVertices(coords);
let mesh = this.getMorph();
if (!mesh) {
const material = new THREE.Line2NodeMaterial({
color: colour,
linewidth: 1,
vertexColors: false,
worldUnits: false,
});
const options = { localTimeEnabled: false, localMorphColour: false };
this.createLineSegment(positions, material, options);
}
if (this.region) this.region.pickableUpdateRequired = true;
}
};
};
Lines2.prototype = Object.create(Lines.prototype);
Lines2.prototype.constructor = Lines2;
export { Lines2 };