diff --git a/resources/openbim-components.js b/resources/openbim-components.js index dccce4af9..2bff23a28 100644 --- a/resources/openbim-components.js +++ b/resources/openbim-components.js @@ -120965,1070 +120965,1070 @@ class DXFExporter extends Component { DXFExporter.uuid = "568f2167-24a3-4519-b552-3b04cc74a6a6"; ToolComponent.libraryUUIDs.add(DXFExporter.uuid); -/** - * Class for Managing Markers along with creating different types of markers - * Every marker is a Simple2DMarker - * For every marker that needs to be added, you can use the Manager to add the marker and change its look and feel - */ -class MarkerManager { - constructor(components, renderer, scene, controls) { - this.components = components; - this.renderer = renderer; - this.controls = controls; - this.markers = new Set(); - this.clusterLabels = new Set(); - this.currentKeys = new Set(); - this._clusterOnZoom = true; - this._color = "white"; - // TODO: Replace with UUID for the marker key - this._markerKey = 0; - this._clusterKey = 0; - this._clusterThreeshold = 50; - this.isNavigating = false; - this.scene = scene; +class RoadNavigator extends Component { + constructor(components) { + super(components); + this.enabled = true; + this.onHighlight = new Event(); + this.onMarkerChange = new Event(); + this.onMarkerHidden = new Event(); + this._curveMeshes = []; + this._previousAlignment = null; + this.scene = new Simple2DScene(this.components, false); + this.mouseMarkers = { + select: this.newMouseMarker("#ffffff"), + hover: this.newMouseMarker("#575757"), + }; this.setupEvents(); + this.adjustRaycasterOnZoom(); } - set clusterOnZoom(value) { - this._clusterOnZoom = value; + initialize() { + console.log("View for RoadNavigator: ", this.view); } - get clusterOnZoom() { - return this._clusterOnZoom; + get() { + return null; } - set color(value) { - this._color = value; - for (const marker of this.markers) { - marker.label.get().element.style.color = value; + async draw(model, filter) { + if (!model.civilData) { + throw new Error("The provided model doesn't have civil data!"); } - } - set clusterThreeshold(value) { - this._clusterThreeshold = value; - } - get clusterThreeshold() { - return this._clusterThreeshold; - } - setupEvents() { - if (this.scene) { - this.controls.addEventListener("sleep", () => { - this.manageCluster(); - }); - this.controls.addEventListener("rest", () => { - if (this.isNavigating) { - this.manageCluster(); - this.isNavigating = false; + const { alignments } = model.civilData; + const allAlignments = filter || alignments.values(); + const scene = this.scene.get(); + const totalBBox = new THREE$1.Box3(); + totalBBox.makeEmpty(); + totalBBox.min.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + totalBBox.max.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); + for (const alignment of allAlignments) { + if (!alignment) { + throw new Error("Alignment not found!"); + } + for (const curve of alignment[this.view]) { + scene.add(curve.mesh); + this._curveMeshes.push(curve.mesh); + if (!totalBBox.isEmpty()) { + totalBBox.expandByObject(curve.mesh); } - }); - } - } - resetMarkers() { - for (const marker of this.markers) { - marker.merged = false; - } - for (const cluster of this.clusterLabels) { - this.scene.remove(cluster.label.get()); + else { + curve.mesh.geometry.computeBoundingBox(); + const cbox = curve.mesh.geometry.boundingBox; + if (cbox instanceof THREE$1.Box3) { + totalBBox.copy(cbox).applyMatrix4(curve.mesh.matrixWorld); + } + } + } } - this.clusterLabels.clear(); - this._clusterKey = 0; + const scaledBbox = new THREE$1.Box3(); + const size = new THREE$1.Vector3(); + const center = new THREE$1.Vector3(); + totalBBox.getCenter(center); + totalBBox.getSize(size); + size.multiplyScalar(1.2); + scaledBbox.setFromCenterAndSize(center, size); + await this.scene.controls.fitToBox(scaledBbox, false); } - removeMergeMarkers() { - for (const marker of this.markers) { - if (marker.merged) { - this.scene.remove(marker.label.get()); - } - else { - this.scene.add(marker.label.get()); + setupEvents() { + this.scene.uiElement + .get("container") + .domElement.addEventListener("mousemove", async (event) => { + const dom = this.scene.uiElement.get("container").domElement; + const result = this.highlighter.castRay(event, this.scene.camera, dom, this._curveMeshes); + if (result) { + const { object } = result; + this.highlighter.hover(object); + await this.updateMarker(result, "hover"); + return; } - } - for (const cluster of this.clusterLabels) { - if (cluster.markerKeys.length === 1) { - const marker = Array.from(this.markers).find((marker) => marker.key === cluster.markerKeys[0]); - if (marker) { - this.scene.add(marker.label.get()); - marker.merged = false; + this.mouseMarkers.hover.visible = false; + this.highlighter.unHover(); + await this.onMarkerHidden.trigger({ type: "hover" }); + }); + this.scene.uiElement + .get("container") + .domElement.addEventListener("click", async (event) => { + const dom = this.scene.uiElement.get("container").domElement; + const intersects = this.highlighter.castRay(event, this.scene.camera, dom, this._curveMeshes); + if (intersects) { + const result = intersects; + const mesh = result.object; + this.highlighter.select(mesh); + await this.updateMarker(result, "select"); + await this.onHighlight.trigger({ mesh, point: result.point }); + if (this._previousAlignment !== mesh.curve.alignment) { + this.kpManager.clearKPStations(); + // this.showKPStations(mesh); + this.kpManager.showKPStations(mesh); + // this.kpManager.createKP(); + this._previousAlignment = mesh.curve.alignment; } - this.scene.remove(cluster.label.get()); - this.clusterLabels.delete(cluster); } + // this.highlighter.unSelect(); + // this.clearKPStations(); + }); + } + async dispose() { + this.highlighter.dispose(); + this.clear(); + this.onHighlight.reset(); + await this.scene.dispose(); + this._curveMeshes = []; + } + clear() { + this.highlighter.unSelect(); + this.highlighter.unHover(); + for (const mesh of this._curveMeshes) { + mesh.removeFromParent(); } + this._curveMeshes = []; } - manageCluster() { - this.resetMarkers(); - for (const marker of this.markers) { - if (!marker.merged && !marker.static) { - this.currentKeys.clear(); - for (const marker2 of this.markers) { - if (marker2.static) { - continue; - } - if (marker.key !== marker2.key && !marker2.merged) { - const distance = this.distance(marker.label, marker2.label); - if (distance < this._clusterThreeshold) { - this.currentKeys.add(marker2.key); - marker2.merged = true; - } - } + setMarker(alignment, percentage, type) { + if (!this._curveMeshes.length) { + return; + } + const found = alignment.getCurveAt(percentage, this.view); + const point = alignment.getPointAt(percentage, this.view); + const { index } = found.curve.getSegmentAt(found.percentage); + this.setMouseMarker(point, found.curve.mesh, index, type); + } + setDefSegments(segmentsArray) { + const defSegments = []; + const slope = []; + const calculateSlopeSegment = (point1, point2) => { + const deltaY = point2[1] - point1[1]; + const deltaX = point2[0] - point1[0]; + return deltaY / deltaX; + }; + for (let i = 0; i < segmentsArray.length; i++) { + const segment = segmentsArray[i]; + let startX; + let startY; + let endX; + let endY; + // Set start + for (let j = 0; j < Object.keys(segment).length / 3; j++) { + if (segment[j * 3] !== undefined && segment[j * 3 + 1] !== undefined) { + startX = segment[j * 3]; + startY = segment[j * 3 + 1]; + break; } - if (this.currentKeys.size > 0) { - if (!this.scene) { - return; - } - this.currentKeys.add(marker.key); - marker.merged = true; - const clusterGroup = Array.from(this.currentKeys); - const averagePosition = this.getAveragePositionFromLabels(clusterGroup); - const clusterLabel = new Simple2DMarker(this.components, this.createClusterElement(this._clusterKey.toString()), this.scene); - clusterLabel.get().element.textContent = - clusterGroup.length.toString(); - clusterLabel.get().position.copy(averagePosition); - this.clusterLabels.add({ - key: this._clusterKey.toString(), - markerKeys: clusterGroup, - label: clusterLabel, - }); - this._clusterKey++; + } + // Set end + for (let j = Object.keys(segment).length / 3 - 1; j >= 0; j--) { + if (segment[j * 3] !== undefined && segment[j * 3 + 1] !== undefined) { + endX = segment[j * 3]; + endY = segment[j * 3 + 1]; + break; } } + const defSlope = calculateSlopeSegment( + // @ts-ignore + [startX, startY], + // @ts-ignore + [endX, endY]); + const slopeSegment = (defSlope * 100).toFixed(2); + slope.push({ slope: slopeSegment }); } - this.removeMergeMarkers(); + segmentsArray.forEach((segment) => { + for (let i = 0; i < segment.length - 3; i += 3) { + const startX = segment[i]; + const startY = segment[i + 1]; + const startZ = segment[i + 2]; + const endX = segment[i + 3]; + const endY = segment[i + 4]; + const endZ = segment[i + 5]; + defSegments.push({ + start: new THREE$1.Vector3(startX, startY, startZ), + end: new THREE$1.Vector3(endX, endY, endZ), + }); + } + }); + return { defSegments, slope }; } - getAveragePositionFromLabels(clusterGroup) { - const positions = clusterGroup.map((key) => { - const marker = Array.from(this.markers).find((marker) => marker.key === key); - if (marker) { - return marker.label.get().position; - } - return new THREE$1.Vector3(); - }); - const averagePosition = positions - .reduce((acc, curr) => acc.add(curr), new THREE$1.Vector3()) - .divideScalar(positions.length); - return averagePosition; + hideMarker(type) { + this.mouseMarkers[type].visible = false; } - createClusterElement(key) { - const div = document.createElement("div"); - div.textContent = key; - div.style.color = "#000000"; - div.style.background = "#FFFFFF"; - div.style.fontSize = "1.2rem"; - div.style.fontWeight = "500"; - div.style.pointerEvents = "auto"; - div.style.borderRadius = "50%"; - div.style.padding = "5px 11px"; - div.style.textAlign = "center"; - div.style.cursor = "pointer"; - // div.style.transition = "all 0.05s"; - div.addEventListener("pointerdown", () => { - this.navigateToCluster(key); - }); - div.addEventListener("pointerover", () => { - div.style.background = "#BCF124"; - }); - div.addEventListener("pointerout", () => { - div.style.background = "#FFFFFF"; + adjustRaycasterOnZoom() { + this.scene.controls.addEventListener("update", () => { + const { zoom, left, right, top, bottom } = this.scene.camera; + const width = left - right; + const height = top - bottom; + const screenSize = Math.max(width, height); + const realScreenSize = screenSize / zoom; + const range = 40; + const { caster } = this.highlighter; + caster.params.Line.threshold = realScreenSize / range; }); - return div; } - addMarker(text, mesh) { - const span = document.createElement("span"); - span.innerHTML = text; - span.style.color = this._color; - const marker = this.addMarkerToScene(span); - marker.get().position.copy(mesh.position); - this.markers.add({ - label: marker, - mesh, - key: this._markerKey.toString(), - merged: false, - static: false, - }); - this._markerKey++; + newMouseMarker(color) { + const scene = this.scene.get(); + const root = document.createElement("div"); + const bar = document.createElement("div"); + root.appendChild(bar); + bar.style.backgroundColor = color; + bar.style.width = "3rem"; + bar.style.height = "3px"; + const mouseMarker = new Simple2DMarker(this.components, root, scene); + mouseMarker.visible = false; + return mouseMarker; } - addMarkerAtPoint(text, point, type, isStatic = false) { - if (type !== undefined) { - const span = document.createElement("span"); - span.innerHTML = text; - span.style.color = this._color; - const marker = new Simple2DMarker(this.components, span, this.scene); - marker.get().position.copy(point); - this.markers.add({ - label: marker, - mesh: new THREE$1.Mesh(), - key: this._markerKey.toString(), - merged: false, - type, - static: isStatic, - }); - this._markerKey++; + setMouseMarker(point, object, index, type) { + if (index === undefined) { + return; } + this.mouseMarkers[type].visible = true; + const marker = this.mouseMarkers[type].get(); + marker.position.copy(point); + const curveMesh = object; + const { startPoint, endPoint } = curveMesh.curve.getSegment(index); + const angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); + const bar = marker.element.children[0]; + const trueAngle = 90 - (angle / Math.PI) * 180; + bar.style.transform = `rotate(${trueAngle}deg)`; } - // TODO: Move this to switch statement inside addCivilMarker - addKPStation(text, mesh) { - const container = document.createElement("div"); - const span = document.createElement("div"); - container.appendChild(span); - span.innerHTML = text; - span.style.color = this._color; - span.style.borderBottom = "1px dotted white"; - span.style.width = "160px"; - span.style.textAlign = "left"; - const marker = new Simple2DMarker(this.components, container, this.scene); - const point = new THREE$1.Vector3(); - point.x = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 1); - point.y = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 1); - point.z = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 1); - const secondLastPoint = new THREE$1.Vector3(); - secondLastPoint.x = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 2); - secondLastPoint.y = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 2); - secondLastPoint.z = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 2); - const midPoint = new THREE$1.Vector3(); - midPoint.x = (point.x + secondLastPoint.x) / 2; - midPoint.y = (point.y + secondLastPoint.y) / 2; - midPoint.z = (point.z + secondLastPoint.z) / 2; - marker.get().position.copy(midPoint); - const direction = new THREE$1.Vector3(); - direction.subVectors(point, secondLastPoint).normalize(); - const quaternion = new THREE$1.Quaternion(); - quaternion.setFromUnitVectors(new THREE$1.Vector3(0, 1, 0), direction); - const eulerZ = new THREE$1.Euler().setFromQuaternion(quaternion).z; - const rotationZ = THREE$1.MathUtils.radToDeg(eulerZ); - span.style.transform = `rotate(${-rotationZ - 90}deg) translate(-35%, -50%)`; - this.markers.add({ - label: marker, - mesh, - key: this._markerKey.toString(), - merged: false, - static: false, - }); - this._markerKey++; - } - addCivilVerticalMarker(text, mesh, type, scene) { - const span = document.createElement("span"); - span.innerHTML = text; - span.style.color = this._color; - const marker = new Simple2DMarker(this.components, span, scene); - if (type === "Height") { - const span = document.createElement("span"); - span.innerHTML = text; - span.style.color = this._color; - const { position } = mesh.geometry.attributes; - const setArray = position.array.length / 3; - const firstIndex = (setArray - 1) * 3; - const lastIndex = position.array.slice(firstIndex, firstIndex + 3); - marker.get().position.set(lastIndex[0], lastIndex[1] + 10, lastIndex[2]); - } - else if (type === "InitialKPV") { - const { position } = mesh.geometry.attributes; - const pX = position.getX(0); - const pY = position.getY(0); - const pZ = position.getZ(0); - marker.get().position.set(pX - 20, pY, pZ); - } - else if (type === "FinalKPV") { - const { position } = mesh.geometry.attributes; - const pX = position.getX(mesh.geometry.attributes.position.count - 1); - const pY = position.getY(mesh.geometry.attributes.position.count - 1); - const pZ = position.getZ(mesh.geometry.attributes.position.count - 1); - marker.get().position.set(pX + 20, pY, pZ); - } - else if (type === "Slope") { - span.style.color = "grey"; - const { position } = mesh.geometry.attributes; - const pointStart = new THREE$1.Vector3(); - pointStart.x = position.getX(0); - pointStart.y = position.getY(0); - pointStart.z = position.getZ(0); - const pointEnd = new THREE$1.Vector3(); - pointEnd.x = position.getX(position.count - 1); - pointEnd.y = position.getY(position.count - 1); - pointEnd.z = position.getZ(position.count - 1); - const midPoint = new THREE$1.Vector3(); - midPoint.addVectors(pointStart, pointEnd).multiplyScalar(0.5); - marker.get().position.set(midPoint.x, midPoint.y - 10, midPoint.z); + async updateMarker(intersects, type) { + const { point, index, object } = intersects; + const mesh = object; + const curve = mesh.curve; + const alignment = mesh.curve.alignment; + const percentage = alignment.getPercentageAt(point, this.view); + const markerPoint = point.clone(); + this.setMouseMarker(markerPoint, mesh, index, type); + if (percentage !== null) { + await this.onMarkerChange.trigger({ alignment, percentage, type, curve }); } - this.markers.add({ - label: marker, - mesh, - key: this._markerKey.toString(), - type, - merged: false, - static: false, - }); - this._markerKey++; - return marker; } - addCivilMarker(text, mesh, type) { - const span = document.createElement("span"); - span.innerHTML = text; - span.style.color = this._color; - const marker = this.addMarkerToScene(span); - if (type === "InitialKP") { - const pX = mesh.geometry.attributes.position.getX(0); - const pY = mesh.geometry.attributes.position.getY(0); - const pZ = mesh.geometry.attributes.position.getZ(0); - marker.get().position.set(pX + 2, pY + 2, pZ); - } - else if (type === "FinalKP") { - const pX = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 1); - const pY = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 1); - const pZ = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 1); - marker.get().position.set(pX + 2, pY - 2, pZ); - } - else if (type === "Length") { - const pointStart = new THREE$1.Vector3(); - pointStart.x = mesh.geometry.attributes.position.getX(0); - pointStart.y = mesh.geometry.attributes.position.getY(0); - pointStart.z = mesh.geometry.attributes.position.getZ(0); - const pointEnd = new THREE$1.Vector3(); - pointEnd.x = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 1); - pointEnd.y = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 1); - pointEnd.z = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 1); - const length = pointStart.distanceTo(pointEnd); - marker.get().element.innerText = length.toFixed(2); - marker - .get() - .position.copy(pointEnd.clone().add(pointStart).divideScalar(2)); - } - this.markers.add({ - label: marker, - mesh, - key: this._markerKey.toString(), - type, - merged: false, - static: false, - }); - this._markerKey++; - return marker; +} + +class CurveHighlighter { + constructor(scene, type) { + this.onSelect = new Event(); + this.caster = new THREE$1.Raycaster(); + this.scene = scene; + this.type = type; + this.hoverCurve = this.newCurve(0.003, 0x444444, false); + this.hoverPoints = this.newPoints(5, 0x444444); + this.selectCurve = this.newCurve(0.005, 0xffffff, true); + this.selectPoints = this.newPoints(7, 0xffffff); } - addMarkerToScene(span) { - if (!this.scene) { - throw new Error("Scene is needed to add markers!"); + dispose() { + if (this.selectCurve) { + this.scene.remove(this.selectCurve); } - const scene = this.scene; - const marker = new Simple2DMarker(this.components, span, scene); - return marker; + this.selectCurve.material.dispose(); + this.selectCurve.geometry.dispose(); + this.selectCurve = null; + this.hoverCurve.material.dispose(); + this.hoverCurve.geometry.dispose(); + this.hoverCurve = null; + this.hoverPoints.material.dispose(); + this.hoverPoints.geometry.dispose(); + this.selectPoints.material.dispose(); + this.selectPoints.geometry.dispose(); + this.scene = null; } - getScreenPosition(label) { - const screenPosition = new THREE$1.Vector3(); - if (!this.scene) { - const labelPosition = label - .get() - .position.clone() - .project(this.components.camera.get()); - const dimensions = this.components.renderer.getSize(); - screenPosition.x = - (labelPosition.x * dimensions.x) / 2 + dimensions.x / 2; - screenPosition.y = - -((labelPosition.y * dimensions.y) / 2) + dimensions.y / 2; - } - else { - const labelPosition = label - .get() - .position.clone() - .project(this.controls.camera); - const dimensions = this.renderer.getSize(); - screenPosition.x = - (labelPosition.x * dimensions.x) / 2 + dimensions.x / 2; - screenPosition.y = - -((labelPosition.y * dimensions.y) / 2) + dimensions.y / 2; + castRay(event, camera, dom, meshes) { + const mouse = new THREE$1.Vector2(); + const rect = dom.getBoundingClientRect(); + mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + this.caster.setFromCamera(mouse, camera); + const intersects = this.caster.intersectObjects(meshes); + if (!intersects.length) { + return null; } - return screenPosition; + return intersects[0]; } - distance(label1, label2) { - const screenPosition1 = this.getScreenPosition(label1); - const screenPosition2 = this.getScreenPosition(label2); - const dx = screenPosition1.x - screenPosition2.x; - const dy = screenPosition1.y - screenPosition2.y; - const distance = Math.sqrt(dx * dx + dy * dy) * 0.5; - // Managing Overlapping Labels - if (distance === 0) { - const updateDistance = this._clusterThreeshold + 1; - return updateDistance; - } - return distance; + select(mesh) { + this.highlight(mesh, this.selectCurve, this.selectPoints, true); + this.onSelect.trigger(mesh); } - navigateToCluster(key) { - const boundingRegion = []; - const cluster = Array.from(this.clusterLabels).find((cluster) => cluster.key === key); - if (cluster) { - for (const markerKey of cluster.markerKeys) { - const marker = Array.from(this.markers).find((marker) => marker.key === markerKey); - if (marker) { - boundingRegion.push(marker.label.get().position); - } - } - this.scene.remove(cluster?.label.get()); - this.clusterLabels.delete(cluster); - } - if (this.scene) { - const box3 = this.createBox3FromPoints(boundingRegion); - const size = new THREE$1.Vector3(); - box3.getSize(size); - const center = new THREE$1.Vector3(); - box3.getCenter(center); - const geometry = new THREE$1.BoxGeometry(size.x, size.y, size.z); - geometry.translate(center.x, center.y, center.z); - const mesh = new THREE$1.Mesh(geometry); - mesh.geometry.computeBoundingSphere(); - const boundingSphere = mesh.geometry?.boundingSphere; - if (this.controls && boundingSphere) { - this.controls.fitToSphere(mesh, true); + unSelect() { + this.selectCurve.removeFromParent(); + this.selectPoints.removeFromParent(); + } + hover(mesh) { + this.highlight(mesh, this.hoverCurve, this.hoverPoints, false); + } + unHover() { + this.hoverCurve.removeFromParent(); + this.hoverPoints.removeFromParent(); + } + highlight(mesh, curve, points, useColors) { + const { alignment } = mesh.curve; + this.scene.add(curve); + this.scene.add(points); + const lines = []; + const colors = []; + const vertices = []; + for (const foundCurve of alignment[this.type]) { + const position = foundCurve.mesh.geometry.attributes.position; + for (const coord of position.array) { + lines.push(coord); } - this.isNavigating = true; - geometry.dispose(); - mesh.clear(); - } - boundingRegion.length = 0; - } - createBox3FromPoints(points) { - const bbox = new THREE$1.Box3(); - for (const point of points) { - bbox.expandByPoint(point); + if (useColors) { + let type; + if (this.type === "absolute") { + // 3D curves don't have type defined, so we take the horizontal + const { horizontal } = foundCurve.alignment; + type = horizontal[foundCurve.index].data.TYPE; + } + else { + type = foundCurve.data.TYPE; + } + const found = CurveHighlighter.settings.colors[type] || [1, 1, 1]; + for (let i = 0; i < position.count; i++) { + colors.push(...found); + } + } + const [x, y, z] = position.array; + vertices.push(new THREE$1.Vector3(x, y, z)); } - return bbox; - } - clearMarkers() { - for (const marker of this.markers) { - this.scene.remove(marker.label.get()); + const lastX = lines[lines.length - 3]; + const lastY = lines[lines.length - 2]; + const lastZ = lines[lines.length - 1]; + vertices.push(new THREE$1.Vector3(lastX, lastY, lastZ)); + if (lines.length / 3 > curve.geometry.attributes.position.count) { + curve.geometry.dispose(); + curve.geometry = new LineGeometry(); } - this.markers.clear(); - this._markerKey = 0; - } - clearMarkersByType(type) { - for (const marker of this.markers) { - if (marker.type === type) { - this.scene.remove(marker.label.get()); - this.markers.delete(marker); - } + curve.geometry.setPositions(lines); + if (useColors) { + curve.geometry.setColors(colors); } + points.geometry.setFromPoints(vertices); } - dispose() { - this.markers.forEach((marker) => { - marker.label.dispose(); + newCurve(linewidth, color, vertexColors) { + const selectGeometry = new LineGeometry(); + const selectMaterial = new LineMaterial({ + color, + linewidth, + vertexColors, + worldUnits: false, + depthTest: false, }); - this.markers.clear(); - this._markerKey = 0; - this.clusterLabels.forEach((cluster) => { - cluster.label.dispose(); + const curve = new Line2(selectGeometry, selectMaterial); + this.scene.add(curve); + return curve; + } + newPoints(size, color) { + const pointsGeometry = new THREE$1.BufferGeometry(); + const pointsAttr = new THREE$1.BufferAttribute(new Float32Array(), 3); + pointsGeometry.setAttribute("position", pointsAttr); + const pointsMaterial = new THREE$1.PointsMaterial({ + size, + color, + sizeAttenuation: false, + depthTest: false, }); - this.clusterLabels.clear(); - this._clusterKey = 0; - this.currentKeys.clear(); + const points = new THREE$1.Points(pointsGeometry, pointsMaterial); + points.frustumCulled = false; + this.scene.add(points); + return points; } } +CurveHighlighter.settings = { + colors: { + LINE: [213 / 255, 0 / 255, 255 / 255], + CIRCULARARC: [0 / 255, 46, 255 / 255], + CLOTHOID: [0 / 255, 255 / 255, 0 / 255], + PARABOLICARC: [0 / 255, 255 / 255, 72 / 255], + CONSTANTGRADIENT: [213 / 255, 0 / 255, 255 / 255], + }, +}; -class RoadNavigator extends Component { - constructor(components) { - super(components); - this.enabled = true; - this.onHighlight = new Event(); - this.onMarkerChange = new Event(); - this.onMarkerHidden = new Event(); - this._curveMeshes = []; - this._previousAlignment = null; - this.scene = new Simple2DScene(this.components, false); - // @ts-ignore - this.components._renderer._renderer; - // @ts-ignore - const controls = this.components._camera.controls; - this.markerManager = new MarkerManager(this.components, this.scene.renderer, this.scene.scene, controls); - this.mouseMarkers = { - select: this.newMouseMarker("#ffffff"), - hover: this.newMouseMarker("#575757"), - }; - this.setupEvents(); - this.adjustRaycasterOnZoom(); +class PlanHighlighter extends CurveHighlighter { + constructor(scene, kpManager) { + super(scene, "horizontal"); + this.kpManager = kpManager; + this.offset = 10; + this.markupLines = []; + this.markupMaterial = new THREE$1.LineBasicMaterial({ + color: 0x686868, + }); } - initialize() { - console.log("View for RoadNavigator: ", this.view); + showCurveInfo(curveMesh) { + this.disposeMarkups(); + this.currentCurveMesh = curveMesh; + switch (curveMesh.curve.data.TYPE) { + case "LINE": + this.showLineInfo(curveMesh, this.offset); + break; + case "CIRCULARARC": + this.showCircularArcInfo(curveMesh, this.offset); + break; + case "CLOTHOID": + this.showClothoidInfo(curveMesh, this.offset); + break; + default: + console.log("Unknown curve type:", curveMesh.curve.data.TYPE); + break; + } } - get() { - return null; + updateOffset(screenSize, _zoom, _triggerRedraw) { + const biggerSize = Math.max(screenSize.height, screenSize.width); + const newOffset = biggerSize / (_zoom * 150); + if (newOffset !== this.offset) { + this.offset = newOffset; + if (_triggerRedraw && this.currentCurveMesh) { + this.showCurveInfo(this.currentCurveMesh); + } + } } - async draw(model, filter) { - if (!model.civilData) { - throw new Error("The provided model doesn't have civil data!"); + dispose() { + super.dispose(); + for (const line of this.markupLines) { + this.scene.remove(line); + line.removeFromParent(); } - const { alignments } = model.civilData; - const allAlignments = filter || alignments.values(); - const scene = this.scene.get(); - const totalBBox = new THREE$1.Box3(); - totalBBox.makeEmpty(); - totalBBox.min.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); - totalBBox.max.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); - for (const alignment of allAlignments) { - if (!alignment) { - throw new Error("Alignment not found!"); - } - for (const curve of alignment[this.view]) { - scene.add(curve.mesh); - this._curveMeshes.push(curve.mesh); - if (!totalBBox.isEmpty()) { - totalBBox.expandByObject(curve.mesh); - } - else { - curve.mesh.geometry.computeBoundingBox(); - const cbox = curve.mesh.geometry.boundingBox; - if (cbox instanceof THREE$1.Box3) { - totalBBox.copy(cbox).applyMatrix4(curve.mesh.matrixWorld); - } - } - } + this.disposeMarkups(); + this.markupMaterial.dispose(); + } + disposeMarkups() { + for (const line of this.markupLines) { + line.geometry.dispose(); + this.scene.remove(line); } - const scaledBbox = new THREE$1.Box3(); - const size = new THREE$1.Vector3(); - const center = new THREE$1.Vector3(); - totalBBox.getCenter(center); - totalBBox.getSize(size); - size.multiplyScalar(1.2); - scaledBbox.setFromCenterAndSize(center, size); - await this.scene.controls.fitToBox(scaledBbox, false); + this.markupLines = []; } - setupEvents() { - this.scene.uiElement - .get("container") - .domElement.addEventListener("mousemove", async (event) => { - const dom = this.scene.uiElement.get("container").domElement; - const result = this.highlighter.castRay(event, this.scene.camera, dom, this._curveMeshes); - if (result) { - const { object } = result; - this.highlighter.hover(object); - await this.updateMarker(result, "hover"); - return; - } - this.mouseMarkers.hover.visible = false; - this.highlighter.unHover(); - await this.onMarkerHidden.trigger({ type: "hover" }); - }); - this.scene.uiElement - .get("container") - .domElement.addEventListener("click", async (event) => { - const dom = this.scene.uiElement.get("container").domElement; - const intersects = this.highlighter.castRay(event, this.scene.camera, dom, this._curveMeshes); - if (intersects) { - const result = intersects; - const mesh = result.object; - this.highlighter.select(mesh); - await this.updateMarker(result, "select"); - await this.onHighlight.trigger({ mesh, point: result.point }); - if (this._previousAlignment !== mesh.curve.alignment) { - this.kpManager.clearKPStations(); - // this.showKPStations(mesh); - this.kpManager.showKPStations(mesh); - // this.kpManager.createKP(); - this._previousAlignment = mesh.curve.alignment; - } - } - // this.highlighter.unSelect(); - // this.clearKPStations(); - }); + unSelect() { + super.unSelect(); + this.disposeMarkups(); } - async dispose() { - this.highlighter.dispose(); - this.clear(); - this.onHighlight.reset(); - await this.scene.dispose(); - this._curveMeshes = []; + calculateTangent(positions, index) { + const numComponents = 3; + const pointIndex = index * numComponents; + const prevPointIndex = Math.max(0, pointIndex - numComponents); + const nextPointIndex = Math.min(positions.length - numComponents, pointIndex + numComponents); + const prevPoint = new THREE$1.Vector3().fromArray(positions, prevPointIndex); + const nextPoint = new THREE$1.Vector3().fromArray(positions, nextPointIndex); + const tangent = nextPoint.clone().sub(prevPoint).normalize(); + return tangent; } - clear() { - this.highlighter.unSelect(); - this.highlighter.unHover(); - for (const mesh of this._curveMeshes) { - mesh.removeFromParent(); + calculateParallelCurve(positions, count, offset) { + const parallelCurvePoints = []; + for (let i = 0; i < count; i++) { + const tangentVector = this.calculateTangent(positions, i); + const perpendicularVector = tangentVector + .clone() + .applyAxisAngle(new THREE$1.Vector3(0, 0, 1), Math.PI / 2); + perpendicularVector.normalize(); + const offsetVector = perpendicularVector.clone().multiplyScalar(offset); + const pointIndex = i * 3; + const parallelPoint = new THREE$1.Vector3() + .fromArray(positions, pointIndex) + .add(offsetVector); + parallelCurvePoints.push(parallelPoint); } - this._curveMeshes = []; + return parallelCurvePoints; } - setMarker(alignment, percentage, type) { - if (!this._curveMeshes.length) { - return; + calculateDimensionLines(curve, line) { + const startDimensionPoints = []; + const curvePositions = curve.geometry.attributes.position.array; + const linePositions = line.geometry.attributes.position.array; + if (curvePositions.length < 6 && linePositions.length < 6) { + throw new Error("Line must have at least two vertices"); } - const found = alignment.getCurveAt(percentage, this.view); - const point = alignment.getPointAt(percentage, this.view); - const { index } = found.curve.getSegmentAt(found.percentage); - this.setMouseMarker(point, found.curve.mesh, index, type); + const startCurvePoint = new THREE$1.Vector3(curvePositions[0], curvePositions[1], curvePositions[2]); + const startLinePoint = new THREE$1.Vector3(linePositions[0], linePositions[1], linePositions[2]); + const endDimensionPoints = []; + const lastCurveIndex = curvePositions.length - 3; + const endCurvePoint = new THREE$1.Vector3(curvePositions[lastCurveIndex], curvePositions[lastCurveIndex + 1], curvePositions[lastCurveIndex + 2]); + const lastLineIndex = linePositions.length - 3; + const endLinePoint = new THREE$1.Vector3(linePositions[lastLineIndex], linePositions[lastLineIndex + 1], linePositions[lastLineIndex + 2]); + startDimensionPoints.push(startCurvePoint, startLinePoint); + endDimensionPoints.push(endCurvePoint, endLinePoint); + return { startDimensionPoints, endDimensionPoints }; } - setDefSegments(segmentsArray) { - let defSegments = []; - let slope = []; - const calculateSlopeSegment = (point1, point2) => { - const deltaY = point2[1] - point1[1]; - const deltaX = point2[0] - point1[0]; - return deltaY / deltaX; - }; - for (let i = 0; i < segmentsArray.length; i++) { - const segment = segmentsArray[i]; - let startX, startY, endX, endY; - // Set start - for (let j = 0; j < Object.keys(segment).length / 3; j++) { - if (segment[j * 3] !== undefined && segment[j * 3 + 1] !== undefined) { - startX = segment[j * 3]; - startY = segment[j * 3 + 1]; - break; - } - } - // Set end - for (let j = Object.keys(segment).length / 3 - 1; j >= 0; j--) { - if (segment[j * 3] !== undefined && segment[j * 3 + 1] !== undefined) { - endX = segment[j * 3]; - endY = segment[j * 3 + 1]; - break; - } + offsetDimensionLine(points, offset) { + const direction = new THREE$1.Vector3() + .copy(points[points.length - 1]) + .sub(points[0]) + .normalize(); + const offsetVector = direction.clone().multiplyScalar(offset); + const newPoints = points.map((point) => point.clone().add(offsetVector)); + return newPoints; + } + showLineInfo(curveMesh, offset) { + this.kpManager.clearMarkersByType("Length"); + this.kpManager.clearMarkersByType("Radius"); + const positions = curveMesh.geometry.attributes.position.array; + const parallelCurvePoints = this.calculateParallelCurve(positions, positions.length / 3, offset); + const lengthGeometry = new THREE$1.BufferGeometry().setFromPoints(parallelCurvePoints); + const lineParallelLine = new THREE$1.Line(lengthGeometry, this.markupMaterial); + this.kpManager.showLineLength(lineParallelLine, curveMesh.curve.getLength()); + this.scene.add(lineParallelLine); + this.markupLines.push(lineParallelLine); + const { startDimensionPoints, endDimensionPoints } = this.calculateDimensionLines(curveMesh, lineParallelLine); + const offsetStartDimensionPoints = this.offsetDimensionLine(startDimensionPoints, offset * 0.1); + const offsetEndDimensionPoints = this.offsetDimensionLine(endDimensionPoints, offset * 0.1); + const startDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetStartDimensionPoints); + const endDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetEndDimensionPoints); + const lineStartDimensionlLine = new THREE$1.Line(startDimensionGeometry, this.markupMaterial); + this.scene.add(lineStartDimensionlLine); + this.markupLines.push(lineStartDimensionlLine); + const lineEndDimensionlLine = new THREE$1.Line(endDimensionGeometry, this.markupMaterial); + this.scene.add(lineEndDimensionlLine); + this.markupLines.push(lineEndDimensionlLine); + } + showClothoidInfo(curveMesh, offset) { + this.kpManager.clearMarkersByType("Length"); + this.kpManager.clearMarkersByType("Radius"); + const positions = curveMesh.geometry.attributes.position.array; + const parallelCurvePoints = this.calculateParallelCurve(positions, positions.length / 3, offset); + const lengthGeometry = new THREE$1.BufferGeometry().setFromPoints(parallelCurvePoints); + this.kpManager.showCurveLength(parallelCurvePoints, curveMesh.curve.getLength()); + const clothParallelLine = new THREE$1.Line(lengthGeometry, this.markupMaterial); + this.scene.add(clothParallelLine); + this.markupLines.push(clothParallelLine); + const { startDimensionPoints, endDimensionPoints } = this.calculateDimensionLines(curveMesh, clothParallelLine); + const offsetStartDimensionPoints = this.offsetDimensionLine(startDimensionPoints, offset * 0.1); + const offsetEndDimensionPoints = this.offsetDimensionLine(endDimensionPoints, offset * 0.1); + const startDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetStartDimensionPoints); + const endDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetEndDimensionPoints); + const clothStartDimensionlLine = new THREE$1.Line(startDimensionGeometry, this.markupMaterial); + this.scene.add(clothStartDimensionlLine); + this.markupLines.push(clothStartDimensionlLine); + const clothEndDimensionlLine = new THREE$1.Line(endDimensionGeometry, this.markupMaterial); + this.scene.add(clothEndDimensionlLine); + this.markupLines.push(clothEndDimensionlLine); + } + showCircularArcInfo(curveMesh, offset) { + this.kpManager.clearMarkersByType("Length"); + this.kpManager.clearMarkersByType("Radius"); + const radius = curveMesh.curve.data.RADIUS; + const positions = curveMesh.geometry.attributes.position.array; + const count = curveMesh.geometry.attributes.position.count; + const linePoints = []; + const firstPoint = new THREE$1.Vector3(positions[0], positions[1], positions[2]); + const lastPointIndex = (count - 1) * 3; + const lastPoint = new THREE$1.Vector3(positions[lastPointIndex], positions[lastPointIndex + 1], positions[lastPointIndex + 2]); + const middlePointIndex = (count / 2) * 3; + const middlePoint = new THREE$1.Vector3(positions[middlePointIndex], positions[middlePointIndex + 1], positions[middlePointIndex + 2]); + const tangentVector = lastPoint.clone().sub(firstPoint).normalize(); + const perpendicularVector = new THREE$1.Vector3(-tangentVector.y, tangentVector.x, 0); + perpendicularVector.multiplyScalar(radius); + const arcCenterPoint = middlePoint.clone().add(perpendicularVector); + linePoints.push(middlePoint); + linePoints.push(arcCenterPoint); + const radiusGeometry = new THREE$1.BufferGeometry().setFromPoints(linePoints); + const radiusLine = new THREE$1.Line(radiusGeometry, this.markupMaterial); + this.kpManager.showCurveRadius(radiusLine, Math.abs(radius)); + this.scene.add(radiusLine); + this.markupLines.push(radiusLine); + const parallelCurvePoints = []; + for (let i = 0; i < count; i++) { + const tangentVector = this.calculateTangent(positions, i); + const radius = curveMesh.curve.data.RADIUS; + const perpendicularVector = new THREE$1.Vector3(tangentVector.y, -tangentVector.x, 0); + perpendicularVector.normalize(); + if (radius < 0) { + perpendicularVector.negate(); } - const defSlope = calculateSlopeSegment( - // @ts-ignore - [startX, startY], - // @ts-ignore - [endX, endY]); - const slopeSegment = (defSlope * 100).toFixed(2); - slope.push({ slope: slopeSegment }); + const offsetVector = perpendicularVector.clone().multiplyScalar(offset); + const pointIndex = i * 3; + const parallelPoint = new THREE$1.Vector3(positions[pointIndex] + offsetVector.x, positions[pointIndex + 1] + offsetVector.y, positions[pointIndex + 2] + offsetVector.z); + parallelCurvePoints.push(parallelPoint); } - segmentsArray.forEach((segment) => { - for (let i = 0; i < segment.length - 3; i += 3) { - let startX = segment[i]; - let startY = segment[i + 1]; - let startZ = segment[i + 2]; - let endX = segment[i + 3]; - let endY = segment[i + 4]; - let endZ = segment[i + 5]; - defSegments.push({ - start: new THREE$1.Vector3(startX, startY, startZ), - end: new THREE$1.Vector3(endX, endY, endZ), - }); + const lengthGeometry = new THREE$1.BufferGeometry().setFromPoints(parallelCurvePoints); + this.kpManager.showCurveLength(parallelCurvePoints, curveMesh.curve.getLength()); + const circArcParallelLine = new THREE$1.Line(lengthGeometry, this.markupMaterial); + this.scene.add(circArcParallelLine); + this.markupLines.push(circArcParallelLine); + const { startDimensionPoints, endDimensionPoints } = this.calculateDimensionLines(curveMesh, circArcParallelLine); + const offsetStartDimensionPoints = this.offsetDimensionLine(startDimensionPoints, offset * 0.1); + const offsetEndDimensionPoints = this.offsetDimensionLine(endDimensionPoints, offset * 0.1); + const startDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetStartDimensionPoints); + const endDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetEndDimensionPoints); + const circArcStartDimensionlLine = new THREE$1.Line(startDimensionGeometry, this.markupMaterial); + this.scene.add(circArcStartDimensionlLine); + this.markupLines.push(circArcStartDimensionlLine); + const circArcEndDimensionlLine = new THREE$1.Line(endDimensionGeometry, this.markupMaterial); + this.scene.add(circArcEndDimensionlLine); + this.markupLines.push(circArcEndDimensionlLine); + } +} + +class CivilFloatingWindow { + static get(components, scene, name) { + const floatingWindow = new FloatingWindow(components); + floatingWindow.title = name; + components.ui.add(floatingWindow); + floatingWindow.visible = false; + const hContainer = scene.uiElement.get("container"); + floatingWindow.addChild(hContainer); + floatingWindow.onResized.add(() => scene.grid.regenerate()); + floatingWindow.slots.content.domElement.style.padding = "0"; + floatingWindow.slots.content.domElement.style.overflow = "hidden"; + floatingWindow.onResized.add(() => { + const { width, height } = floatingWindow.containerSize; + scene.setSize(height, width); + }); + floatingWindow.domElement.style.width = "20rem"; + floatingWindow.domElement.style.height = "20rem"; + floatingWindow.onVisible.add(() => { + if (floatingWindow.visible) { + scene.grid.regenerate(); } }); - return { defSegments, slope }; + if (components.renderer.isUpdateable()) { + components.renderer.onAfterUpdate.add(async () => { + if (floatingWindow.visible) { + await scene.update(); + } + }); + } + floatingWindow.onResized.trigger(); + return floatingWindow; } - hideMarker(type) { - this.mouseMarkers[type].visible = false; +} + +/** + * Class for Managing Markers along with creating different types of markers + * Every marker is a Simple2DMarker + * For every marker that needs to be added, you can use the Manager to add the marker and change its look and feel + */ +class MarkerManager { + constructor(components, renderer, scene, controls) { + this.components = components; + this.renderer = renderer; + this.controls = controls; + this.markers = new Set(); + this.clusterLabels = new Set(); + this.currentKeys = new Set(); + this._clusterOnZoom = true; + this._color = "white"; + // TODO: Replace with UUID for the marker key + this._markerKey = 0; + this._clusterKey = 0; + this._clusterThreeshold = 50; + this.isNavigating = false; + this.scene = scene; + this.setupEvents(); } - adjustRaycasterOnZoom() { - this.scene.controls.addEventListener("update", () => { - const { zoom, left, right, top, bottom } = this.scene.camera; - const width = left - right; - const height = top - bottom; - const screenSize = Math.max(width, height); - const realScreenSize = screenSize / zoom; - const range = 40; - const { caster } = this.highlighter; - caster.params.Line.threshold = realScreenSize / range; - }); + set clusterOnZoom(value) { + this._clusterOnZoom = value; } - newMouseMarker(color) { - const scene = this.scene.get(); - const root = document.createElement("div"); - const bar = document.createElement("div"); - root.appendChild(bar); - bar.style.backgroundColor = color; - bar.style.width = "3rem"; - bar.style.height = "3px"; - const mouseMarker = new Simple2DMarker(this.components, root, scene); - mouseMarker.visible = false; - return mouseMarker; + get clusterOnZoom() { + return this._clusterOnZoom; } - setMouseMarker(point, object, index, type) { - if (index === undefined) { - return; + set color(value) { + this._color = value; + for (const marker of this.markers) { + marker.label.get().element.style.color = value; } - this.mouseMarkers[type].visible = true; - const marker = this.mouseMarkers[type].get(); - marker.position.copy(point); - const curveMesh = object; - const { startPoint, endPoint } = curveMesh.curve.getSegment(index); - const angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); - const bar = marker.element.children[0]; - const trueAngle = 90 - (angle / Math.PI) * 180; - bar.style.transform = `rotate(${trueAngle}deg)`; } - async updateMarker(intersects, type) { - const { point, index, object } = intersects; - const mesh = object; - const curve = mesh.curve; - const alignment = mesh.curve.alignment; - const percentage = alignment.getPercentageAt(point, this.view); - const markerPoint = point.clone(); - this.setMouseMarker(markerPoint, mesh, index, type); - if (percentage !== null) { - await this.onMarkerChange.trigger({ alignment, percentage, type, curve }); + set clusterThreeshold(value) { + this._clusterThreeshold = value; + } + get clusterThreeshold() { + return this._clusterThreeshold; + } + setupEvents() { + if (this.scene) { + this.controls.addEventListener("sleep", () => { + this.manageCluster(); + }); + this.controls.addEventListener("rest", () => { + if (this.isNavigating) { + this.manageCluster(); + this.isNavigating = false; + } + }); } } -} - -class CurveHighlighter { - constructor(scene, type) { - this.caster = new THREE$1.Raycaster(); - this.scene = scene; - this.type = type; - this.hoverCurve = this.newCurve(0.003, 0x444444, false); - this.hoverPoints = this.newPoints(5, 0x444444); - this.selectCurve = this.newCurve(0.005, 0xffffff, true); - this.selectPoints = this.newPoints(7, 0xffffff); + resetMarkers() { + for (const marker of this.markers) { + marker.merged = false; + } + for (const cluster of this.clusterLabels) { + this.scene.remove(cluster.label.get()); + } + this.clusterLabels.clear(); + this._clusterKey = 0; } - dispose() { - if (this.selectCurve) { - this.scene.remove(this.selectCurve); + removeMergeMarkers() { + for (const marker of this.markers) { + if (marker.merged) { + this.scene.remove(marker.label.get()); + } + else { + this.scene.add(marker.label.get()); + } + } + for (const cluster of this.clusterLabels) { + if (cluster.markerKeys.length === 1) { + const marker = Array.from(this.markers).find((marker) => marker.key === cluster.markerKeys[0]); + if (marker) { + this.scene.add(marker.label.get()); + marker.merged = false; + } + this.scene.remove(cluster.label.get()); + this.clusterLabels.delete(cluster); + } } - this.selectCurve.material.dispose(); - this.selectCurve.geometry.dispose(); - this.selectCurve = null; - this.hoverCurve.material.dispose(); - this.hoverCurve.geometry.dispose(); - this.hoverCurve = null; - this.hoverPoints.material.dispose(); - this.hoverPoints.geometry.dispose(); - this.selectPoints.material.dispose(); - this.selectPoints.geometry.dispose(); - this.scene = null; } - castRay(event, camera, dom, meshes) { - const mouse = new THREE$1.Vector2(); - const rect = dom.getBoundingClientRect(); - mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; - mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; - this.caster.setFromCamera(mouse, camera); - const intersects = this.caster.intersectObjects(meshes); - if (!intersects.length) { - return null; + manageCluster() { + this.resetMarkers(); + for (const marker of this.markers) { + if (!marker.merged && !marker.static) { + this.currentKeys.clear(); + for (const marker2 of this.markers) { + if (marker2.static) { + continue; + } + if (marker.key !== marker2.key && !marker2.merged) { + const distance = this.distance(marker.label, marker2.label); + if (distance < this._clusterThreeshold) { + this.currentKeys.add(marker2.key); + marker2.merged = true; + } + } + } + if (this.currentKeys.size > 0) { + if (!this.scene) { + return; + } + this.currentKeys.add(marker.key); + marker.merged = true; + const clusterGroup = Array.from(this.currentKeys); + const averagePosition = this.getAveragePositionFromLabels(clusterGroup); + const clusterLabel = new Simple2DMarker(this.components, this.createClusterElement(this._clusterKey.toString()), this.scene); + clusterLabel.get().element.textContent = + clusterGroup.length.toString(); + clusterLabel.get().position.copy(averagePosition); + this.clusterLabels.add({ + key: this._clusterKey.toString(), + markerKeys: clusterGroup, + label: clusterLabel, + }); + this._clusterKey++; + } + } } - return intersects[0]; + this.removeMergeMarkers(); } - select(mesh) { - this.highlight(mesh, this.selectCurve, this.selectPoints, true); + getAveragePositionFromLabels(clusterGroup) { + const positions = clusterGroup.map((key) => { + const marker = Array.from(this.markers).find((marker) => marker.key === key); + if (marker) { + return marker.label.get().position; + } + return new THREE$1.Vector3(); + }); + const averagePosition = positions + .reduce((acc, curr) => acc.add(curr), new THREE$1.Vector3()) + .divideScalar(positions.length); + return averagePosition; } - unSelect() { - this.selectCurve.removeFromParent(); - this.selectPoints.removeFromParent(); + createClusterElement(key) { + const div = document.createElement("div"); + div.textContent = key; + div.style.color = "#000000"; + div.style.background = "#FFFFFF"; + div.style.fontSize = "1.2rem"; + div.style.fontWeight = "500"; + div.style.pointerEvents = "auto"; + div.style.borderRadius = "50%"; + div.style.padding = "5px 11px"; + div.style.textAlign = "center"; + div.style.cursor = "pointer"; + // div.style.transition = "all 0.05s"; + div.addEventListener("pointerdown", () => { + this.navigateToCluster(key); + }); + div.addEventListener("pointerover", () => { + div.style.background = "#BCF124"; + }); + div.addEventListener("pointerout", () => { + div.style.background = "#FFFFFF"; + }); + return div; } - hover(mesh) { - this.highlight(mesh, this.hoverCurve, this.hoverPoints, false); + addMarker(text, mesh) { + const span = document.createElement("span"); + span.innerHTML = text; + span.style.color = this._color; + const marker = this.addMarkerToScene(span); + marker.get().position.copy(mesh.position); + this.markers.add({ + label: marker, + mesh, + key: this._markerKey.toString(), + merged: false, + static: false, + }); + this._markerKey++; } - unHover() { - this.hoverCurve.removeFromParent(); - this.hoverPoints.removeFromParent(); + addMarkerAtPoint(text, point, type, isStatic = false) { + if (type !== undefined) { + const span = document.createElement("span"); + span.innerHTML = text; + span.style.color = this._color; + const marker = new Simple2DMarker(this.components, span, this.scene); + marker.get().position.copy(point); + this.markers.add({ + label: marker, + mesh: new THREE$1.Mesh(), + key: this._markerKey.toString(), + merged: false, + type, + static: isStatic, + }); + this._markerKey++; + } + } + // TODO: Move this to switch statement inside addCivilMarker + addKPStation(text, mesh) { + const container = document.createElement("div"); + const span = document.createElement("div"); + container.appendChild(span); + span.innerHTML = text; + span.style.color = this._color; + span.style.borderBottom = "1px dotted white"; + span.style.width = "160px"; + span.style.textAlign = "left"; + const marker = new Simple2DMarker(this.components, container, this.scene); + const point = new THREE$1.Vector3(); + point.x = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 1); + point.y = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 1); + point.z = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 1); + const secondLastPoint = new THREE$1.Vector3(); + secondLastPoint.x = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 2); + secondLastPoint.y = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 2); + secondLastPoint.z = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 2); + const midPoint = new THREE$1.Vector3(); + midPoint.x = (point.x + secondLastPoint.x) / 2; + midPoint.y = (point.y + secondLastPoint.y) / 2; + midPoint.z = (point.z + secondLastPoint.z) / 2; + marker.get().position.copy(midPoint); + const direction = new THREE$1.Vector3(); + direction.subVectors(point, secondLastPoint).normalize(); + const quaternion = new THREE$1.Quaternion(); + quaternion.setFromUnitVectors(new THREE$1.Vector3(0, 1, 0), direction); + const eulerZ = new THREE$1.Euler().setFromQuaternion(quaternion).z; + const rotationZ = THREE$1.MathUtils.radToDeg(eulerZ); + span.style.transform = `rotate(${-rotationZ - 90}deg) translate(-35%, -50%)`; + this.markers.add({ + label: marker, + mesh, + key: this._markerKey.toString(), + merged: false, + static: false, + }); + this._markerKey++; } - highlight(mesh, curve, points, useColors) { - const { alignment } = mesh.curve; - this.scene.add(curve); - this.scene.add(points); - const lines = []; - const colors = []; - const vertices = []; - for (const foundCurve of alignment[this.type]) { - const position = foundCurve.mesh.geometry.attributes.position; - for (const coord of position.array) { - lines.push(coord); - } - if (useColors) { - let type; - if (this.type === "absolute") { - // 3D curves don't have type defined, so we take the horizontal - const { horizontal } = foundCurve.alignment; - type = horizontal[foundCurve.index].data.TYPE; - } - else { - type = foundCurve.data.TYPE; - } - const found = CurveHighlighter.settings.colors[type] || [1, 1, 1]; - for (let i = 0; i < position.count; i++) { - colors.push(...found); - } - } - const [x, y, z] = position.array; - vertices.push(new THREE$1.Vector3(x, y, z)); + addCivilVerticalMarker(text, mesh, type, root) { + const span = document.createElement("span"); + span.innerHTML = text; + span.style.color = this._color; + const marker = new Simple2DMarker(this.components, span, root); + if (type === "Height") { + const span = document.createElement("span"); + span.innerHTML = text; + span.style.color = this._color; + const { position } = mesh.geometry.attributes; + const setArray = position.array.length / 3; + const firstIndex = (setArray - 1) * 3; + const lastIndex = position.array.slice(firstIndex, firstIndex + 3); + marker.get().position.set(lastIndex[0], lastIndex[1] + 10, lastIndex[2]); } - const lastX = lines[lines.length - 3]; - const lastY = lines[lines.length - 2]; - const lastZ = lines[lines.length - 1]; - vertices.push(new THREE$1.Vector3(lastX, lastY, lastZ)); - if (lines.length / 3 > curve.geometry.attributes.position.count) { - curve.geometry.dispose(); - curve.geometry = new LineGeometry(); + else if (type === "InitialKPV") { + const { position } = mesh.geometry.attributes; + const pX = position.getX(0); + const pY = position.getY(0); + const pZ = position.getZ(0); + marker.get().position.set(pX - 20, pY, pZ); } - curve.geometry.setPositions(lines); - if (useColors) { - curve.geometry.setColors(colors); + else if (type === "FinalKPV") { + const { position } = mesh.geometry.attributes; + const pX = position.getX(mesh.geometry.attributes.position.count - 1); + const pY = position.getY(mesh.geometry.attributes.position.count - 1); + const pZ = position.getZ(mesh.geometry.attributes.position.count - 1); + marker.get().position.set(pX + 20, pY, pZ); } - points.geometry.setFromPoints(vertices); - } - newCurve(linewidth, color, vertexColors) { - const selectGeometry = new LineGeometry(); - const selectMaterial = new LineMaterial({ - color, - linewidth, - vertexColors, - worldUnits: false, - depthTest: false, + else if (type === "Slope") { + span.style.color = "grey"; + const { position } = mesh.geometry.attributes; + const pointStart = new THREE$1.Vector3(); + pointStart.x = position.getX(0); + pointStart.y = position.getY(0); + pointStart.z = position.getZ(0); + const pointEnd = new THREE$1.Vector3(); + pointEnd.x = position.getX(position.count - 1); + pointEnd.y = position.getY(position.count - 1); + pointEnd.z = position.getZ(position.count - 1); + const midPoint = new THREE$1.Vector3(); + midPoint.addVectors(pointStart, pointEnd).multiplyScalar(0.5); + marker.get().position.set(midPoint.x, midPoint.y - 10, midPoint.z); + } + this.markers.add({ + label: marker, + mesh, + key: this._markerKey.toString(), + type, + merged: false, + static: false, }); - const curve = new Line2(selectGeometry, selectMaterial); - this.scene.add(curve); - return curve; + this._markerKey++; + return marker; } - newPoints(size, color) { - const pointsGeometry = new THREE$1.BufferGeometry(); - const pointsAttr = new THREE$1.BufferAttribute(new Float32Array(), 3); - pointsGeometry.setAttribute("position", pointsAttr); - const pointsMaterial = new THREE$1.PointsMaterial({ - size, - color, - sizeAttenuation: false, - depthTest: false, + addCivilMarker(text, mesh, type) { + const span = document.createElement("span"); + span.innerHTML = text; + span.style.color = this._color; + const marker = this.addMarkerToScene(span); + if (type === "InitialKP") { + const pX = mesh.geometry.attributes.position.getX(0); + const pY = mesh.geometry.attributes.position.getY(0); + const pZ = mesh.geometry.attributes.position.getZ(0); + marker.get().position.set(pX + 2, pY + 2, pZ); + } + else if (type === "FinalKP") { + const pX = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 1); + const pY = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 1); + const pZ = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 1); + marker.get().position.set(pX + 2, pY - 2, pZ); + } + else if (type === "Length") { + const pointStart = new THREE$1.Vector3(); + pointStart.x = mesh.geometry.attributes.position.getX(0); + pointStart.y = mesh.geometry.attributes.position.getY(0); + pointStart.z = mesh.geometry.attributes.position.getZ(0); + const pointEnd = new THREE$1.Vector3(); + pointEnd.x = mesh.geometry.attributes.position.getX(mesh.geometry.attributes.position.count - 1); + pointEnd.y = mesh.geometry.attributes.position.getY(mesh.geometry.attributes.position.count - 1); + pointEnd.z = mesh.geometry.attributes.position.getZ(mesh.geometry.attributes.position.count - 1); + const length = pointStart.distanceTo(pointEnd); + marker.get().element.innerText = length.toFixed(2); + marker + .get() + .position.copy(pointEnd.clone().add(pointStart).divideScalar(2)); + } + this.markers.add({ + label: marker, + mesh, + key: this._markerKey.toString(), + type, + merged: false, + static: false, }); - const points = new THREE$1.Points(pointsGeometry, pointsMaterial); - points.frustumCulled = false; - this.scene.add(points); - return points; + this._markerKey++; + return marker; } -} -CurveHighlighter.settings = { - colors: { - LINE: [213 / 255, 0 / 255, 255 / 255], - CIRCULARARC: [0 / 255, 46, 255 / 255], - CLOTHOID: [0 / 255, 255 / 255, 0 / 255], - PARABOLICARC: [0 / 255, 255 / 255, 72 / 255], - CONSTANTGRADIENT: [213 / 255, 0 / 255, 255 / 255], - }, -}; - -class PlanHighlighter extends CurveHighlighter { - constructor(scene, kpManager) { - super(scene, "horizontal"); - this.kpManager = kpManager; - this.offset = 10; - this.markupLines = []; - this.markupMaterial = new THREE$1.LineBasicMaterial({ - color: 0x686868, - }); + addMarkerToScene(span) { + if (!this.scene) { + throw new Error("Scene is needed to add markers!"); + } + const scene = this.scene; + const marker = new Simple2DMarker(this.components, span, scene); + return marker; } - showCurveInfo(curveMesh) { - this.disposeMarkups(); - this.currentCurveMesh = curveMesh; - switch (curveMesh.curve.data.TYPE) { - case "LINE": - this.showLineInfo(curveMesh, this.offset); - break; - case "CIRCULARARC": - this.showCircularArcInfo(curveMesh, this.offset); - break; - case "CLOTHOID": - this.showClothoidInfo(curveMesh, this.offset); - break; - default: - console.log("Unknown curve type:", curveMesh.curve.data.TYPE); - break; + getScreenPosition(label) { + const screenPosition = new THREE$1.Vector3(); + if (!this.scene) { + const labelPosition = label + .get() + .position.clone() + .project(this.components.camera.get()); + const dimensions = this.components.renderer.getSize(); + screenPosition.x = + (labelPosition.x * dimensions.x) / 2 + dimensions.x / 2; + screenPosition.y = + -((labelPosition.y * dimensions.y) / 2) + dimensions.y / 2; + } + else { + const labelPosition = label + .get() + .position.clone() + .project(this.controls.camera); + const dimensions = this.renderer.getSize(); + screenPosition.x = + (labelPosition.x * dimensions.x) / 2 + dimensions.x / 2; + screenPosition.y = + -((labelPosition.y * dimensions.y) / 2) + dimensions.y / 2; } + return screenPosition; } - updateOffset(screenSize, _zoom, _triggerRedraw) { - const biggerSize = Math.max(screenSize.height, screenSize.width); - const newOffset = biggerSize / (_zoom * 150); - if (newOffset !== this.offset) { - this.offset = newOffset; - if (_triggerRedraw && this.currentCurveMesh) { - this.showCurveInfo(this.currentCurveMesh); - } + distance(label1, label2) { + const screenPosition1 = this.getScreenPosition(label1); + const screenPosition2 = this.getScreenPosition(label2); + const dx = screenPosition1.x - screenPosition2.x; + const dy = screenPosition1.y - screenPosition2.y; + const distance = Math.sqrt(dx * dx + dy * dy) * 0.5; + // Managing Overlapping Labels + if (distance === 0) { + const updateDistance = this._clusterThreeshold + 1; + return updateDistance; } + return distance; } - dispose() { - super.dispose(); - for (const line of this.markupLines) { - this.scene.remove(line); - line.removeFromParent(); + navigateToCluster(key) { + const boundingRegion = []; + const cluster = Array.from(this.clusterLabels).find((cluster) => cluster.key === key); + if (cluster) { + for (const markerKey of cluster.markerKeys) { + const marker = Array.from(this.markers).find((marker) => marker.key === markerKey); + if (marker) { + boundingRegion.push(marker.label.get().position); + } + } + this.scene.remove(cluster?.label.get()); + this.clusterLabels.delete(cluster); } - this.disposeMarkups(); - this.markupMaterial.dispose(); - } - disposeMarkups() { - for (const line of this.markupLines) { - line.geometry.dispose(); - this.scene.remove(line); + if (this.scene) { + const box3 = this.createBox3FromPoints(boundingRegion); + const size = new THREE$1.Vector3(); + box3.getSize(size); + const center = new THREE$1.Vector3(); + box3.getCenter(center); + const geometry = new THREE$1.BoxGeometry(size.x, size.y, size.z); + geometry.translate(center.x, center.y, center.z); + const mesh = new THREE$1.Mesh(geometry); + mesh.geometry.computeBoundingSphere(); + const boundingSphere = mesh.geometry?.boundingSphere; + if (this.controls && boundingSphere) { + this.controls.fitToSphere(mesh, true); + } + this.isNavigating = true; + geometry.dispose(); + mesh.clear(); } - this.markupLines = []; - } - unSelect() { - super.unSelect(); - this.disposeMarkups(); - } - calculateTangent(positions, index) { - const numComponents = 3; - const pointIndex = index * numComponents; - const prevPointIndex = Math.max(0, pointIndex - numComponents); - const nextPointIndex = Math.min(positions.length - numComponents, pointIndex + numComponents); - const prevPoint = new THREE$1.Vector3().fromArray(positions, prevPointIndex); - const nextPoint = new THREE$1.Vector3().fromArray(positions, nextPointIndex); - const tangent = nextPoint.clone().sub(prevPoint).normalize(); - return tangent; + boundingRegion.length = 0; } - calculateParallelCurve(positions, count, offset) { - const parallelCurvePoints = []; - for (let i = 0; i < count; i++) { - const tangentVector = this.calculateTangent(positions, i); - const perpendicularVector = tangentVector - .clone() - .applyAxisAngle(new THREE$1.Vector3(0, 0, 1), Math.PI / 2); - perpendicularVector.normalize(); - const offsetVector = perpendicularVector.clone().multiplyScalar(offset); - const pointIndex = i * 3; - const parallelPoint = new THREE$1.Vector3() - .fromArray(positions, pointIndex) - .add(offsetVector); - parallelCurvePoints.push(parallelPoint); + createBox3FromPoints(points) { + const bbox = new THREE$1.Box3(); + for (const point of points) { + bbox.expandByPoint(point); } - return parallelCurvePoints; + return bbox; } - calculateDimensionLines(curve, line) { - const startDimensionPoints = []; - const curvePositions = curve.geometry.attributes.position.array; - const linePositions = line.geometry.attributes.position.array; - if (curvePositions.length < 6 && linePositions.length < 6) { - throw new Error("Line must have at least two vertices"); + clearMarkers() { + for (const marker of this.markers) { + this.scene.remove(marker.label.get()); } - const startCurvePoint = new THREE$1.Vector3(curvePositions[0], curvePositions[1], curvePositions[2]); - const startLinePoint = new THREE$1.Vector3(linePositions[0], linePositions[1], linePositions[2]); - const endDimensionPoints = []; - const lastCurveIndex = curvePositions.length - 3; - const endCurvePoint = new THREE$1.Vector3(curvePositions[lastCurveIndex], curvePositions[lastCurveIndex + 1], curvePositions[lastCurveIndex + 2]); - const lastLineIndex = linePositions.length - 3; - const endLinePoint = new THREE$1.Vector3(linePositions[lastLineIndex], linePositions[lastLineIndex + 1], linePositions[lastLineIndex + 2]); - startDimensionPoints.push(startCurvePoint, startLinePoint); - endDimensionPoints.push(endCurvePoint, endLinePoint); - return { startDimensionPoints, endDimensionPoints }; - } - offsetDimensionLine(points, offset) { - const direction = new THREE$1.Vector3() - .copy(points[points.length - 1]) - .sub(points[0]) - .normalize(); - const offsetVector = direction.clone().multiplyScalar(offset); - const newPoints = points.map((point) => point.clone().add(offsetVector)); - return newPoints; - } - showLineInfo(curveMesh, offset) { - this.kpManager.clearMarkersByType("Length"); - this.kpManager.clearMarkersByType("Radius"); - const positions = curveMesh.geometry.attributes.position.array; - const parallelCurvePoints = this.calculateParallelCurve(positions, positions.length / 3, offset); - const lengthGeometry = new THREE$1.BufferGeometry().setFromPoints(parallelCurvePoints); - const lineParallelLine = new THREE$1.Line(lengthGeometry, this.markupMaterial); - this.kpManager.showLineLength(lineParallelLine, curveMesh.curve.getLength()); - this.scene.add(lineParallelLine); - this.markupLines.push(lineParallelLine); - const { startDimensionPoints, endDimensionPoints } = this.calculateDimensionLines(curveMesh, lineParallelLine); - const offsetStartDimensionPoints = this.offsetDimensionLine(startDimensionPoints, offset * 0.1); - const offsetEndDimensionPoints = this.offsetDimensionLine(endDimensionPoints, offset * 0.1); - const startDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetStartDimensionPoints); - const endDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetEndDimensionPoints); - const lineStartDimensionlLine = new THREE$1.Line(startDimensionGeometry, this.markupMaterial); - this.scene.add(lineStartDimensionlLine); - this.markupLines.push(lineStartDimensionlLine); - const lineEndDimensionlLine = new THREE$1.Line(endDimensionGeometry, this.markupMaterial); - this.scene.add(lineEndDimensionlLine); - this.markupLines.push(lineEndDimensionlLine); - } - showClothoidInfo(curveMesh, offset) { - this.kpManager.clearMarkersByType("Length"); - this.kpManager.clearMarkersByType("Radius"); - const positions = curveMesh.geometry.attributes.position.array; - const parallelCurvePoints = this.calculateParallelCurve(positions, positions.length / 3, offset); - const lengthGeometry = new THREE$1.BufferGeometry().setFromPoints(parallelCurvePoints); - this.kpManager.showCurveLength(parallelCurvePoints, curveMesh.curve.getLength()); - const clothParallelLine = new THREE$1.Line(lengthGeometry, this.markupMaterial); - this.scene.add(clothParallelLine); - this.markupLines.push(clothParallelLine); - const { startDimensionPoints, endDimensionPoints } = this.calculateDimensionLines(curveMesh, clothParallelLine); - const offsetStartDimensionPoints = this.offsetDimensionLine(startDimensionPoints, offset * 0.1); - const offsetEndDimensionPoints = this.offsetDimensionLine(endDimensionPoints, offset * 0.1); - const startDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetStartDimensionPoints); - const endDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetEndDimensionPoints); - const clothStartDimensionlLine = new THREE$1.Line(startDimensionGeometry, this.markupMaterial); - this.scene.add(clothStartDimensionlLine); - this.markupLines.push(clothStartDimensionlLine); - const clothEndDimensionlLine = new THREE$1.Line(endDimensionGeometry, this.markupMaterial); - this.scene.add(clothEndDimensionlLine); - this.markupLines.push(clothEndDimensionlLine); + this.markers.clear(); + this._markerKey = 0; } - showCircularArcInfo(curveMesh, offset) { - this.kpManager.clearMarkersByType("Length"); - this.kpManager.clearMarkersByType("Radius"); - const radius = curveMesh.curve.data.RADIUS; - const positions = curveMesh.geometry.attributes.position.array; - const count = curveMesh.geometry.attributes.position.count; - const linePoints = []; - const firstPoint = new THREE$1.Vector3(positions[0], positions[1], positions[2]); - const lastPointIndex = (count - 1) * 3; - const lastPoint = new THREE$1.Vector3(positions[lastPointIndex], positions[lastPointIndex + 1], positions[lastPointIndex + 2]); - const middlePointIndex = (count / 2) * 3; - const middlePoint = new THREE$1.Vector3(positions[middlePointIndex], positions[middlePointIndex + 1], positions[middlePointIndex + 2]); - const tangentVector = lastPoint.clone().sub(firstPoint).normalize(); - const perpendicularVector = new THREE$1.Vector3(-tangentVector.y, tangentVector.x, 0); - perpendicularVector.multiplyScalar(radius); - const arcCenterPoint = middlePoint.clone().add(perpendicularVector); - linePoints.push(middlePoint); - linePoints.push(arcCenterPoint); - const radiusGeometry = new THREE$1.BufferGeometry().setFromPoints(linePoints); - const radiusLine = new THREE$1.Line(radiusGeometry, this.markupMaterial); - this.kpManager.showCurveRadius(radiusLine, Math.abs(radius)); - this.scene.add(radiusLine); - this.markupLines.push(radiusLine); - const parallelCurvePoints = []; - for (let i = 0; i < count; i++) { - const tangentVector = this.calculateTangent(positions, i); - const radius = curveMesh.curve.data.RADIUS; - const perpendicularVector = new THREE$1.Vector3(tangentVector.y, -tangentVector.x, 0); - perpendicularVector.normalize(); - if (radius < 0) { - perpendicularVector.negate(); + clearMarkersByType(type) { + for (const marker of this.markers) { + if (marker.type === type) { + this.scene.remove(marker.label.get()); + this.markers.delete(marker); } - const offsetVector = perpendicularVector.clone().multiplyScalar(offset); - const pointIndex = i * 3; - const parallelPoint = new THREE$1.Vector3(positions[pointIndex] + offsetVector.x, positions[pointIndex + 1] + offsetVector.y, positions[pointIndex + 2] + offsetVector.z); - parallelCurvePoints.push(parallelPoint); } - const lengthGeometry = new THREE$1.BufferGeometry().setFromPoints(parallelCurvePoints); - this.kpManager.showCurveLength(parallelCurvePoints, curveMesh.curve.getLength()); - const circArcParallelLine = new THREE$1.Line(lengthGeometry, this.markupMaterial); - this.scene.add(circArcParallelLine); - this.markupLines.push(circArcParallelLine); - const { startDimensionPoints, endDimensionPoints } = this.calculateDimensionLines(curveMesh, circArcParallelLine); - const offsetStartDimensionPoints = this.offsetDimensionLine(startDimensionPoints, offset * 0.1); - const offsetEndDimensionPoints = this.offsetDimensionLine(endDimensionPoints, offset * 0.1); - const startDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetStartDimensionPoints); - const endDimensionGeometry = new THREE$1.BufferGeometry().setFromPoints(offsetEndDimensionPoints); - const circArcStartDimensionlLine = new THREE$1.Line(startDimensionGeometry, this.markupMaterial); - this.scene.add(circArcStartDimensionlLine); - this.markupLines.push(circArcStartDimensionlLine); - const circArcEndDimensionlLine = new THREE$1.Line(endDimensionGeometry, this.markupMaterial); - this.scene.add(circArcEndDimensionlLine); - this.markupLines.push(circArcEndDimensionlLine); } -} - -class CivilFloatingWindow { - static get(components, scene, name) { - const floatingWindow = new FloatingWindow(components); - floatingWindow.title = name; - components.ui.add(floatingWindow); - floatingWindow.visible = false; - const hContainer = scene.uiElement.get("container"); - floatingWindow.addChild(hContainer); - floatingWindow.onResized.add(() => scene.grid.regenerate()); - floatingWindow.slots.content.domElement.style.padding = "0"; - floatingWindow.slots.content.domElement.style.overflow = "hidden"; - floatingWindow.onResized.add(() => { - const { width, height } = floatingWindow.containerSize; - scene.setSize(height, width); - }); - floatingWindow.domElement.style.width = "20rem"; - floatingWindow.domElement.style.height = "20rem"; - floatingWindow.onVisible.add(() => { - if (floatingWindow.visible) { - scene.grid.regenerate(); - } - }); - if (components.renderer.isUpdateable()) { - components.renderer.onAfterUpdate.add(async () => { - if (floatingWindow.visible) { - await scene.update(); - } - }); + dispose() { + for (const marker of this.markers) { + marker.label.dispose(); } - floatingWindow.onResized.trigger(); - return floatingWindow; + this.markers.clear(); + this._markerKey = 0; + for (const cluster of this.clusterLabels) { + cluster.label.dispose(); + } + this.clusterLabels.clear(); + this._clusterKey = 0; + this.currentKeys.clear(); } } @@ -122233,9 +122233,6 @@ class KPManager extends MarkerManager { clearKPStations() { this.clearMarkers(); } - dispose() { - this.dispose(); - } } class RoadPlanNavigator extends RoadNavigator { @@ -122303,6 +122300,24 @@ class RoadElevationNavigator extends RoadNavigator { const scene = this.scene.get(); this.highlighter = new CurveHighlighter(scene, "vertical"); this.kpManager = new KPManager(components, this.scene.renderer, this.scene.get(), this.scene.controls, this.view); + this.highlighter.onSelect.add((mesh) => { + // Add markers elevation + this.kpManager.dispose(); + const { alignment } = mesh.curve; + const positionsVertical = []; + for (const align of alignment.vertical) { + const pos = align.mesh.geometry.attributes.position.array; + positionsVertical.push(pos); + } + const { defSegments, slope } = this.setDefSegments(positionsVertical); + const scene = this.scene.get(); + alignment.vertical.forEach((align, index) => { + this.kpManager.addCivilVerticalMarker(`S: ${slope[index].slope}%`, align.mesh, "Slope", scene); + this.kpManager.addCivilVerticalMarker(`H: ${defSegments[index].end.y.toFixed(2)}`, align.mesh, "Height", scene); + }); + this.kpManager.addCivilVerticalMarker("KP: 0", alignment.vertical[0].mesh, "InitialKPV", scene); + this.kpManager.addCivilVerticalMarker(`KP: ${alignment.vertical.length}`, alignment.vertical[alignment.vertical.length - 1].mesh, "FinalKPV", scene); + }); } get() { return null; diff --git a/resources/small.frag b/resources/small.frag index 85f5bd966..1b985fbe9 100644 Binary files a/resources/small.frag and b/resources/small.frag differ diff --git a/src/civil/RoadElevationNavigator/index.ts b/src/civil/RoadElevationNavigator/index.ts index 2e86068fa..73fb01c5f 100644 --- a/src/civil/RoadElevationNavigator/index.ts +++ b/src/civil/RoadElevationNavigator/index.ts @@ -31,6 +31,56 @@ export class RoadElevationNavigator extends RoadNavigator implements UI { this.scene.controls, this.view ); + + this.highlighter.onSelect.add((mesh) => { + // Add markers elevation + + this.kpManager.dispose(); + + const { alignment } = mesh.curve; + const positionsVertical = []; + + for (const align of alignment.vertical) { + const pos = align.mesh.geometry.attributes.position.array; + positionsVertical.push(pos); + } + + const { defSegments, slope } = this.setDefSegments(positionsVertical); + + const scene = this.scene.get(); + + for (let i = 0; i < alignment.vertical.length; i++) { + const align = alignment.vertical[i]; + + this.kpManager.addCivilVerticalMarker( + `S: ${slope[i].slope}%`, + align.mesh, + "Slope", + scene + ); + + this.kpManager.addCivilVerticalMarker( + `H: ${defSegments[i].end.y.toFixed(2)}`, + align.mesh, + "Height", + scene + ); + } + + this.kpManager.addCivilVerticalMarker( + "KP: 0", + alignment.vertical[0].mesh, + "InitialKPV", + scene + ); + + this.kpManager.addCivilVerticalMarker( + `KP: ${alignment.vertical.length}`, + alignment.vertical[alignment.vertical.length - 1].mesh, + "FinalKPV", + scene + ); + }); } get() { diff --git a/src/civil/RoadNavigator/index.ts b/src/civil/RoadNavigator/index.ts index a55786245..42f78f952 100644 --- a/src/civil/RoadNavigator/index.ts +++ b/src/civil/RoadNavigator/index.ts @@ -5,7 +5,6 @@ import { Component, Event } from "../../base-types"; import { Components, Simple2DMarker, Simple2DScene } from "../../core"; import { CurveHighlighter } from "./src/curve-highlighter"; import { KPManager } from "./src/kp-manager"; -import { MarkerManager } from "../../core/Simple2DMarker/src/marker-manager"; export type CivilMarkerType = "hover" | "select"; @@ -21,7 +20,6 @@ export abstract class RoadNavigator extends Component { // abstract showKPStations(curveMesh: FRAGS.CurveMesh): void; // abstract clearKPStations(): void; - // abstract kpManager: KPManager; abstract kpManager: KPManager; readonly onHighlight = new Event<{ @@ -44,8 +42,6 @@ export abstract class RoadNavigator extends Component { private _previousAlignment: FRAGS.Alignment | null = null; - markerManager: MarkerManager; - mouseMarkers: { hover: Simple2DMarker; select: Simple2DMarker; @@ -54,17 +50,6 @@ export abstract class RoadNavigator extends Component { protected constructor(components: Components) { super(components); this.scene = new Simple2DScene(this.components, false); - // @ts-ignore - const renderer = this.components._renderer._renderer; - // @ts-ignore - const controls = this.components._camera.controls; - - this.markerManager = new MarkerManager( - this.components, - this.scene.renderer, - this.scene.scene, - controls - ); this.mouseMarkers = { select: this.newMouseMarker("#ffffff"), @@ -217,8 +202,8 @@ export abstract class RoadNavigator extends Component { } setDefSegments(segmentsArray: any[]) { - let defSegments: any = []; - let slope: any = []; + const defSegments: any = []; + const slope: any = []; const calculateSlopeSegment = (point1: number[], point2: number[]) => { const deltaY = point2[1] - point1[1]; @@ -228,7 +213,10 @@ export abstract class RoadNavigator extends Component { for (let i = 0; i < segmentsArray.length; i++) { const segment = segmentsArray[i]; - let startX: number, startY: number, endX: number, endY: number; + let startX: number; + let startY: number; + let endX: number; + let endY: number; // Set start for (let j = 0; j < Object.keys(segment).length / 3; j++) { @@ -259,22 +247,22 @@ export abstract class RoadNavigator extends Component { slope.push({ slope: slopeSegment }); } - segmentsArray.forEach((segment: any) => { + for (const segment of segmentsArray) { for (let i = 0; i < segment.length - 3; i += 3) { - let startX = segment[i]; - let startY = segment[i + 1]; - let startZ = segment[i + 2]; + const startX = segment[i]; + const startY = segment[i + 1]; + const startZ = segment[i + 2]; - let endX = segment[i + 3]; - let endY = segment[i + 4]; - let endZ = segment[i + 5]; + const endX = segment[i + 3]; + const endY = segment[i + 4]; + const endZ = segment[i + 5]; defSegments.push({ start: new THREE.Vector3(startX, startY, startZ), end: new THREE.Vector3(endX, endY, endZ), }); } - }); + } return { defSegments, slope }; } diff --git a/src/civil/RoadNavigator/src/curve-highlighter.ts b/src/civil/RoadNavigator/src/curve-highlighter.ts index 92b03fe92..fcdef57f1 100644 --- a/src/civil/RoadNavigator/src/curve-highlighter.ts +++ b/src/civil/RoadNavigator/src/curve-highlighter.ts @@ -3,6 +3,7 @@ import * as FRAGS from "bim-fragment"; import { Line2 } from "three/examples/jsm/lines/Line2"; import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; +import { Event } from "../../../base-types"; type CivilHighlightType = "horizontal" | "absolute" | "vertical"; @@ -19,6 +20,8 @@ export class CurveHighlighter { } as { [curve: string]: number[] }, }; + readonly onSelect = new Event(); + type: CivilHighlightType; selectCurve: Line2; @@ -82,6 +85,7 @@ export class CurveHighlighter { select(mesh: FRAGS.CurveMesh) { this.highlight(mesh, this.selectCurve, this.selectPoints, true); + this.onSelect.trigger(mesh); } unSelect() { diff --git a/src/civil/RoadNavigator/src/kp-manager.ts b/src/civil/RoadNavigator/src/kp-manager.ts index 2bf0b5d98..25f32080f 100644 --- a/src/civil/RoadNavigator/src/kp-manager.ts +++ b/src/civil/RoadNavigator/src/kp-manager.ts @@ -284,8 +284,4 @@ export class KPManager extends MarkerManager { clearKPStations() { this.clearMarkers(); } - - dispose() { - this.dispose(); - } } diff --git a/src/civil/RoadPlanNavigator/index.html b/src/civil/RoadPlanNavigator/index.html index d68d68c2d..638c489f6 100644 --- a/src/civil/RoadPlanNavigator/index.html +++ b/src/civil/RoadPlanNavigator/index.html @@ -62,6 +62,7 @@ components, container ); + components.camera = new OBC.SimpleCamera(components); components.raycaster = new OBC.SimpleRaycaster(components); @@ -177,52 +178,7 @@ true ); - // Add markers elevation - - planNavigator.markerManager.dispose(); - - const { alignment } = mesh.curve; - let positionsVertical = []; - - alignment.vertical.forEach((align) => { - const pos = align.mesh.geometry.attributes.position.array; - positionsVertical.push(pos); - }); - - const { defSegments, slope } = - planNavigator.setDefSegments(positionsVertical); - - const sceneElevation = planNavigator.elevation; - alignment.vertical.forEach((align, index) => { - planNavigator.markerManager.addCivilVerticalMarker( - `S: ${slope[index].slope}%`, - align.mesh, - "Slope", - sceneElevation - ); - - planNavigator.markerManager.addCivilVerticalMarker( - `H: ${defSegments[index].end.y.toFixed(2)}`, - align.mesh, - "Height", - sceneElevation - ); - }); - - planNavigator.markerManager.addCivilVerticalMarker( - "KP: 0", - alignment.vertical[0].mesh, - "InitialKPV", - sceneElevation - ); - - planNavigator.markerManager.addCivilVerticalMarker( - `KP: ${alignment.vertical.length}`, - alignment.vertical[alignment.vertical.length - 1].mesh, - "FinalKPV", - sceneElevation - ); }); // planNavigator.onMarkerChange.add(({ alignment, percentage, type, curve }) => { diff --git a/src/core/Simple2DMarker/src/marker-manager.ts b/src/core/Simple2DMarker/src/marker-manager.ts index 0a79e42d2..148cabdc0 100644 --- a/src/core/Simple2DMarker/src/marker-manager.ts +++ b/src/core/Simple2DMarker/src/marker-manager.ts @@ -343,13 +343,13 @@ export class MarkerManager { text: string, mesh: FRAGS.CurveMesh, type: CivilLabels, - scene: THREE.Scene + root: THREE.Object3D ) { const span = document.createElement("span"); span.innerHTML = text; span.style.color = this._color; - const marker = new Simple2DMarker(this.components, span, scene); + const marker = new Simple2DMarker(this.components, span, root); if (type === "Height") { const span = document.createElement("span"); @@ -587,15 +587,17 @@ export class MarkerManager { } dispose() { - this.markers.forEach((marker) => { + for (const marker of this.markers) { marker.label.dispose(); - }); + } + this.markers.clear(); this._markerKey = 0; - this.clusterLabels.forEach((cluster) => { + for (const cluster of this.clusterLabels) { cluster.label.dispose(); - }); + } + this.clusterLabels.clear(); this._clusterKey = 0;