To incorporate double-arrow animation.

This commit is contained in:
ethan 2024-03-23 18:45:05 +08:00
parent c23c9aa654
commit b234fca7dd
5 changed files with 240 additions and 11 deletions

View File

@ -47,6 +47,7 @@ const editStartHandler = () => {
const editEndHandler = (geometryPoints: any) => {
console.error('editEnd', geometryPoints);
};
const buttonGroup = document.getElementById('button-group') as HTMLElement;
buttonGroup.onclick = (evt) => {
const targetElement = evt.target as HTMLElement;
@ -160,6 +161,12 @@ buttonGroup.onclick = (evt) => {
geometry.off('editEnd', editEndHandler);
}
break;
case 'startGrowthAnimation':
if (geometry) {
geometry.startGrowthAnimation();
}
break;
default:
break;
}

View File

@ -68,6 +68,7 @@
<button id="remove">删除</button>
<button id="addEvent">绑定事件</button>
<button id="removeEvent">解绑事件</button>
<button id="startGrowthAnimation">生长动画</button>
</div>
<script>
window.CESIUM_BASE_URL = 'node_modules/cesium/Build/Cesium';

View File

@ -15,6 +15,12 @@ export default class DoubleArrow extends Base {
neckHeightFactor: number;
connPoint: Position;
tempPoint4: Position;
minPointsForShape: number;
llBodyPnts: Position[] = [];
rrBodyPnts: Position[] = [];
curveControlPointLeft: Cartesian3;
curveControlPointRight: Cartesian3;
isClockWise: boolean;
constructor(cesium: any, viewer: any, style?: PolygonStyle) {
super(cesium, viewer, style);
@ -25,6 +31,7 @@ export default class DoubleArrow extends Base {
this.neckWidthFactor = 0.15;
this.connPoint = [0, 0];
this.tempPoint4 = [0, 0];
this.minPointsForShape = 4;
this.setState('drawing');
}
@ -42,8 +49,30 @@ export default class DoubleArrow extends Base {
} else if (this.points.length === 2) {
this.setGeometryPoints(this.points);
this.drawPolygon();
} else if (this.points.length === 4) {
} else if (this.points.length === 3) {
this.lineEntity && this.viewer.entities.remove(this.lineEntity);
} else {
this.finishDrawing();
this.curveControlPointLeft = this.cesium.Cartesian3.fromDegrees(this.llBodyPnts[2][0], this.llBodyPnts[2][1]);
this.curveControlPointRight = this.cesium.Cartesian3.fromDegrees(this.rrBodyPnts[1][0], this.rrBodyPnts[1][1]);
// 辅助查看插值控制点位置
// this.CesiumViewer.entities.add({
// position: this.curveControlPointLeft,
// point: {
// pixelSize: 10,
// heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
// color: this.cesium.Color.RED,
// },
// });
// this.CesiumViewer.entities.add({
// position: this.curveControlPointRight,
// point: {
// pixelSize: 10,
// heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
// color: this.cesium.Color.RED,
// },
// });
}
}
/**
@ -55,7 +84,7 @@ export default class DoubleArrow extends Base {
if (tempPoints.length === 2) {
this.addFirstLineOfTheArrow();
} else if (tempPoints.length > 2) {
const geometryPoints = this.createPolygon(tempPoints);
const geometryPoints = this.createGraphic(tempPoints);
this.setGeometryPoints(geometryPoints);
this.drawPolygon();
}
@ -66,7 +95,7 @@ export default class DoubleArrow extends Base {
*/
updateDraggingPoint(cartesian: Cartesian3, index: number) {
this.points[index] = cartesian;
const geometryPoints = this.createPolygon(this.points);
const geometryPoints = this.createGraphic(this.points);
this.setGeometryPoints(geometryPoints);
this.drawPolygon();
}
@ -74,7 +103,7 @@ export default class DoubleArrow extends Base {
/**
* Generate geometric shapes based on key points.
*/
createPolygon(positions: Cartesian3[]) {
createGraphic(positions: Cartesian3[]) {
const lnglatPoints = positions.map((pnt) => {
return this.cartesianToLnglat(pnt);
});
@ -92,7 +121,8 @@ export default class DoubleArrow extends Base {
}
let leftArrowPnts: Position[];
let rightArrowPnts;
if (Utils.isClockWise(pnt1, pnt2, pnt3)) {
this.isClockWise = Utils.isClockWise(pnt1, pnt2, pnt3);
if (this.isClockWise) {
leftArrowPnts = this.getArrowPoints(pnt1, this.connPoint, this.tempPoint4, false);
rightArrowPnts = this.getArrowPoints(this.connPoint, pnt2, pnt3, true);
} else {
@ -104,9 +134,11 @@ export default class DoubleArrow extends Base {
const llBodyPnts = leftArrowPnts.slice(0, t);
const lArrowPnts = leftArrowPnts.slice(t, t + 5);
let lrBodyPnts = leftArrowPnts.slice(t + 5, m);
this.llBodyPnts = llBodyPnts;
let rlBodyPnts = rightArrowPnts.slice(0, t);
const rArrowPnts = rightArrowPnts.slice(t, t + 5);
const rrBodyPnts = rightArrowPnts.slice(t + 5, m);
this.rrBodyPnts = rrBodyPnts;
rlBodyPnts = Utils.getBezierPoints(rlBodyPnts);
const bodyPnts = Utils.getBezierPoints(rrBodyPnts.concat(llBodyPnts.slice(1)));
lrBodyPnts = Utils.getBezierPoints(lrBodyPnts);
@ -220,4 +252,16 @@ export default class DoubleArrow extends Base {
getPoints() {
return this.points;
}
getBezierControlPointforGrowthAnimation() {
return this.isClockWise
? {
left: this.curveControlPointLeft,
right: this.curveControlPointRight,
}
: {
right: this.curveControlPointLeft,
left: this.curveControlPointRight,
};
}
}

View File

@ -1,5 +1,4 @@
// @ts-ignore
// import * as CesiumTypeOnly from 'cesium';
import * as CesiumTypeOnly from 'cesium';
import {
State,
@ -9,10 +8,12 @@ import {
EventType,
EventListener,
VisibleAnimationOpts,
GrowthAnimationOpts,
} from './interface';
import EventDispatcher from './events';
import cloneDeep from 'lodash.clonedeep';
import merge from 'lodash.merge';
// import merge from 'lodash.merge';
import * as Utils from './utils';
export default class Base {
cesium: typeof CesiumTypeOnly;
@ -34,6 +35,7 @@ export default class Base {
points: CesiumTypeOnly.Cartesian3[] = [];
isHidden: boolean = false;
styleCache: GeometryStyle | undefined;
minPointsForShape: number = 0;
constructor(cesium: CesiumTypeOnly, viewer: CesiumTypeOnly.Viewer, style?: GeometryStyle) {
this.cesium = cesium;
@ -642,6 +644,170 @@ export default class Base {
}, delay);
}
startGrowthAnimation(opts: GrowthAnimationOpts) {
const { duration = 3000, delay = 0, callback } = opts || {};
if (this.state !== 'static') {
return;
}
if (!this.minPointsForShape) {
console.warn('Growth animation is not supported for this type of shape');
return;
}
this.setState('animating');
if (this.minPointsForShape === 4) {
// For double arrows, special handling is required.
this.doubleArrowGrowthAnimation(duration, delay, callback);
return;
}
setTimeout(() => {
this.hideWithAnimation(0, 0, undefined);
const points = this.getPoints();
let segmentDuration = 0;
if (this.minPointsForShape === 2) {
segmentDuration = duration / (points.length - 1);
} else {
segmentDuration = duration / (points.length - 2);
}
let startTime = Date.now();
let movingPointIndex = 0;
this.viewer.clock.shouldAnimate = true;
const frameListener = (clock) => {
const currentTime = Date.now();
const elapsedTime = currentTime - startTime;
if (elapsedTime >= duration) {
// Animation ends
callback && callback();
startTime = 0;
this.viewer.clock.shouldAnimate = false;
this.viewer.clock.onTick.removeEventListener(frameListener);
this.setState('static');
return;
}
const currentSegment = Math.floor(elapsedTime / segmentDuration);
let startPoint;
if (this.minPointsForShape === 2) {
movingPointIndex = currentSegment + 1;
} else {
movingPointIndex = currentSegment + 2;
}
startPoint = points[movingPointIndex - 1];
if (currentSegment == 0 && this.minPointsForShape === 3) {
// The face-arrow determined by three points, with the animation starting from the midpoint of the line connecting the first two points.
startPoint = this.cesium.Cartesian3.midpoint(points[0], points[1], new this.cesium.Cartesian3());
}
let endPoint = points[movingPointIndex];
// To dynamically add points between the startPoint and endPoint, consistent with the initial drawing logic,
// update the point at index movingPointIndex in the points array with the newPosition,
// generate the arrow, and execute the animation.
const t = (elapsedTime - currentSegment * segmentDuration) / segmentDuration;
const newPosition = this.cesium.Cartesian3.lerp(startPoint, endPoint, t, new this.cesium.Cartesian3());
const tempPoints = points.slice(0, movingPointIndex + 1);
tempPoints[tempPoints.length - 1] = newPosition;
const geometryPoints = this.createGraphic(tempPoints);
this.setGeometryPoints(geometryPoints);
this.showAnimation(0, 0, undefined);
};
this.viewer.clock.onTick.addEventListener(frameListener);
}, delay);
}
private doubleArrowGrowthAnimation(duration: number = 3000, delay: number = 0, callback?: Function) {
setTimeout(() => {
this.viewer.entities.remove(this.polygonEntity);
this.viewer.entities.remove(this.outlineEntity);
this.polygonEntity = null;
this.outlineEntity = null;
const points = this.getPoints();
let startTime = Date.now();
this.viewer.clock.shouldAnimate = true;
const frameListener = (clock) => {
const currentTime = Date.now();
const elapsedTime = currentTime - startTime;
if (elapsedTime >= duration) {
// Animation ends
callback && callback();
startTime = 0;
this.viewer.clock.shouldAnimate = false;
this.viewer.clock.onTick.removeEventListener(frameListener);
this.setState('static');
return;
}
// Utils.isClockWise(pnt1, pnt2, pnt3)
const midPoint = this.cesium.Cartesian3.midpoint(points[0], points[1], new this.cesium.Cartesian3());
const startPointLeft = this.cesium.Cartesian3.midpoint(points[0], midPoint, new this.cesium.Cartesian3());
const startPointRight = this.cesium.Cartesian3.midpoint(midPoint, points[1], new this.cesium.Cartesian3());
let endPointLeft = points[3];
let endPointRight = points[2];
const t = elapsedTime / duration;
const controlPoint = this.getBezierControlPointforGrowthAnimation();
let curveControlPointsLeft = [startPointLeft, controlPoint.left, endPointLeft];
let curveControlPointsRight = [startPointRight, controlPoint.right, endPointRight];
const newPositionLeft = this.getNewPosition(curveControlPointsLeft, t);
const newPositionRight = this.getNewPosition(curveControlPointsRight, t);
// Assist in viewing exercise routes
// this.viewer.entities.add({
// position: newPositionLeft,
// point: {
// pixelSize: 4,
// heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
// color: this.cesium.Color.RED,
// },
// });
// this.viewer.entities.add({
// position: newPositionRight,
// point: {
// pixelSize: 4,
// heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
// color: this.cesium.Color.RED,
// },
// });
const tempPoints = [...points];
tempPoints[2] = newPositionRight;
tempPoints[3] = newPositionLeft;
const geometryPoints = this.createGraphic(tempPoints);
this.setGeometryPoints(geometryPoints);
this.drawPolygon();
};
this.viewer.clock.onTick.addEventListener(frameListener);
}, delay);
}
private getNewPosition(curveControlPoints, t) {
curveControlPoints = curveControlPoints.map((item) => {
return this.cartesianToLnglat(item);
});
let curvePoints = Utils.getCurvePoints(0.3, curveControlPoints);
curvePoints = curvePoints.map((p) => {
return this.cesium.Cartesian3.fromDegrees(p[0], p[1]);
});
let newPosition = this.interpolateAlongCurve(curvePoints, t);
return newPosition;
}
private interpolateAlongCurve(curvePoints, t) {
const numPoints = curvePoints.length - 1;
const index = Math.floor(t * numPoints);
const tSegment = t * numPoints - index;
const startPoint = curvePoints[index];
const endPoint = curvePoints[index + 1];
const x = startPoint.x + (endPoint.x - startPoint.x) * tSegment;
const y = startPoint.y + (endPoint.y - startPoint.y) * tSegment;
const z = startPoint.z + (endPoint.z - startPoint.z) * tSegment;
return new this.cesium.Cartesian3(x, y, z);
}
remove() {
if (this.type === 'polygon') {
this.viewer.entities.remove(this.polygonEntity);
@ -692,4 +858,9 @@ export default class Base {
return 'polygon';
//Abstract method that must be implemented by subclasses.
}
createGraphic(points: CesiumTypeOnly.Cartesian3[]): CesiumTypeOnly.Cartesian3[] {
//Abstract method that must be implemented by subclasses.
return [new this.cesium.Cartesian3()];
}
}

View File

@ -12,14 +12,20 @@ export type LineStyle = {
lineWidth?: number;
};
export type State = 'drawing' | 'edit' | 'static';
export type State = 'drawing' | 'edit' | 'static' | 'animating';
export type GeometryStyle = PolygonStyle | LineStyle;
export type EventType = 'drawStart' | 'drawUpdate' | 'drawEnd' | 'editEnd' | 'editStart';
export type EventListener = (eventData?: any) => void;
export type VisibleOpts = {
export type VisibleAnimationOpts = {
duration?: number;
delay?: number;
callback?: (() => void)
}
callback?: () => void;
};
export type GrowthAnimationOpts = {
duration: number;
delay: number;
callback: Function;
};