Files
cadSet/cad-agent-studio/src/viewer-cadjs/common/topologyDisplayEdges.js
T
2026-07-27 17:24:04 +08:00

435 lines
14 KiB
JavaScript

function isFloat32Array(value) {
return value instanceof Float32Array;
}
function isUint32Array(value) {
return value instanceof Uint32Array;
}
function pointDistance(left, right) {
return Math.hypot(
Number(right?.[0] || 0) - Number(left?.[0] || 0),
Number(right?.[1] || 0) - Number(left?.[1] || 0),
Number(right?.[2] || 0) - Number(left?.[2] || 0)
);
}
function boundsForPolylines(polylines) {
const min = [Infinity, Infinity, Infinity];
const max = [-Infinity, -Infinity, -Infinity];
for (const positions of polylines) {
for (let offset = 0; offset + 2 < positions.length; offset += 3) {
min[0] = Math.min(min[0], positions[offset]);
min[1] = Math.min(min[1], positions[offset + 1]);
min[2] = Math.min(min[2], positions[offset + 2]);
max[0] = Math.max(max[0], positions[offset]);
max[1] = Math.max(max[1], positions[offset + 1]);
max[2] = Math.max(max[2], positions[offset + 2]);
}
}
if (!Number.isFinite(min[0]) || !Number.isFinite(max[0])) {
return null;
}
return { min, max };
}
function stitchToleranceForPolylines(polylines) {
const bounds = boundsForPolylines(polylines);
if (!bounds) {
return 1e-5;
}
return Math.max(1e-5, pointDistance(bounds.min, bounds.max) * 1e-7);
}
function pointFromProxy(proxy, vertexIndex) {
const offset = Number(vertexIndex) * 3;
if (
!Number.isInteger(vertexIndex) ||
offset < 0 ||
offset + 2 >= proxy.edgePositions.length
) {
return null;
}
return [
proxy.edgePositions[offset],
proxy.edgePositions[offset + 1],
proxy.edgePositions[offset + 2]
];
}
function normalizePartIdSet(value) {
return new Set(
(Array.isArray(value) ? value : [value])
.map((id) => String(id || "").trim())
.filter(Boolean)
);
}
function normalizeVisibilityClassSet(value) {
const values = (Array.isArray(value) ? value : [value])
.map((item) => String(item || "").trim().toLowerCase())
.filter(Boolean);
return values.length ? new Set(values) : null;
}
function occurrenceMatchesPartIds(occurrenceId, partIds) {
const normalizedOccurrenceId = String(occurrenceId || "").trim();
if (!normalizedOccurrenceId || !partIds?.size) {
return false;
}
for (const partId of partIds) {
if (normalizedOccurrenceId === partId || normalizedOccurrenceId.startsWith(`${partId}.`)) {
return true;
}
}
return false;
}
function edgeRowMatchesPartIds(row, partIds) {
return occurrenceMatchesPartIds(row?.occurrenceId || row?.partId || row?.id, partIds);
}
function edgeRowPassesPartFilter(row, includePartIds, excludePartIds) {
if (includePartIds.size && !edgeRowMatchesPartIds(row, includePartIds)) {
return false;
}
if (excludePartIds.size && edgeRowMatchesPartIds(row, excludePartIds)) {
return false;
}
return true;
}
function edgeRowPassesVisibilityClassFilter(row, visibilityClasses) {
if (!visibilityClasses?.size) {
return true;
}
const visibilityClass = String(row?.visibilityClass || "feature").trim().toLowerCase() || "feature";
return visibilityClasses.has(visibilityClass);
}
function edgeRowPassesFilters(row, includePartIds, excludePartIds, visibilityClasses) {
return (
edgeRowPassesPartFilter(row, includePartIds, excludePartIds) &&
edgeRowPassesVisibilityClassFilter(row, visibilityClasses)
);
}
function pushPoint(target, point, tolerance = 1e-7) {
if (!point) {
return;
}
const last = target.length >= 3
? [target[target.length - 3], target[target.length - 2], target[target.length - 1]]
: null;
if (!last || pointDistance(last, point) > tolerance) {
target.push(point[0], point[1], point[2]);
}
}
function buildTopologyPolylinesForRun(proxy, segmentStart, segmentCount) {
const polylines = [];
let current = [];
let lastPoint = null;
for (let segmentIndex = segmentStart; segmentIndex < segmentStart + segmentCount; segmentIndex += 1) {
const startVertexIndex = Number(proxy.edgeIndices[segmentIndex * 2]);
const endVertexIndex = Number(proxy.edgeIndices[(segmentIndex * 2) + 1]);
const startPoint = pointFromProxy(proxy, startVertexIndex);
const endPoint = pointFromProxy(proxy, endVertexIndex);
if (!startPoint || !endPoint || pointDistance(startPoint, endPoint) <= 1e-10) {
continue;
}
if (lastPoint && pointDistance(lastPoint, startPoint) > 1e-5) {
if (current.length >= 6) {
polylines.push(new Float32Array(current));
}
current = [];
lastPoint = null;
}
if (!lastPoint) {
pushPoint(current, startPoint);
}
pushPoint(current, endPoint);
lastPoint = endPoint;
}
if (current.length >= 6) {
polylines.push(new Float32Array(current));
}
return polylines;
}
function buildTopologyPolylinesForEdgeId(proxy, edgeIds, rowIndex, segmentCount) {
const polylines = [];
let runStart = -1;
let runCount = 0;
const flushRun = () => {
if (runStart >= 0 && runCount > 0) {
polylines.push(...buildTopologyPolylinesForRun(proxy, runStart, runCount));
}
runStart = -1;
runCount = 0;
};
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
if (Number(edgeIds[segmentIndex]) === rowIndex) {
if (runStart < 0) {
runStart = segmentIndex;
}
runCount += 1;
} else {
flushRun();
}
}
flushRun();
return polylines;
}
function polylinePoint(positions, vertexIndex) {
const offset = vertexIndex * 3;
return [
positions[offset],
positions[offset + 1],
positions[offset + 2]
];
}
function polylineStart(positions) {
return polylinePoint(positions, 0);
}
function polylineEnd(positions) {
return polylinePoint(positions, Math.floor(positions.length / 3) - 1);
}
function reversePolyline(positions) {
const reversed = [];
for (let offset = positions.length - 3; offset >= 0; offset -= 3) {
reversed.push(positions[offset], positions[offset + 1], positions[offset + 2]);
}
return reversed;
}
function appendPolyline(target, source, { skipFirst = true, tolerance = 1e-7 } = {}) {
const startOffset = skipFirst ? 3 : 0;
for (let offset = startOffset; offset + 2 < source.length; offset += 3) {
pushPoint(target, [source[offset], source[offset + 1], source[offset + 2]], tolerance);
}
}
function closePolylineIfNeeded(positions, tolerance) {
if (positions.length < 12) {
return positions;
}
const start = polylineStart(positions);
const end = polylineEnd(positions);
if (pointDistance(start, end) > tolerance) {
return positions;
}
const output = Array.from(positions);
output[output.length - 3] = output[0];
output[output.length - 2] = output[1];
output[output.length - 1] = output[2];
return new Float32Array(output);
}
export function stitchTopologyDisplayEdgePolylines(polylines) {
if (!Array.isArray(polylines) || polylines.length <= 1) {
return polylines || [];
}
const tolerance = stitchToleranceForPolylines(polylines);
const chains = polylines
.filter((positions) => positions?.length >= 6)
.map((positions) => Array.from(positions));
for (let index = 0; index < chains.length; index += 1) {
let changed = true;
while (changed) {
changed = false;
for (let candidateIndex = index + 1; candidateIndex < chains.length; candidateIndex += 1) {
const current = chains[index];
const candidate = chains[candidateIndex];
const currentStart = polylineStart(current);
const currentEnd = polylineEnd(current);
const candidateStart = polylineStart(candidate);
const candidateEnd = polylineEnd(candidate);
let merged = null;
if (pointDistance(currentEnd, candidateStart) <= tolerance) {
merged = [...current];
appendPolyline(merged, candidate, { tolerance });
} else if (pointDistance(currentEnd, candidateEnd) <= tolerance) {
merged = [...current];
appendPolyline(merged, reversePolyline(candidate), { tolerance });
} else if (pointDistance(currentStart, candidateEnd) <= tolerance) {
merged = [...candidate];
appendPolyline(merged, current, { tolerance });
} else if (pointDistance(currentStart, candidateStart) <= tolerance) {
merged = reversePolyline(candidate);
appendPolyline(merged, current, { tolerance });
}
if (merged) {
chains[index] = merged;
chains.splice(candidateIndex, 1);
changed = true;
break;
}
}
}
}
return chains
.filter((positions) => positions.length >= 6)
.map((positions) => closePolylineIfNeeded(new Float32Array(positions), tolerance));
}
export function hasTopologyDisplayEdgeProxy(selectorRuntime) {
const proxy = selectorRuntime?.proxy || {};
return (
isFloat32Array(proxy.edgePositions) &&
isUint32Array(proxy.edgeIndices) &&
proxy.edgePositions.length >= 6 &&
proxy.edgeIndices.length >= 2
);
}
export function hasTopologyDisplayEdgeClassification(selectorRuntime) {
return hasTopologyDisplayEdgeProxy(selectorRuntime);
}
export function shouldUseTopologyDisplayEdges(selectorRuntime) {
return !selectorRuntime?.surfaceEdgeRendering &&
Number(selectorRuntime?.schemaVersion || 0) < 3 &&
hasTopologyDisplayEdgeProxy(selectorRuntime);
}
export function buildTopologyDisplayEdgePositions(selectorRuntime, {
includePartIds = [],
excludePartIds = [],
visibilityClasses = null
} = {}) {
if (!hasTopologyDisplayEdgeProxy(selectorRuntime)) {
return null;
}
const proxy = selectorRuntime.proxy;
const segmentCount = Math.floor(proxy.edgeIndices.length / 2);
if (segmentCount <= 0) {
return null;
}
const includePartIdSet = normalizePartIdSet(includePartIds);
const excludePartIdSet = normalizePartIdSet(excludePartIds);
const hasPartFilter = includePartIdSet.size > 0 || excludePartIdSet.size > 0;
const visibilityClassSet = normalizeVisibilityClassSet(visibilityClasses);
const edgeRows = Array.isArray(selectorRuntime.edges) ? selectorRuntime.edges : [];
const hasVisibilityClassFilter = Boolean(visibilityClassSet?.size && edgeRows.length);
const hasEdgeRowFilter = hasPartFilter || hasVisibilityClassFilter;
const edgeIds = isUint32Array(proxy.edgeIds) ? proxy.edgeIds : null;
const linePositions = new Float32Array(segmentCount * 6);
let writeOffset = 0;
const writeSegment = (segmentIndex) => {
const startVertexIndex = Number(proxy.edgeIndices[segmentIndex * 2]);
const endVertexIndex = Number(proxy.edgeIndices[(segmentIndex * 2) + 1]);
const startPoint = pointFromProxy(proxy, startVertexIndex);
const endPoint = pointFromProxy(proxy, endVertexIndex);
if (!startPoint || !endPoint || pointDistance(startPoint, endPoint) <= 1e-10) {
return;
}
linePositions[writeOffset] = startPoint[0];
linePositions[writeOffset + 1] = startPoint[1];
linePositions[writeOffset + 2] = startPoint[2];
linePositions[writeOffset + 3] = endPoint[0];
linePositions[writeOffset + 4] = endPoint[1];
linePositions[writeOffset + 5] = endPoint[2];
writeOffset += 6;
};
if (hasEdgeRowFilter) {
if (!edgeRows.length) {
return null;
}
for (let rowIndex = 0; rowIndex < edgeRows.length; rowIndex += 1) {
const row = edgeRows[rowIndex];
if (!edgeRowPassesFilters(row, includePartIdSet, excludePartIdSet, visibilityClassSet)) {
continue;
}
const start = Number(row?.segmentStart || 0);
const count = Number(row?.segmentCount || 0);
if (Number.isInteger(start) && Number.isInteger(count) && start >= 0 && count > 0 && start + count <= segmentCount) {
for (let segmentIndex = start; segmentIndex < start + count; segmentIndex += 1) {
writeSegment(segmentIndex);
}
continue;
}
if (edgeIds) {
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
if (Number(edgeIds[segmentIndex]) === rowIndex) {
writeSegment(segmentIndex);
}
}
}
}
} else {
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
writeSegment(segmentIndex);
}
}
if (writeOffset <= 0) {
return null;
}
return writeOffset === linePositions.length ? linePositions : linePositions.subarray(0, writeOffset);
}
export function buildTopologyDisplayEdgePolylines(selectorRuntime, {
includePartIds = [],
excludePartIds = [],
visibilityClasses = null
} = {}) {
if (!hasTopologyDisplayEdgeProxy(selectorRuntime)) {
return [];
}
const proxy = selectorRuntime.proxy;
const segmentCount = Math.floor(proxy.edgeIndices.length / 2);
if (segmentCount <= 0) {
return [];
}
const edgeRows = Array.isArray(selectorRuntime.edges) ? selectorRuntime.edges : [];
const edgeIds = isUint32Array(proxy.edgeIds) ? proxy.edgeIds : null;
const includePartIdSet = normalizePartIdSet(includePartIds);
const excludePartIdSet = normalizePartIdSet(excludePartIds);
const hasPartFilter = includePartIdSet.size > 0 || excludePartIdSet.size > 0;
const visibilityClassSet = normalizeVisibilityClassSet(visibilityClasses);
const hasVisibilityClassFilter = Boolean(visibilityClassSet?.size && edgeRows.length);
const hasEdgeRowFilter = hasPartFilter || hasVisibilityClassFilter;
const polylines = [];
if (edgeRows.length) {
for (let rowIndex = 0; rowIndex < edgeRows.length; rowIndex += 1) {
const row = edgeRows[rowIndex];
if (!row) {
continue;
}
if (!edgeRowPassesFilters(row, includePartIdSet, excludePartIdSet, visibilityClassSet)) {
continue;
}
const start = Number(row.segmentStart || 0);
const count = Number(row.segmentCount || 0);
if (Number.isInteger(start) && Number.isInteger(count) && start >= 0 && count > 0 && start + count <= segmentCount) {
polylines.push(...buildTopologyPolylinesForRun(proxy, start, count));
} else if (edgeIds) {
polylines.push(...buildTopologyPolylinesForEdgeId(proxy, edgeIds, rowIndex, segmentCount));
}
}
return stitchTopologyDisplayEdgePolylines(polylines);
}
if (hasEdgeRowFilter) {
return [];
}
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
const rowIndex = edgeIds ? Number(edgeIds[segmentIndex]) : -1;
void rowIndex;
polylines.push(...buildTopologyPolylinesForRun(proxy, segmentIndex, 1));
}
return stitchTopologyDisplayEdgePolylines(polylines);
}