feat: scene -> standard
This commit is contained in:
parent
6319afa218
commit
7da2ac2f2a
46
scene.md
Normal file
46
scene.md
Normal file
@ -0,0 +1,46 @@
|
||||
# 标准地图数据结构
|
||||
|
||||
```typescript
|
||||
interface StandardScene {
|
||||
robotGroups?: Array<RobotGroup>; // 机器人组信息
|
||||
robots?: Array<RobotInfo>; // 机器人信息
|
||||
points?: Array<StandardScenePoint>; // 标准点位信息
|
||||
routes?: Array<StandardSceneRoute>; // 标准线路信息
|
||||
areas?: Array<StandardSceneArea>; // 标准区域信息
|
||||
blocks?: Array<[number, number]>; // 障碍点集合
|
||||
}
|
||||
interface StandardScenePoint {
|
||||
id: string;
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
type: number; // 点位类型
|
||||
robots?: Array<string>; // 绑定机器人id集合
|
||||
actions?: Array<string>; // 绑定动作点id集合
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
interface StandardSceneRoute {
|
||||
id: string;
|
||||
connect: [string, string]; // 连接点位id
|
||||
type: 'line' | 'bezier2' | 'bezier3'; // 线路类型
|
||||
pass?: number; // 可通行类型
|
||||
c1?: { x?: number; y?: number }; // 控制点1
|
||||
c2?: { x?: number; y?: number }; // 控制点2
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
interface StandardSceneArea {
|
||||
id: string;
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
type: number; // 区域类型
|
||||
points?: Array<string>; // 绑定点位id集合
|
||||
routes?: Array<string>; // 绑定线路id集合
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
```
|
@ -14,8 +14,6 @@ export enum MapPointType {
|
||||
停靠点,
|
||||
动作点,
|
||||
禁行点,
|
||||
|
||||
障碍点 = 99,
|
||||
}
|
||||
export const MAP_POINT_TYPES = Object.freeze(
|
||||
<[string, MapPointType][]>Object.entries(MapPointType).filter(([, v]) => typeof v === 'number'),
|
||||
@ -25,6 +23,7 @@ export const MAP_POINT_TYPES = Object.freeze(
|
||||
//#region 线路
|
||||
export enum MapRouteType {
|
||||
直线 = 'line',
|
||||
二阶贝塞尔曲线 = 'bezier2',
|
||||
三阶贝塞尔曲线 = 'bezier3',
|
||||
}
|
||||
export const MAP_ROUTE_TYPE = invert(MapRouteType);
|
||||
|
@ -13,6 +13,8 @@ export interface MapPen extends Pen {
|
||||
|
||||
attrs?: Record<string, unknown>; // 额外属性
|
||||
activeAttrs?: Array<string>; // 已激活的额外属性
|
||||
|
||||
properties?: unknown; // 第三方附加参数
|
||||
}
|
||||
|
||||
//#region 点位
|
||||
@ -30,8 +32,8 @@ export interface MapRouteInfo {
|
||||
type: MapRouteType; // 线路类型
|
||||
direction?: -1 | 1; // 方向
|
||||
pass?: MapRoutePassType; // 可通行类型
|
||||
c1?: Point; // 控制点A
|
||||
c2?: Point; // 控制点B
|
||||
c1?: Point; // 控制点1
|
||||
c2?: Point; // 控制点2
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
@ -9,11 +9,58 @@ export interface SceneDetail extends SceneInfo {
|
||||
json?: string; // 场景JSON
|
||||
}
|
||||
export interface GroupSceneDetail extends SceneDetail {
|
||||
group: RobotGroup;
|
||||
robots?: Array<RobotInfo>;
|
||||
group: RobotGroup; // 机器人组信息
|
||||
robots?: Array<RobotInfo>; // 机器人信息
|
||||
}
|
||||
|
||||
export interface SceneData extends Meta2dData {
|
||||
robots?: Array<RobotInfo>;
|
||||
robotGroups?: Array<RobotGroup>;
|
||||
robotGroups?: Array<RobotGroup>; // 机器人组信息
|
||||
robots?: Array<RobotInfo>; // 机器人信息
|
||||
}
|
||||
|
||||
export interface StandardScene {
|
||||
robotGroups?: Array<RobotGroup>; // 机器人组信息
|
||||
robots?: Array<RobotInfo>; // 机器人信息
|
||||
points?: Array<StandardScenePoint>; // 标准点位信息
|
||||
routes?: Array<StandardSceneRoute>; // 标准线路信息
|
||||
areas?: Array<StandardSceneArea>; // 标准区域信息
|
||||
blocks?: Array<[number, number]>; // 障碍点集合
|
||||
}
|
||||
export interface StandardScenePoint {
|
||||
id: string;
|
||||
name: string;
|
||||
desc?: string; // 描述
|
||||
x: number;
|
||||
y: number;
|
||||
type: number; // 点位类型
|
||||
robots?: Array<string>; // 绑定机器人id集合
|
||||
actions?: Array<string>; // 绑定动作点id集合
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
export interface StandardSceneRoute {
|
||||
id: string;
|
||||
desc?: string; // 描述
|
||||
from: string; // 起点点位id
|
||||
to: string; // 终点点位id
|
||||
type: 'line' | 'bezier2' | 'bezier3'; // 线路类型
|
||||
pass?: number; // 可通行类型
|
||||
c1?: { x?: number; y?: number }; // 控制点1
|
||||
c2?: { x?: number; y?: number }; // 控制点2
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
export interface StandardSceneArea {
|
||||
id: string;
|
||||
name: string;
|
||||
desc?: string; // 描述
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
type: number; // 区域类型
|
||||
points?: Array<string>; // 绑定点位id集合
|
||||
routes?: Array<string>; // 绑定线路id集合
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
|
@ -92,71 +92,73 @@ const route = computed<MapRouteInfo | null>(() => {
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<template v-if="MapRouteType.三阶贝塞尔曲线 === route.type">
|
||||
<a-row align="middle" :gutter="8">
|
||||
<a-col flex="auto">
|
||||
<a-typography-text>{{ $t('控制点1') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>X:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c1?.x.toFixed()"
|
||||
@change="editor.updateRoute(id, { c1: { x: +$event, y: route?.c1?.y ?? 0 } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>Y:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c1?.y.toFixed()"
|
||||
@change="editor.updateRoute(id, { c1: { x: route?.c1?.x ?? 0, y: +$event } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row
|
||||
v-if="[MapRouteType.二阶贝塞尔曲线, MapRouteType.三阶贝塞尔曲线].includes(route.type)"
|
||||
align="middle"
|
||||
:gutter="8"
|
||||
>
|
||||
<a-col flex="auto">
|
||||
<a-typography-text>{{ $t('控制点1') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>X:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c1?.x.toFixed()"
|
||||
@change="editor.updateRoute(id, { c1: { x: +$event, y: route?.c1?.y ?? 0 } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>Y:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c1?.y.toFixed()"
|
||||
@change="editor.updateRoute(id, { c1: { x: route?.c1?.x ?? 0, y: +$event } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row align="middle" :gutter="8">
|
||||
<a-col flex="auto">
|
||||
<a-typography-text>{{ $t('控制点2') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>X:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c2?.x.toFixed()"
|
||||
@change="editor.updateRoute(id, { c2: { x: +$event, y: route?.c2?.y ?? 0 } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>Y:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c2?.y.toFixed()"
|
||||
@change="editor.updateRoute(id, { c2: { x: route?.c2?.x ?? 0, y: +$event } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<a-row v-if="MapRouteType.三阶贝塞尔曲线 === route.type" align="middle" :gutter="8">
|
||||
<a-col flex="auto">
|
||||
<a-typography-text>{{ $t('控制点2') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>X:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c2?.x.toFixed()"
|
||||
@change="editor.updateRoute(id, { c2: { x: +$event, y: route?.c2?.y ?? 0 } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>Y:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c2?.y.toFixed()"
|
||||
@change="editor.updateRoute(id, { c2: { x: route?.c2?.x ?? 0, y: +$event } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-flex>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
</a-card>
|
||||
|
@ -12,15 +12,23 @@ import {
|
||||
type Point,
|
||||
} from '@api/map';
|
||||
import type { RobotGroup, RobotInfo } from '@api/robot';
|
||||
import type { GroupSceneDetail, SceneData } from '@api/scene';
|
||||
import type {
|
||||
GroupSceneDetail,
|
||||
SceneData,
|
||||
StandardScene,
|
||||
StandardSceneArea,
|
||||
StandardScenePoint,
|
||||
StandardSceneRoute,
|
||||
} from '@api/scene';
|
||||
import sTheme from '@core/theme.service';
|
||||
import { CanvasLayer, LockState, Meta2d, type Meta2dStore, type Pen, s8 } from '@meta2d/core';
|
||||
import { useObservable } from '@vueuse/rxjs';
|
||||
import { clone, get, isNil, isString, pick, remove, some } from 'lodash-es';
|
||||
import { clone, get, isEmpty, isNil, isString, pick, remove, some } from 'lodash-es';
|
||||
import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from 'rxjs';
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
export class EditorService extends Meta2d {
|
||||
//#region 场景文件
|
||||
public async load(map?: string, editable = false, detail?: Partial<GroupSceneDetail>): Promise<void> {
|
||||
let data = map ? JSON.parse(map) : undefined;
|
||||
if (!isNil(detail)) {
|
||||
@ -32,22 +40,101 @@ export class EditorService extends Meta2d {
|
||||
this.setState(editable);
|
||||
}
|
||||
public save(): string {
|
||||
const data = this.data();
|
||||
data.pens.forEach((pen: MapPen) => {
|
||||
remove(pen.point?.robots ?? [], (v) => !this.#robotMap.has(v));
|
||||
remove(pen.point?.actions ?? [], (v) => this.getPenById(v)?.point?.type !== MapPointType.动作点);
|
||||
remove(pen.area?.points ?? [], (v) => {
|
||||
const { point } = this.getPenById(v) ?? {};
|
||||
if (isNil(point)) return true;
|
||||
if (point.type === MapPointType.禁行点) return true;
|
||||
if (pen.area?.type === MapAreaType.库区 && point.type !== MapPointType.动作点) return true;
|
||||
return false;
|
||||
});
|
||||
remove(pen.area?.routes ?? [], (v) => isNil(this.getPenById(v)));
|
||||
});
|
||||
return JSON.stringify(data);
|
||||
const scene: StandardScene = {
|
||||
robotGroups: this.robotGroups.value,
|
||||
robots: this.robots,
|
||||
points: this.points.value.map((v) => this.#mapScenePoint(v)).filter((v) => !isNil(v)),
|
||||
routes: this.routes.value.map((v) => this.#mapSceneRoute(v)).filter((v) => !isNil(v)),
|
||||
areas: this.areas.value.map((v) => this.#mapSceneArea(v)).filter((v) => !isNil(v)),
|
||||
blocks: [],
|
||||
};
|
||||
return JSON.stringify(scene);
|
||||
}
|
||||
|
||||
#mapScenePoint(pen?: MapPen): StandardScenePoint | null {
|
||||
if (!pen?.id || isEmpty(pen?.point)) return null;
|
||||
const { id, x = 0, y = 0, label, desc, properties } = pen;
|
||||
const { type, robots, actions } = pen.point;
|
||||
const point: StandardScenePoint = {
|
||||
id: id,
|
||||
name: label || id,
|
||||
desc,
|
||||
x,
|
||||
y,
|
||||
type,
|
||||
config: {},
|
||||
properties,
|
||||
};
|
||||
if ([MapPointType.充电点, MapPointType.停靠点].includes(type)) {
|
||||
point.robots = robots?.filter((v) => this.#robotMap.has(v));
|
||||
}
|
||||
if (MapPointType.等待点 === type) {
|
||||
point.actions = actions?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.动作点);
|
||||
}
|
||||
return point;
|
||||
}
|
||||
#mapSceneRoute(pen?: MapPen): StandardSceneRoute | null {
|
||||
if (!pen?.id || pen.anchors?.length !== 2 || isEmpty(pen?.route)) return null;
|
||||
const { id, anchors, desc, properties } = pen;
|
||||
const { type, direction = 1, pass, c1, c2 } = pen.route;
|
||||
const [p1, p2] = anchors.map((v) => v.connectTo!);
|
||||
const route: StandardSceneRoute = {
|
||||
id: id,
|
||||
desc,
|
||||
from: direction < 0 ? p2 : p1,
|
||||
to: direction < 0 ? p1 : p2,
|
||||
type,
|
||||
pass,
|
||||
config: {},
|
||||
properties,
|
||||
};
|
||||
switch (type) {
|
||||
case MapRouteType.二阶贝塞尔曲线:
|
||||
route.c1 = c1;
|
||||
break;
|
||||
case MapRouteType.三阶贝塞尔曲线:
|
||||
route.c1 = direction < 0 ? c2 : c1;
|
||||
route.c2 = direction < 0 ? c1 : c2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return route;
|
||||
}
|
||||
#mapSceneArea(pen: MapPen): StandardSceneArea | null {
|
||||
if (!pen.id || isEmpty(pen.area)) return null;
|
||||
const { id, x = 0, y = 0, width = 0, height = 0, label, desc, properties } = pen;
|
||||
const { type, points, routes } = pen.area;
|
||||
const area: StandardSceneArea = {
|
||||
id,
|
||||
name: label || id,
|
||||
desc,
|
||||
x,
|
||||
y,
|
||||
w: width,
|
||||
h: height,
|
||||
type,
|
||||
config: {},
|
||||
properties,
|
||||
};
|
||||
if (MapAreaType.库区 === type) {
|
||||
area.points = points?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.动作点);
|
||||
}
|
||||
if ([MapAreaType.互斥区, MapAreaType.非互斥区].includes(type)) {
|
||||
area.points = points?.filter((v) => {
|
||||
const { point } = this.getPenById(v) ?? {};
|
||||
if (isNil(point)) return false;
|
||||
if (point.type === MapPointType.禁行点) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (MapAreaType.互斥区 === type) {
|
||||
area.routes = routes?.filter((v) => !isEmpty(this.getPenById(v)?.area));
|
||||
}
|
||||
return area;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
public setState(editable?: boolean): void {
|
||||
this.lock(editable ? LockState.None : LockState.DisableEdit);
|
||||
}
|
||||
@ -458,6 +545,7 @@ export class EditorService extends Meta2d {
|
||||
this.register({ line: () => new Path2D() });
|
||||
this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea });
|
||||
this.registerAnchors({ point: anchorPoint });
|
||||
this.addDrawLineFn('bezier2', lineBezier2);
|
||||
this.addDrawLineFn('bezier3', lineBezier3);
|
||||
}
|
||||
}
|
||||
@ -545,7 +633,7 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
|
||||
const { type, direction = 1, pass = 0, c1, c2 } = pen.route ?? {};
|
||||
const { x: dx1 = x2 - x1, y: dy1 = 0 } = c1 ?? {};
|
||||
const { x: dx2 = 0, y: dy2 = y2 - y1 } = c2 ?? {};
|
||||
const { x: dx2 = 0, y: dy2 = y1 - y2 } = c2 ?? {};
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
@ -555,10 +643,15 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
case MapRouteType.直线:
|
||||
ctx.lineTo(x2, y2);
|
||||
break;
|
||||
case MapRouteType.二阶贝塞尔曲线:
|
||||
ctx.quadraticCurveTo(x1 + dx1, y1 + dy1, x2, y2);
|
||||
p1.next = { x: x1 + (2 / 3) * dx1, y: y1 + (2 / 3) * dy1 };
|
||||
p2.prev = { x: x2 / 3 + (2 / 3) * (x1 + dx1), y: y2 / 3 + (2 / 3) * (y1 + dy1) };
|
||||
break;
|
||||
case MapRouteType.三阶贝塞尔曲线:
|
||||
ctx.bezierCurveTo(x1 + dx1, y1 + dy1, x2 - dx2, y2 - dy2, x2, y2);
|
||||
ctx.bezierCurveTo(x1 + dx1, y1 + dy1, x2 + dx2, y2 + dy2, x2, y2);
|
||||
p1.next = { x: x1 + dx1, y: y1 + dy1 };
|
||||
p2.prev = { x: x2 - dx2, y: y2 - dy2 };
|
||||
p2.prev = { x: x2 + dx2, y: y2 + dy2 };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -573,8 +666,10 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
switch (type) {
|
||||
case MapRouteType.直线:
|
||||
return Math.atan2(y2 - y1, x2 - x1);
|
||||
case MapRouteType.二阶贝塞尔曲线:
|
||||
return direction < 0 ? Math.atan2(dy1, dx1) : Math.atan2(y2 - y1 - dy1, x2 - x1 - dx1);
|
||||
case MapRouteType.三阶贝塞尔曲线:
|
||||
return direction < 0 ? Math.atan2(dy1, dx1) : Math.atan2(dy2, dx2);
|
||||
return direction < 0 ? Math.atan2(dy1, dx1) : Math.atan2(-dy2, -dx2);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -592,6 +687,16 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
function lineBezier2(_: Meta2dStore, pen: MapPen): void {
|
||||
if (pen.calculative?.worldAnchors?.length !== 2) return;
|
||||
const { c1 } = pen.route ?? {};
|
||||
const [p1, p2] = pen.calculative?.worldAnchors ?? [];
|
||||
const { x: x1 = 0, y: y1 = 0 } = p1 ?? {};
|
||||
const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
|
||||
const { x: dx = x2 - x1, y: dy = 0 } = c1 ?? {};
|
||||
pen.calculative.worldAnchors[0].next = { x: x1 + (2 / 3) * dx, y: y1 + (2 / 3) * dy };
|
||||
pen.calculative.worldAnchors[1].prev = { x: x2 / 3 + (2 / 3) * (x1 + dx), y: y2 / 3 + (2 / 3) * (y1 + dy) };
|
||||
}
|
||||
function lineBezier3(_: Meta2dStore, pen: MapPen): void {
|
||||
if (pen.calculative?.worldAnchors?.length !== 2) return;
|
||||
const { c1, c2 } = pen.route ?? {};
|
||||
@ -599,9 +704,9 @@ function lineBezier3(_: Meta2dStore, pen: MapPen): void {
|
||||
const { x: x1 = 0, y: y1 = 0 } = p1 ?? {};
|
||||
const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
|
||||
const { x: dx1 = x2 - x1, y: dy1 = 0 } = c1 ?? {};
|
||||
const { x: dx2 = 0, y: dy2 = y2 - y1 } = c2 ?? {};
|
||||
const { x: dx2 = 0, y: dy2 = y1 - y2 } = c2 ?? {};
|
||||
pen.calculative.worldAnchors[0].next = { x: x1 + dx1, y: y1 + dy1 };
|
||||
pen.calculative.worldAnchors[1].prev = { x: x2 - dx2, y: y2 - dy2 };
|
||||
pen.calculative.worldAnchors[1].prev = { x: x2 + dx2, y: y2 + dy2 };
|
||||
}
|
||||
|
||||
function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
|
Loading…
x
Reference in New Issue
Block a user