From 599f047e0ab6ca0ef7d66afe262a69df25189ca9 Mon Sep 17 00:00:00 2001 From: ethan Date: Wed, 16 Aug 2023 16:17:33 +0800 Subject: [PATCH] add attack arrow --- examples/index.ts | 16 +++-- index.html | 28 +++++++- src/arrow/attack-arrow.ts | 139 ++++++++++++++++++++++++++++++++++++++ src/arrow/fine-arrow.ts | 34 ++++++++-- src/draw.ts | 95 +++++++++++++++++--------- src/index.ts | 12 ++-- 6 files changed, 271 insertions(+), 53 deletions(-) create mode 100644 src/arrow/attack-arrow.ts diff --git a/examples/index.ts b/examples/index.ts index cb14b7e..b9f5cf3 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,16 +1,16 @@ -import CesiumPlot from "../src"; +import CesiumPlot from '../src'; // import CesiumPlot from "../dist/CesiumPlot"; -import * as Cesium from "./cesium/index"; +import * as Cesium from './cesium/index'; // let raster = new Cesium.ArcGisMapServerImageryProvider({ // url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer", // }); let keda = new Cesium.UrlTemplateImageryProvider({ - url: "https://10.68.8.41:9043/kmap-server/gridMap/tile/{z}/{y}/{x}", + url: 'https://10.68.8.41:9043/kmap-server/gridMap/tile/{z}/{y}/{x}', // url: 'http://10.68.8.58:8080/3d/dom2/{z}/{x}/{y}.png' }); -const viewer = new Cesium.Viewer("cesiumContainer", { +const viewer = new Cesium.Viewer('cesiumContainer', { animation: false, shouldAnimate: true, geocoder: false, @@ -34,6 +34,12 @@ viewer.scene.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(107.857, 35.594498, 8000000), }); -document.getElementById("drawStraightArrow").onclick = () => { +const fineArrow = document.getElementById('drawFineArrow') as HTMLElement; +fineArrow.onclick = () => { new CesiumPlot.FineArrow(Cesium, viewer, {}); }; + +const attackArrow = document.getElementById('drawAttackArrow') as HTMLElement; +attackArrow.onclick = () => { + new CesiumPlot.AttackArrow(Cesium, viewer, {}); +}; \ No newline at end of file diff --git a/index.html b/index.html index 448ebf7..cd19f0a 100644 --- a/index.html +++ b/index.html @@ -15,19 +15,41 @@ padding: 0; overflow: hidden; } - #drawStraightArrow { + /* #btn { position: absolute; top: 10px; left: 10px; + } */ + + .button-container { + position: absolute; + top: 10px; + left: 10px; + display: flex; /* 使用 Flex 布局 */ + justify-content: center; /* 水平居中对齐 */ + } + + .button { + margin: 0 10px; /* 按钮之间的间距 */ + padding: 6px 10px; /* 按钮内边距 */ + background-color: #3498db; /* 按钮背景颜色 */ + color: #ffffff; /* 按钮文字颜色 */ + border: none; /* 去除边框 */ + border-radius: 5px; /* 圆角边框 */ + cursor: pointer; /* 鼠标指针样式 */ }
- +
+ + +
+ diff --git a/src/arrow/attack-arrow.ts b/src/arrow/attack-arrow.ts new file mode 100644 index 0000000..539bab4 --- /dev/null +++ b/src/arrow/attack-arrow.ts @@ -0,0 +1,139 @@ +import Draw from '../draw'; +import * as Utils from '../utils'; +import { Cartesian3 } from '@examples/cesium'; + +export default class AttackArrow extends Draw { + points: Cartesian3[] = []; + headHeightFactor: number; + headWidthFactor: number; + neckHeightFactor: number; + neckWidthFactor: number; + headTailFactor: number; + + constructor(cesium: any, viewer: any, style: any) { + super(cesium, viewer); + this.cesium = cesium; + this.headHeightFactor = 0.18; + this.headWidthFactor = 0.3; + this.neckHeightFactor = 0.85; + this.neckWidthFactor = 0.15; + this.headTailFactor = 0.8; + this.setState('drawing'); + this.onDoubleClick(); + } + + /** + * Add points only on click events + */ + addPoint(cartesian: Cartesian3) { + this.points.push(cartesian); + if (this.points.length < 2) { + this.onMouseMove(); + } else if (this.points.length === 2) { + this.setGeometryPoints(this.points); + this.addToMap(); + this.lineEntity && this.viewer.entities.remove(this.lineEntity); + } else { + console.error('click...'); + } + } + + /** + * Draw a shape based on mouse movement points during the initial drawing. + */ + updateMovingPoint(cartesian: Cartesian3) { + const tempPoints = [...this.points, cartesian]; + // + this.setGeometryPoints(tempPoints); + if (tempPoints.length === 2) { + this.drawLine(); + } else { + const geometryPoints = this.createPolygon(tempPoints); + this.setGeometryPoints(geometryPoints); + this.addToMap(); + } + } + + /** + * Generate geometric shapes based on key points. + */ + createPolygon(positions: Cartesian3[]): Cartesian3[] { + const lnglatPoints = positions.map((pnt) => { + return this.cartesianToLnglat(pnt); + }); + + let [tailLeft, tailRight] = [lnglatPoints[0], lnglatPoints[1]]; + if (Utils.isClockWise(lnglatPoints[0], lnglatPoints[1], lnglatPoints[2])) { + tailLeft = lnglatPoints[1]; + tailRight = lnglatPoints[0]; + } + + const midTail = Utils.Mid(tailLeft, tailRight); + const bonePnts = [midTail].concat(lnglatPoints.slice(2)); + const headPnts = this.getArrowHeadPoints(bonePnts, tailLeft, tailRight); + const [neckLeft, neckRight] = [headPnts[0], headPnts[4]]; + const tailWidthFactor = Utils.MathDistance(tailLeft, tailRight) / Utils.getBaseLength(bonePnts); + const bodyPnts = this.getArrowBodyPoints(bonePnts, neckLeft, neckRight, tailWidthFactor); + const count = bodyPnts.length; + let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2)); + leftPnts.push(neckLeft); + let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count)); + rightPnts.push(neckRight); + leftPnts = Utils.getQBSplinePoints(leftPnts); + rightPnts = Utils.getQBSplinePoints(rightPnts); + const points = leftPnts.concat(headPnts, rightPnts.reverse()); + const temp = [].concat(...points); + const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp); + return cartesianPoints; + } + + getPoints() { + return this.points; + } + + getArrowHeadPoints(points, tailLeft, tailRight) { + try { + let len = Utils.getBaseLength(points); + let headHeight = len * this.headHeightFactor; + const headPnt = points[points.length - 1]; + len = Utils.MathDistance(headPnt, points[points.length - 2]); + const tailWidth = Utils.MathDistance(tailLeft, tailRight); + if (headHeight > tailWidth * this.headTailFactor) { + headHeight = tailWidth * this.headTailFactor; + } + const headWidth = headHeight * this.headWidthFactor; + const neckWidth = headHeight * this.neckWidthFactor; + headHeight = headHeight > len ? len : headHeight; + const neckHeight = headHeight * this.neckHeightFactor; + const headEndPnt = Utils.getThirdPoint(points[points.length - 2], headPnt, 0, headHeight, true); + const neckEndPnt = Utils.getThirdPoint(points[points.length - 2], headPnt, 0, neckHeight, true); + const headLeft = Utils.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, false); + const headRight = Utils.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, true); + const neckLeft = Utils.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, false); + const neckRight = Utils.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, true); + return [neckLeft, headLeft, headPnt, headRight, neckRight]; + } catch (e) { + console.log(e); + } + } + + getArrowBodyPoints(points, neckLeft, neckRight, tailWidthFactor) { + const allLen = Utils.wholeDistance(points); + const len = Utils.getBaseLength(points); + const tailWidth = len * tailWidthFactor; + const neckWidth = Utils.MathDistance(neckLeft, neckRight); + const widthDif = (tailWidth - neckWidth) / 2; + // eslint-disable-next-line + let [tempLen, leftBodyPnts, rightBodyPnts] = [0, [], []]; + for (let i = 1; i < points.length - 1; i++) { + const angle = Utils.getAngleOfThreePoints(points[i - 1], points[i], points[i + 1]) / 2; + tempLen += Utils.MathDistance(points[i - 1], points[i]); + const w = (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle); + const left = Utils.getThirdPoint(points[i - 1], points[i], Math.PI - angle, w, true); + const right = Utils.getThirdPoint(points[i - 1], points[i], angle, w, false); + leftBodyPnts.push(left); + rightBodyPnts.push(right); + } + return leftBodyPnts.concat(rightBodyPnts); + } +} diff --git a/src/arrow/fine-arrow.ts b/src/arrow/fine-arrow.ts index 6d53980..e8015f6 100644 --- a/src/arrow/fine-arrow.ts +++ b/src/arrow/fine-arrow.ts @@ -4,10 +4,22 @@ import { Cartesian3 } from '@examples/cesium'; export default class FineArrow extends Draw { points: Cartesian3[] = []; + arrowLengthScale: number = 5; + maxArrowLength: number = 2; + tailWidthFactor: number; + neckWidthFactor: number; + headWidthFactor: number; + headAngle: number; + neckAngle: number; constructor(cesium: any, viewer: any, style: any) { super(cesium, viewer); this.cesium = cesium; + this.tailWidthFactor = 0.1; + this.neckWidthFactor = 0.2; + this.headWidthFactor = 0.25; + this.headAngle = Math.PI / 8.5; + this.neckAngle = Math.PI / 13; this.setState('drawing'); } @@ -15,24 +27,32 @@ export default class FineArrow extends Draw { * Add points only on click events */ addPoint(cartesian: Cartesian3) { - this.points.push(); - if (this.points.length === 0) { - this.points[0] = cartesian; + if (this.points.length < 2) { + this.points.push(cartesian); this.onMouseMove(); } if (this.points.length === 2) { const geometryPoints = this.createPolygon(this.points); this.setGeometryPoints(geometryPoints); this.addToMap(); - this.removeMoveListener(); - this.setState('static'); + this.finishDrawing(); } } /** - * Update the last point as the mouse moves. + * Draw a shape based on mouse movement points during the initial drawing. */ - updateMovingPoint(cartesian: Cartesian3, index: number) { + updateMovingPoint(cartesian: Cartesian3) { + const tempPoints = [...this.points, cartesian]; + const geometryPoints = this.createPolygon(tempPoints); + this.setGeometryPoints(geometryPoints); + this.addToMap(); + } + + /** + * In edit mode, drag key points to update corresponding key point data. + */ + updateDraggingPoint(cartesian: Cartesian3, index: number) { this.points[index] = cartesian; const geometryPoints = this.createPolygon(this.points); this.setGeometryPoints(geometryPoints); diff --git a/src/draw.ts b/src/draw.ts index 3d898dc..16dcccb 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -5,28 +5,18 @@ import { State } from './interface'; export default class Draw { cesium: typeof CesiumTypeOnly; viewer: CesiumTypeOnly.Viewer; - arrowLengthScale: number = 5; - maxArrowLength: number = 2; - tailWidthFactor: number; - neckWidthFactor: number; - headWidthFactor: number; - headAngle: number; - neckAngle: number; eventHandler: CesiumTypeOnly.ScreenSpaceEventHandler; - entity: CesiumTypeOnly.Entity; + polygonEntity: CesiumTypeOnly.Entity; geometryPoints: CesiumTypeOnly.Cartesian3[] = []; state: State = 'drawing'; controlPoints: CesiumTypeOnly.EntityCollection; controlPointsEventHandler: CesiumTypeOnly.ScreenSpaceEventHandler; + lineEntity: CesiumTypeOnly.Entity; constructor(cesium: typeof CesiumTypeOnly, viewer: CesiumTypeOnly.Viewer) { this.cesium = cesium; this.viewer = viewer; - this.tailWidthFactor = 0.1; - this.neckWidthFactor = 0.2; - this.headWidthFactor = 0.25; - this.headAngle = Math.PI / 8.5; - this.neckAngle = Math.PI / 13; + this.cartesianToLnglat = this.cartesianToLnglat.bind(this); this.pixelToCartesian = this.pixelToCartesian.bind(this); this.onClick(); @@ -52,12 +42,18 @@ export default class Draw { */ onClick() { this.eventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas); - this.eventHandler.setInputAction((evt) => { + this.eventHandler.setInputAction((evt: any) => { const pickedObject = this.viewer.scene.pick(evt.position); const hitEntities = this.cesium.defined(pickedObject) && pickedObject.id instanceof CesiumTypeOnly.Entity; if (this.state === 'drawing') { // In the drawing state, the points clicked are key nodes of the shape, and they are saved in this.points. const cartesian = this.pixelToCartesian(evt.position); + const points = this.getPoints(); + // If clicked outside the sphere or if the distance between the current click position and + // the previous click position is less than 10 meters, it is considered an invalid point. + if (!cartesian || (points.length > 0 && !this.checkDistance(cartesian, points[points.length - 1]))) { + return; + } this.addPoint(cartesian); } else if (this.state === 'edit') { //In edit mode, exiting the edit state and deleting control points when clicking in the blank area. @@ -79,22 +75,38 @@ export default class Draw { } onMouseMove() { - this.eventHandler.setInputAction((evt) => { - this.drawingWhileMoving(evt.endPosition, 1); + this.eventHandler.setInputAction((evt: any) => { + const points = this.getPoints(); + const cartesian = this.pixelToCartesian(evt.endPosition); + if (!cartesian) { + return; + } + if (this.checkDistance(cartesian, points[points.length - 1])) { + // Synchronize data to subclasses.If the distance is less than 10 meters, do not proceed + this.updateMovingPoint(cartesian, points.length); + } }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE); } - drawingWhileMoving(position: CesiumTypeOnly.Cartesian2, index: number) { - const cartesian = this.pixelToCartesian(position); - const lnglat = this.cartesianToLnglat(cartesian); - const points = this.getPoints(); - const lastPoint = this.cartesianToLnglat(points[points.length - 1]); - const distance = Utils.MathDistance(lnglat, lastPoint); - if (distance < 0.0001) { - return false; - } - // Synchronize data to subclasses. - this.updateMovingPoint(cartesian, index); + onDoubleClick() { + // this.eventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas); + this.eventHandler.setInputAction((evt: any) => { + console.error('db click...'); + this.finishDrawing(); + }, this.cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); + } + + /** + * Check if the distance between two points is greater than 10 meters. + */ + checkDistance(cartesian1: CesiumTypeOnly.Cartesian3, cartesian2: CesiumTypeOnly.Cartesian3) { + const distance = this.cesium.Cartesian3.distance(cartesian1, cartesian2); + return distance > 10; + } + + finishDrawing() { + this.removeMoveListener(); + this.setState('static'); } removeClickListener() { @@ -117,8 +129,8 @@ export default class Draw { const callback = () => { return new this.cesium.PolygonHierarchy(this.geometryPoints); }; - if (!this.entity) { - this.entity = this.viewer.entities.add({ + if (!this.polygonEntity) { + this.polygonEntity = this.viewer.entities.add({ polygon: new this.cesium.PolygonGraphics({ hierarchy: new this.cesium.CallbackProperty(callback, false), show: true, @@ -129,6 +141,19 @@ export default class Draw { } } + drawLine() { + if (!this.lineEntity) { + this.lineEntity = this.viewer.entities.add({ + polyline: { + positions: new this.cesium.CallbackProperty(() => this.geometryPoints, false), + width: 5, + // material: this.cesium.Color.RED, + clampToGround: true, + }, + }); + } + } + cartesianToLnglat(cartesian: CesiumTypeOnly.Cartesian3): number[] { const lnglat = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian); const lat = this.cesium.Math.toDegrees(lnglat.latitude); @@ -161,7 +186,7 @@ export default class Draw { this.controlPointsEventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas); // Listen for left mouse button press events - this.controlPointsEventHandler.setInputAction((clickEvent) => { + this.controlPointsEventHandler.setInputAction((clickEvent: any) => { const pickedObject = this.viewer.scene.pick(clickEvent.position); if (this.cesium.defined(pickedObject)) { @@ -179,12 +204,12 @@ export default class Draw { }, this.cesium.ScreenSpaceEventType.LEFT_DOWN); // Listen for mouse movement events - this.controlPointsEventHandler.setInputAction((moveEvent) => { + this.controlPointsEventHandler.setInputAction((moveEvent: any) => { if (isDragging && draggedIcon) { const cartesian = this.viewer.camera.pickEllipsoid(moveEvent.endPosition, this.viewer.scene.globe.ellipsoid); if (cartesian) { draggedIcon.position.setValue(cartesian); - this.drawingWhileMoving(moveEvent.endPosition, draggedIcon.index); + this.updateDraggingPoint(cartesian, draggedIcon.index); } } }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -215,7 +240,11 @@ export default class Draw { return this.cesium.Cartesian3(); } - updateMovingPoint(cartesian: CesiumTypeOnly.Cartesian3, index: number) { + updateMovingPoint(cartesian: CesiumTypeOnly.Cartesian3, index?: number) { + //Abstract method that must be implemented by subclasses. + } + + updateDraggingPoint(cartesian: CesiumTypeOnly.Cartesian3, index: number) { //Abstract method that must be implemented by subclasses. } } diff --git a/src/index.ts b/src/index.ts index 5d96b35..2ad4185 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,9 @@ - import FineArrow from './arrow/fine-arrow' - - const CesiumPlot = { - FineArrow -} +import FineArrow from './arrow/fine-arrow'; +import AttackArrow from './arrow/attack-arrow'; + +const CesiumPlot = { + FineArrow, + AttackArrow, +}; export default CesiumPlot;