可吸附位置功能调整:所有位置都执行吸附,所有图形也都执行吸附(之前一个吸附成功就返回了)

添加过滤重复的可吸附位置对象逻辑
This commit is contained in:
walker 2023-06-19 17:24:55 +08:00
parent 533ebdc9a8
commit ecfe1421bb
8 changed files with 224 additions and 29 deletions

View File

@ -1,7 +1,10 @@
import { FederatedMouseEvent, Point } from 'pixi.js'; import { FederatedMouseEvent, Point } from 'pixi.js';
import { import {
AbsorbableLine,
AbsorbablePosition,
GraphicDrawAssistant, GraphicDrawAssistant,
GraphicInteractionPlugin, GraphicInteractionPlugin,
GraphicTransformEvent,
JlDrawApp, JlDrawApp,
JlGraphic, JlGraphic,
} from 'src/jlgraphic'; } from 'src/jlgraphic';
@ -68,6 +71,16 @@ export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
g.cursor = 'pointer'; g.cursor = 'pointer';
// g.scalable = true; // g.scalable = true;
g.rotatable = 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 { unbind(g: IscsFan): void {
g.eventMode = 'none'; g.eventMode = 'none';
@ -75,3 +88,23 @@ export class IscsFanInteraction extends GraphicInteractionPlugin<IscsFan> {
g.rotatable = false; 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;
}

View File

@ -288,6 +288,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
drag_op_end: [event: AppDragEvent]; drag_op_end: [event: AppDragEvent];
'pre-menu-handle': [menu: MenuItemOptions]; 'pre-menu-handle': [menu: MenuItemOptions];
'post-menu-handle': [menu: MenuItemOptions]; 'post-menu-handle': [menu: MenuItemOptions];
'websocket-state-change': [app: GraphicApp, connected: boolean];
destroy: [app: GraphicApp]; destroy: [app: GraphicApp];
} }
@ -888,10 +889,10 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
this.emit('destroy', this); this.emit('destroy', this);
if (this.wsMsgBroker) { if (this.wsMsgBroker) {
this.wsMsgBroker.close(); this.wsMsgBroker.close();
if (!StompCli.hasAppMsgBroker()) { // if (!StompCli.hasAppMsgBroker()) {
// 如果没有其他消息代理关闭websocket Stomp客户端 // // 如果没有其他消息代理关闭websocket Stomp客户端
StompCli.close(); // StompCli.close();
} // }
} }
this.interactionPluginMap.forEach((plugin) => { this.interactionPluginMap.forEach((plugin) => {
plugin.pause(); plugin.pause();

View File

@ -11,6 +11,8 @@ import {
calculateFootPointFromPointToLine, calculateFootPointFromPointToLine,
calculateIntersectionPointOfCircleAndPoint, calculateIntersectionPointOfCircleAndPoint,
distance, distance,
distance2,
isLineContainOther,
linePoint, linePoint,
} from '../utils'; } from '../utils';
import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic'; import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
@ -19,12 +21,25 @@ import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic';
* *
*/ */
export interface AbsorbablePosition extends Container { export interface AbsorbablePosition extends Container {
/**
* ()
* @param other
*/
isOverlapping(other: AbsorbablePosition): boolean;
/**
*
* @param other
* @returns >0<0另一个吸附范围大=0两个吸附范围一样大
*/
compareTo(other: AbsorbablePosition): number;
/** /**
* *
* @param objs * @param objs
* @returns truefalse * @returns truefalse
*/ */
tryAbsorb(...objs: DisplayObject[]): boolean; tryAbsorb(...objs: DisplayObject[]): void;
} }
/** /**
@ -65,7 +80,22 @@ export default class AbsorbablePoint
this.interactive; this.interactive;
VectorGraphicUtil.handle(this); 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++) { for (let i = 0; i < objs.length; i++) {
const obj = objs[i]; const obj = objs[i];
if ( if (
@ -73,10 +103,8 @@ export default class AbsorbablePoint
this.absorbRange this.absorbRange
) { ) {
obj.position.copyFrom(this._point); obj.position.copyFrom(this._point);
return true;
} }
} }
return false;
} }
updateOnScaled() { updateOnScaled() {
const scaled = this.getAllParentScaled(); const scaled = this.getAllParentScaled();
@ -101,6 +129,22 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition {
this.absorbRange = absorbRange; this.absorbRange = absorbRange;
this.redraw(); 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() { redraw() {
this.clear(); this.clear();
this.lineStyle(1, new Color(this._color)); 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); this.lineTo(this.p2.x, this.p2.y);
} }
tryAbsorb(...objs: DisplayObject[]): boolean { tryAbsorb(...objs: DisplayObject[]): void {
for (let i = 0; i < objs.length; i++) { for (let i = 0; i < objs.length; i++) {
const obj = objs[i]; const obj = objs[i];
const p = obj.position.clone(); const p = obj.position.clone();
if (linePoint(this.p1, this.p2, p, this.absorbRange, true)) { if (linePoint(this.p1, this.p2, p, this.absorbRange, true)) {
const fp = calculateFootPointFromPointToLine(this.p1, this.p2, p); const fp = calculateFootPointFromPointToLine(this.p1, this.p2, p);
obj.position.copyFrom(fp); obj.position.copyFrom(fp);
return true;
} }
} }
return false;
} }
} }
@ -138,6 +180,18 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
this.absorbRange = absorbRange; this.absorbRange = absorbRange;
this.redraw(); 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() { redraw() {
this.clear(); this.clear();
@ -145,7 +199,7 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
this.drawCircle(this.p0.x, this.p0.y, this.radius); 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++) { for (let i = 0; i < objs.length; i++) {
const obj = objs[i]; const obj = objs[i];
const len = distance( const len = distance(
@ -165,9 +219,7 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition {
obj.position obj.position
); );
obj.position.copyFrom(p); obj.position.copyFrom(p);
return true;
} }
} }
return false;
} }
} }

View File

@ -22,7 +22,7 @@ export interface StompCliOption {
* @returns * @returns
*/ */
onAuthenticationFailed?: () => void; onAuthenticationFailed?: () => void;
reconnectDelay?: number; // 重连延时默认3秒 reconnectDelay?: number; // 重连延时默认3秒,设置为0不重连.
heartbeatIncoming?: number; // 服务端过来的心跳间隔默认30秒 heartbeatIncoming?: number; // 服务端过来的心跳间隔默认30秒
heartbeatOutgoing?: number; // 到服务端的心跳间隔默认30秒 heartbeatOutgoing?: number; // 到服务端的心跳间隔默认30秒
} }
@ -36,18 +36,22 @@ const DefaultStompOption: StompCliOption = {
}; };
export class StompCli { export class StompCli {
private static enabled = false;
private static options: StompCliOption;
private static client: StompClient; private static client: StompClient;
private static options: StompCliOption;
private static appMsgBroker: AppWsMsgBroker[] = []; private static appMsgBroker: AppWsMsgBroker[] = [];
/**
* key-
*/
subscriptions: Map<string, AppStateSubscription> = new Map<
string,
AppStateSubscription
>();
private static connected = false; private static connected = false;
static new(options: StompCliOption) { static new(options: StompCliOption) {
if (StompCli.enabled) { if (StompCli.client) {
// 已经启用 // 已经创建
return; return;
// throw new Error('websocket 已连接若确实需要重新连接请先断开StompCli.close再重新StompCli.new')
} }
StompCli.enabled = true;
StompCli.options = Object.assign({}, DefaultStompOption, options); StompCli.options = Object.assign({}, DefaultStompOption, options);
StompCli.client = new StompClient({ StompCli.client = new StompClient({
brokerURL: StompCli.options.wsUrl, brokerURL: StompCli.options.wsUrl,
@ -62,6 +66,7 @@ export class StompCli {
StompCli.client.onConnect = () => { StompCli.client.onConnect = () => {
// console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length) // console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length)
StompCli.connected = true; StompCli.connected = true;
StompCli.emitConnectStateChangeEvent();
StompCli.appMsgBroker.forEach((broker) => { StompCli.appMsgBroker.forEach((broker) => {
broker.resubscribe(); broker.resubscribe();
}); });
@ -83,10 +88,12 @@ export class StompCli {
StompCli.client.onDisconnect = (frame: Frame) => { StompCli.client.onDisconnect = (frame: Frame) => {
console.log('Stomp 断开连接', frame); console.log('Stomp 断开连接', frame);
StompCli.connected = false; StompCli.connected = false;
StompCli.emitConnectStateChangeEvent();
}; };
StompCli.client.onWebSocketClose = (evt: CloseEvent) => { StompCli.client.onWebSocketClose = (evt: CloseEvent) => {
console.log('websocket 关闭', evt); console.log('websocket 关闭', evt);
StompCli.connected = false; StompCli.connected = false;
StompCli.emitConnectStateChangeEvent();
}; };
// websocket错误处理 // websocket错误处理
StompCli.client.onWebSocketError = (err: Event) => { StompCli.client.onWebSocketError = (err: Event) => {
@ -96,8 +103,10 @@ export class StompCli {
StompCli.client.activate(); StompCli.client.activate();
} }
static isEnabled(): boolean { static emitConnectStateChangeEvent() {
return StompCli.enabled; StompCli.appMsgBroker.forEach((broker) => {
broker.app.emit('websocket-state-change', broker.app, StompCli.connected);
});
} }
static isConnected(): boolean { static isConnected(): boolean {
@ -135,7 +144,6 @@ export class StompCli {
* websocket连接 * websocket连接
*/ */
static close() { static close() {
StompCli.enabled = false;
StompCli.connected = false; StompCli.connected = false;
if (StompCli.client) { if (StompCli.client) {
StompCli.client.deactivate(); StompCli.client.deactivate();

View File

@ -194,7 +194,7 @@ export class CommonMouseTool extends AppInteractionPlugin {
const graphic = this.leftDownTarget.getGraphic(); const graphic = this.leftDownTarget.getGraphic();
if (graphic) { if (graphic) {
const app = this.app; const app = this.app;
console.log(this.leftDownTarget.isGraphic()); // console.log(this.leftDownTarget.isGraphic());
// 图形选中 // 图形选中
if (!e.ctrlKey && !graphic.selected && graphic.selectable) { if (!e.ctrlKey && !graphic.selected && graphic.selectable) {
app.updateSelected(graphic); app.updateSelected(graphic);

View File

@ -171,11 +171,40 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
this.app.canvas.addAssistantAppend(this.apContainer); this.app.canvas.addAssistantAppend(this.apContainer);
app.on('options-update', (options) => { app.on('options-update', (options) => {
if (options.absorbablePositions) { 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) { static new(app: GraphicApp) {
return new GraphicTransformPlugin(app); return new GraphicTransformPlugin(app);
} }
@ -266,9 +295,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
if (this.absorbablePositions) { if (this.absorbablePositions) {
for (let i = 0; i < this.absorbablePositions.length; i++) { for (let i = 0; i < this.absorbablePositions.length; i++) {
const ap = this.absorbablePositions[i]; const ap = this.absorbablePositions[i];
if (ap.tryAbsorb(...targets)) { ap.tryAbsorb(...targets);
break;
}
} }
} }
// 事件发布 // 事件发布

View File

@ -607,3 +607,71 @@ export function getIntersectionPoint(line1: number[], line2: number[]) {
-denominator; -denominator;
return new Point(x, y); 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))
);
}

View File

@ -134,6 +134,12 @@ onMounted(() => {
const dom = document.getElementById('draw-app-container'); const dom = document.getElementById('draw-app-container');
if (dom) { if (dom) {
const drawApp = drawStore.initDrawApp(dom); const drawApp = drawStore.initDrawApp(dom);
drawApp.on('websocket-connected', (app) => {
console.log('应用websocket成功连接');
});
drawApp.on('websocket-disconnected', (app) => {
console.log('应用websocket断开连接');
});
loadDrawDatas(drawApp); loadDrawDatas(drawApp);
onResize(); onResize();
drawApp.enableWsStomp({ drawApp.enableWsStomp({