可吸附位置功能调整:所有位置都执行吸附,所有图形也都执行吸附(之前一个吸附成功就返回了)
添加过滤重复的可吸附位置对象逻辑
This commit is contained in:
parent
533ebdc9a8
commit
ecfe1421bb
@ -1,7 +1,10 @@
|
||||
import { FederatedMouseEvent, Point } from 'pixi.js';
|
||||
import {
|
||||
AbsorbableLine,
|
||||
AbsorbablePosition,
|
||||
GraphicDrawAssistant,
|
||||
GraphicInteractionPlugin,
|
||||
GraphicTransformEvent,
|
||||
JlDrawApp,
|
||||
JlGraphic,
|
||||
} from 'src/jlgraphic';
|
||||
@ -68,6 +71,16 @@ export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
|
||||
g.cursor = 'pointer';
|
||||
// g.scalable = true;
|
||||
g.rotatable = true;
|
||||
g.on('drag-start', () => {
|
||||
console.log('风机拖拽');
|
||||
});
|
||||
g.on('transformstart', (e: GraphicTransformEvent) => {
|
||||
if (e.isShift()) {
|
||||
g.getGraphicApp().setOptions({
|
||||
absorbablePositions: buildAbsorbablePositions(g),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
unbind(g: IscsFan): void {
|
||||
g.eventMode = 'none';
|
||||
@ -75,3 +88,23 @@ export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
|
||||
g.rotatable = false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildAbsorbablePositions(
|
||||
g: IscsFan
|
||||
): AbsorbablePosition[] | undefined {
|
||||
const app = g.getGraphicApp();
|
||||
const canvas = app.canvas;
|
||||
const store = app.queryStore;
|
||||
const aps: AbsorbablePosition[] = [];
|
||||
store.queryByType(IscsFan.Type).forEach((fan) => {
|
||||
if (fan.id === g.id) return;
|
||||
const p = fan.position;
|
||||
aps.push(
|
||||
new AbsorbableLine(new Point(0, p.y), new Point(canvas.width, p.y))
|
||||
);
|
||||
aps.push(
|
||||
new AbsorbableLine(new Point(p.x, 0), new Point(p.x, canvas.height))
|
||||
);
|
||||
});
|
||||
return aps;
|
||||
}
|
||||
|
@ -288,6 +288,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
|
||||
drag_op_end: [event: AppDragEvent];
|
||||
'pre-menu-handle': [menu: MenuItemOptions];
|
||||
'post-menu-handle': [menu: MenuItemOptions];
|
||||
'websocket-state-change': [app: GraphicApp, connected: boolean];
|
||||
destroy: [app: GraphicApp];
|
||||
}
|
||||
|
||||
@ -888,10 +889,10 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
|
||||
this.emit('destroy', this);
|
||||
if (this.wsMsgBroker) {
|
||||
this.wsMsgBroker.close();
|
||||
if (!StompCli.hasAppMsgBroker()) {
|
||||
// 如果没有其他消息代理,关闭websocket Stomp客户端
|
||||
StompCli.close();
|
||||
}
|
||||
// if (!StompCli.hasAppMsgBroker()) {
|
||||
// // 如果没有其他消息代理,关闭websocket Stomp客户端
|
||||
// StompCli.close();
|
||||
// }
|
||||
}
|
||||
this.interactionPluginMap.forEach((plugin) => {
|
||||
plugin.pause();
|
||||
|
@ -11,6 +11,8 @@ import {
|
||||
calculateFootPointFromPointToLine,
|
||||
calculateIntersectionPointOfCircleAndPoint,
|
||||
distance,
|
||||
distance2,
|
||||
isLineContainOther,
|
||||
linePoint,
|
||||
} from '../utils';
|
||||
import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
|
||||
@ -19,12 +21,25 @@ import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
|
||||
* 抽象可吸附位置
|
||||
*/
|
||||
export interface AbsorbablePosition extends Container {
|
||||
/**
|
||||
* 是否与另一个可吸附位置重叠(相似,但可能范围不同)
|
||||
* @param other
|
||||
*/
|
||||
isOverlapping(other: AbsorbablePosition): boolean;
|
||||
|
||||
/**
|
||||
* 与另一个相似的吸附位置比较范围大小
|
||||
* @param other
|
||||
* @returns >0此吸附范围大,<0另一个吸附范围大,=0两个吸附范围一样大
|
||||
*/
|
||||
compareTo(other: AbsorbablePosition): number;
|
||||
|
||||
/**
|
||||
* 尝试吸附图形对象
|
||||
* @param objs 图形对象列表
|
||||
* @returns 如果吸附成功,返回true,否则false
|
||||
*/
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean;
|
||||
tryAbsorb(...objs: DisplayObject[]): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,7 +80,22 @@ export default class AbsorbablePoint
|
||||
this.interactive;
|
||||
VectorGraphicUtil.handle(this);
|
||||
}
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean {
|
||||
compareTo(other: AbsorbablePosition): number {
|
||||
if (other instanceof AbsorbablePoint) {
|
||||
return this.absorbRange - other.absorbRange;
|
||||
}
|
||||
throw new Error('非可吸附点');
|
||||
}
|
||||
isOverlapping(other: AbsorbablePosition): boolean {
|
||||
if (other instanceof AbsorbablePoint) {
|
||||
return (
|
||||
this._point.equals(other._point) &&
|
||||
this.absorbRange === other.absorbRange
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
tryAbsorb(...objs: DisplayObject[]): void {
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
const obj = objs[i];
|
||||
if (
|
||||
@ -73,10 +103,8 @@ export default class AbsorbablePoint
|
||||
this.absorbRange
|
||||
) {
|
||||
obj.position.copyFrom(this._point);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
updateOnScaled() {
|
||||
const scaled = this.getAllParentScaled();
|
||||
@ -101,6 +129,22 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
|
||||
this.absorbRange = absorbRange;
|
||||
this.redraw();
|
||||
}
|
||||
isOverlapping(other: AbsorbablePosition): boolean {
|
||||
if (other instanceof AbsorbableLine) {
|
||||
const contain = isLineContainOther(
|
||||
{ p1: this.p1, p2: this.p2 },
|
||||
{ p1: other.p1, p2: other.p2 }
|
||||
);
|
||||
return contain;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
compareTo(other: AbsorbablePosition): number {
|
||||
if (other instanceof AbsorbableLine) {
|
||||
return distance2(this.p1, this.p2) - distance2(other.p1, other.p2);
|
||||
}
|
||||
throw new Error('非可吸附线');
|
||||
}
|
||||
redraw() {
|
||||
this.clear();
|
||||
this.lineStyle(1, new Color(this._color));
|
||||
@ -108,17 +152,15 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
|
||||
this.lineTo(this.p2.x, this.p2.y);
|
||||
}
|
||||
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean {
|
||||
tryAbsorb(...objs: DisplayObject[]): void {
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
const obj = objs[i];
|
||||
const p = obj.position.clone();
|
||||
if (linePoint(this.p1, this.p2, p, this.absorbRange, true)) {
|
||||
const fp = calculateFootPointFromPointToLine(this.p1, this.p2, p);
|
||||
obj.position.copyFrom(fp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,6 +180,18 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
this.absorbRange = absorbRange;
|
||||
this.redraw();
|
||||
}
|
||||
isOverlapping(other: AbsorbablePosition): boolean {
|
||||
if (other instanceof AbsorbableCircle) {
|
||||
return this.p0.equals(other.p0) && this.radius === other.radius;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
compareTo(other: AbsorbablePosition): number {
|
||||
if (other instanceof AbsorbableCircle) {
|
||||
return this.absorbRange - other.absorbRange;
|
||||
}
|
||||
throw new Error('非可吸附圆');
|
||||
}
|
||||
|
||||
redraw() {
|
||||
this.clear();
|
||||
@ -145,7 +199,7 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
this.drawCircle(this.p0.x, this.p0.y, this.radius);
|
||||
}
|
||||
|
||||
tryAbsorb(...objs: DisplayObject[]): boolean {
|
||||
tryAbsorb(...objs: DisplayObject[]): void {
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
const obj = objs[i];
|
||||
const len = distance(
|
||||
@ -165,9 +219,7 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
|
||||
obj.position
|
||||
);
|
||||
obj.position.copyFrom(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export interface StompCliOption {
|
||||
* @returns
|
||||
*/
|
||||
onAuthenticationFailed?: () => void;
|
||||
reconnectDelay?: number; // 重连延时,默认3秒
|
||||
reconnectDelay?: number; // 重连延时,默认3秒,设置为0不重连.
|
||||
heartbeatIncoming?: number; // 服务端过来的心跳间隔,默认30秒
|
||||
heartbeatOutgoing?: number; // 到服务端的心跳间隔,默认30秒
|
||||
}
|
||||
@ -36,18 +36,22 @@ const DefaultStompOption: StompCliOption = {
|
||||
};
|
||||
|
||||
export class StompCli {
|
||||
private static enabled = false;
|
||||
private static options: StompCliOption;
|
||||
private static client: StompClient;
|
||||
private static options: StompCliOption;
|
||||
private static appMsgBroker: AppWsMsgBroker[] = [];
|
||||
/**
|
||||
* key-订阅路径
|
||||
*/
|
||||
subscriptions: Map<string, AppStateSubscription> = new Map<
|
||||
string,
|
||||
AppStateSubscription
|
||||
>();
|
||||
private static connected = false;
|
||||
static new(options: StompCliOption) {
|
||||
if (StompCli.enabled) {
|
||||
// 已经启用
|
||||
if (StompCli.client) {
|
||||
// 已经创建
|
||||
return;
|
||||
// throw new Error('websocket 已连接,若确实需要重新连接,请先断开StompCli.close再重新StompCli.new')
|
||||
}
|
||||
StompCli.enabled = true;
|
||||
StompCli.options = Object.assign({}, DefaultStompOption, options);
|
||||
StompCli.client = new StompClient({
|
||||
brokerURL: StompCli.options.wsUrl,
|
||||
@ -62,6 +66,7 @@ export class StompCli {
|
||||
StompCli.client.onConnect = () => {
|
||||
// console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length)
|
||||
StompCli.connected = true;
|
||||
StompCli.emitConnectStateChangeEvent();
|
||||
StompCli.appMsgBroker.forEach((broker) => {
|
||||
broker.resubscribe();
|
||||
});
|
||||
@ -83,10 +88,12 @@ export class StompCli {
|
||||
StompCli.client.onDisconnect = (frame: Frame) => {
|
||||
console.log('Stomp 断开连接', frame);
|
||||
StompCli.connected = false;
|
||||
StompCli.emitConnectStateChangeEvent();
|
||||
};
|
||||
StompCli.client.onWebSocketClose = (evt: CloseEvent) => {
|
||||
console.log('websocket 关闭', evt);
|
||||
StompCli.connected = false;
|
||||
StompCli.emitConnectStateChangeEvent();
|
||||
};
|
||||
// websocket错误处理
|
||||
StompCli.client.onWebSocketError = (err: Event) => {
|
||||
@ -96,8 +103,10 @@ export class StompCli {
|
||||
StompCli.client.activate();
|
||||
}
|
||||
|
||||
static isEnabled(): boolean {
|
||||
return StompCli.enabled;
|
||||
static emitConnectStateChangeEvent() {
|
||||
StompCli.appMsgBroker.forEach((broker) => {
|
||||
broker.app.emit('websocket-state-change', broker.app, StompCli.connected);
|
||||
});
|
||||
}
|
||||
|
||||
static isConnected(): boolean {
|
||||
@ -135,7 +144,6 @@ export class StompCli {
|
||||
* 关闭websocket连接
|
||||
*/
|
||||
static close() {
|
||||
StompCli.enabled = false;
|
||||
StompCli.connected = false;
|
||||
if (StompCli.client) {
|
||||
StompCli.client.deactivate();
|
||||
|
@ -194,7 +194,7 @@ export class CommonMouseTool extends AppInteractionPlugin {
|
||||
const graphic = this.leftDownTarget.getGraphic();
|
||||
if (graphic) {
|
||||
const app = this.app;
|
||||
console.log(this.leftDownTarget.isGraphic());
|
||||
// console.log(this.leftDownTarget.isGraphic());
|
||||
// 图形选中
|
||||
if (!e.ctrlKey && !graphic.selected && graphic.selectable) {
|
||||
app.updateSelected(graphic);
|
||||
|
@ -171,11 +171,40 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
this.app.canvas.addAssistantAppend(this.apContainer);
|
||||
app.on('options-update', (options) => {
|
||||
if (options.absorbablePositions) {
|
||||
this.absorbablePositions = options.absorbablePositions;
|
||||
this.absorbablePositions = this.filterAbsorbablePositions(
|
||||
options.absorbablePositions
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤重复的吸附位置
|
||||
* @param positions
|
||||
* @returns
|
||||
*/
|
||||
filterAbsorbablePositions(
|
||||
positions: AbsorbablePosition[]
|
||||
): AbsorbablePosition[] {
|
||||
const aps: AbsorbablePosition[] = [];
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
const ap1 = positions[i];
|
||||
let ap: AbsorbablePosition | null = ap1;
|
||||
for (let j = positions.length - 1; j > i; j--) {
|
||||
const ap2 = positions[j];
|
||||
if (ap.isOverlapping(ap2) && ap.compareTo(ap2) <= 0) {
|
||||
ap = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ap != null) {
|
||||
aps.push(ap);
|
||||
}
|
||||
}
|
||||
// console.log(positions, aps);
|
||||
return aps;
|
||||
}
|
||||
|
||||
static new(app: GraphicApp) {
|
||||
return new GraphicTransformPlugin(app);
|
||||
}
|
||||
@ -266,9 +295,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
|
||||
if (this.absorbablePositions) {
|
||||
for (let i = 0; i < this.absorbablePositions.length; i++) {
|
||||
const ap = this.absorbablePositions[i];
|
||||
if (ap.tryAbsorb(...targets)) {
|
||||
break;
|
||||
}
|
||||
ap.tryAbsorb(...targets);
|
||||
}
|
||||
}
|
||||
// 事件发布
|
||||
|
@ -607,3 +607,71 @@ export function getIntersectionPoint(line1: number[], line2: number[]) {
|
||||
-denominator;
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否平行线
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param pa
|
||||
* @param pb
|
||||
* @returns
|
||||
*/
|
||||
export function isParallelLines(
|
||||
p1: IPointData,
|
||||
p2: IPointData,
|
||||
pa: IPointData,
|
||||
pb: IPointData
|
||||
): boolean {
|
||||
const vle1 = Vector2.direction(Vector2.from(p1), Vector2.from(p2));
|
||||
const vle2 = Vector2.direction(Vector2.from(pa), Vector2.from(pb));
|
||||
if (vle2.equals(vle1)) {
|
||||
return true;
|
||||
}
|
||||
return vle1.equals(Vector2.direction(Vector2.from(pb), Vector2.from(pa)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 点是否在线段上
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param p
|
||||
* @returns
|
||||
*/
|
||||
export function isPointOnLine(
|
||||
p1: IPointData,
|
||||
p2: IPointData,
|
||||
p: IPointData
|
||||
): boolean {
|
||||
const vp1 = Vector2.from(p1);
|
||||
const vp2 = Vector2.from(p2);
|
||||
const vp = Vector2.from(p);
|
||||
if (vp1.equals(vp) || vp2.equals(vp)) {
|
||||
return true;
|
||||
}
|
||||
const vle = Vector2.direction(vp1, Vector2.from(p2));
|
||||
const vpe = Vector2.direction(vp1, vp);
|
||||
if (vle.equals(vpe)) {
|
||||
return (
|
||||
Vector2.difference(vp1, vp2).squaredLength() >=
|
||||
Vector2.difference(vp1, vp).squaredLength()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 两条线段是否存在包含关系
|
||||
* @param line1
|
||||
* @param line2
|
||||
* @returns
|
||||
*/
|
||||
export function isLineContainOther(
|
||||
line1: { p1: IPointData; p2: IPointData },
|
||||
line2: { p1: IPointData; p2: IPointData }
|
||||
): boolean {
|
||||
return (
|
||||
(isPointOnLine(line1.p1, line1.p2, line2.p1) &&
|
||||
isPointOnLine(line1.p1, line1.p2, line2.p2)) ||
|
||||
(isPointOnLine(line2.p1, line2.p2, line1.p1) &&
|
||||
isPointOnLine(line2.p1, line2.p2, line1.p2))
|
||||
);
|
||||
}
|
||||
|
@ -134,6 +134,12 @@ onMounted(() => {
|
||||
const dom = document.getElementById('draw-app-container');
|
||||
if (dom) {
|
||||
const drawApp = drawStore.initDrawApp(dom);
|
||||
drawApp.on('websocket-connected', (app) => {
|
||||
console.log('应用websocket成功连接');
|
||||
});
|
||||
drawApp.on('websocket-disconnected', (app) => {
|
||||
console.log('应用websocket断开连接');
|
||||
});
|
||||
loadDrawDatas(drawApp);
|
||||
onResize();
|
||||
drawApp.enableWsStomp({
|
||||
|
Loading…
Reference in New Issue
Block a user