Skip to content

Commit

Permalink
#5107 - Implemented move and resize multitail head and tails
Browse files Browse the repository at this point in the history
  • Loading branch information
daniil-sloboda committed Aug 30, 2024
1 parent 968e55c commit 3311539
Show file tree
Hide file tree
Showing 19 changed files with 853 additions and 256 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ReStruct } from 'application/render';
import { MultitailArrowReferencePosition, ReStruct } from 'application/render';
import {
Action,
MultitailArrowDelete,
MultitailArrowUpsert,
MultitailArrowMove,
MultitailArrowAddTail,
MultitailArrowRemoveTail,
MultitailArrowResizeTailHead,
MultitailArrowMoveHeadTail,
} from 'application/editor';
import { Vec2, MultitailArrow } from 'domain/entities';

Expand Down Expand Up @@ -54,3 +56,30 @@ export function fromMultitailArrowTailRemove(
action.addOp(new MultitailArrowRemoveTail(id, tailId));
return action.perform(reStruct);
}

export function fromMultitailArrowHeadTailsResize(
reStruct: ReStruct,
id: number,
ref: MultitailArrowReferencePosition,
offset: number,
) {
const action = new Action();
action.addOp(
new MultitailArrowResizeTailHead(id, offset, ref.name === 'head'),
);
return action.perform(reStruct);
}

export function fromMultitailArrowHeadTailMove(
reStruct: ReStruct,
id: number,
ref: MultitailArrowReferencePosition,
offset: number,
normalize?: true,
) {
const action = new Action();
action.addOp(
new MultitailArrowMoveHeadTail(id, offset, ref.name, ref.tailId, normalize),
);
return action.perform(reStruct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export const OperationType = Object.freeze({
MULTITAIL_ARROW_MOVE: 'Move multitail arrow',
MULTITAIL_ARROW_ADD_TAIL: 'Add multitail arrow tail',
MULTITAIL_ARROW_REMOVE_TAIL: 'Remove multitail arrow tail',
MULTITAIL_ARROW_RESIZE_HEAD_TAIL: 'Resize head tail',
MULTITAIL_ARROW_MOVE_HEAD_TAIL: 'Move head tail',
});

export enum OperationPriority {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './multitailArrowAddRemoveTail';
export * from './multitailArrowMove';
export * from './multitailArrowMoveHeadTail';
export * from './multitailArrowResizeTailHead';
export * from './multitailArrowUpsertDelete';
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BaseOperation } from 'application/editor/operations/base';
import { OperationType } from 'application/editor';
import { MultitailArrowRefName, ReStruct } from 'application/render';
import { MULTITAIL_ARROW_KEY } from 'domain/constants';

export class MultitailArrowMoveHeadTail extends BaseOperation {
constructor(
private id: number,
private offset: number,
private name: string,
private tailId: number | null,
private normalize?: true,
) {
super(OperationType.MULTITAIL_ARROW_MOVE_HEAD_TAIL);
}

execute(reStruct: ReStruct) {
const reMultitailArrow = reStruct.multitailArrows.get(this.id);
const multitailArrow = reStruct.molecule.multitailArrows.get(this.id);
if (!multitailArrow || !reMultitailArrow) {
return;
}
switch (this.name) {
case MultitailArrowRefName.HEAD:
this.offset = multitailArrow.moveHead(this.offset);
break;
case MultitailArrowRefName.TOP_TAIL:
this.offset = multitailArrow.moveTail(this.offset, this.name);
break;
case MultitailArrowRefName.BOTTOM_TAIL:
this.offset = multitailArrow.moveTail(this.offset, this.name);
break;
default:
this.offset = multitailArrow.moveTail(
this.offset,
this.tailId as number,
this.normalize,
);
}
BaseOperation.invalidateItem(reStruct, MULTITAIL_ARROW_KEY, this.id, 1);
}

invert(): BaseOperation {
return new MultitailArrowMoveHeadTail(
this.id,
-this.offset,
this.name,
this.tailId,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BaseOperation } from 'application/editor/operations/base';
import { OperationType } from 'application/editor';
import { ReStruct } from 'application/render';
import { MULTITAIL_ARROW_KEY } from 'domain/constants';

export class MultitailArrowResizeTailHead extends BaseOperation {
constructor(
private id: number,
private offset: number,
private isHead: boolean,
) {
super(OperationType.MULTITAIL_ARROW_RESIZE_HEAD_TAIL);
}

execute(reStruct: ReStruct) {
const multitailArrow = reStruct.molecule.multitailArrows.get(this.id);
if (!multitailArrow) {
return;
}
this.offset = this.isHead
? multitailArrow.resizeHead(this.offset)
: multitailArrow.resizeTails(this.offset);
BaseOperation.invalidateItem(reStruct, MULTITAIL_ARROW_KEY, this.id, 1);
}

invert(): BaseOperation {
return new MultitailArrowResizeTailHead(this.id, -this.offset, this.isHead);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,24 @@ import { Scale } from 'domain/helpers';
import { Box2Abs, Pool, Vec2 } from 'domain/entities';
import util from 'application/render/util';

interface ClosestReferencePosition {
export enum MultitailArrowRefName {
HEAD = 'head',
TAILS = 'tails',
TOP_TAIL = 'topTail',
BOTTOM_TAIL = 'bottomTail',
SPINE = 'spine',
}

export interface MultitailArrowReferencePosition {
name: MultitailArrowRefName;
offset: Vec2;
isLine: boolean;
tailId: number | null;
}

export interface MultitailArrowClosestReferencePosition {
distance: number;
ref: { name: string; offset: Vec2 } | null;
ref: MultitailArrowReferencePosition | null;
}

export class ReMultitailArrow extends ReObject {
Expand All @@ -20,11 +35,9 @@ export class ReMultitailArrow extends ReObject {
return true;
}

static getTailIdFromRef(
ref?: ClosestReferencePosition['ref'],
): number | null {
if (ref && ref.name.startsWith(ReMultitailArrow.TAILS_NAME)) {
return parseInt(ref.name.replace(`${ReMultitailArrow.TAILS_NAME}-`, ''));
static getTailIdFromRefName(name: string): number | null {
if (name.startsWith(MultitailArrowRefName.TAILS)) {
return parseInt(name.replace(`${MultitailArrowRefName.TAILS}-`, ''));
}
return null;
}
Expand All @@ -33,31 +46,21 @@ export class ReMultitailArrow extends ReObject {
super(MULTITAIL_ARROW_KEY);
}

getReferencePositions(renderOptions: RenderOptions) {
getReferencePositions(
renderOptions: RenderOptions,
): ReturnType<MultitailArrow['getReferencePositions']> {
const positions = this.multitailArrow.getReferencePositions();
const tails = new Pool<Vec2>();
positions.tails.forEach((item, key) => {
tails.set(key, Scale.modelToCanvas(item, renderOptions));
});

return {
headPosition: Scale.modelToCanvas(positions.headPosition, renderOptions),
topTailPosition: Scale.modelToCanvas(
positions.topTailPosition,
renderOptions,
),
bottomTailPosition: Scale.modelToCanvas(
positions.bottomTailPosition,
renderOptions,
),
topSpinePosition: Scale.modelToCanvas(
positions.topSpinePosition,
renderOptions,
),
bottomSpinePosition: Scale.modelToCanvas(
positions.bottomSpinePosition,
renderOptions,
),
head: Scale.modelToCanvas(positions.head, renderOptions),
topTail: Scale.modelToCanvas(positions.topTail, renderOptions),
bottomTail: Scale.modelToCanvas(positions.bottomTail, renderOptions),
topSpine: Scale.modelToCanvas(positions.topSpine, renderOptions),
bottomSpine: Scale.modelToCanvas(positions.bottomSpine, renderOptions),
tails,
};
}
Expand All @@ -78,26 +81,21 @@ export class ReMultitailArrow extends ReObject {
reStruct.clearVisel(this.visel);
const pathBuilder = new PathBuilder();
const headPathBuilder = new PathBuilder();
const {
topTailPosition,
topSpinePosition,
bottomSpinePosition,
headPosition,
tails,
} = this.getReferencePositions(renderOptions);
const topTailOffsetX = topSpinePosition.sub(topTailPosition).x;
const arrowStart = new Vec2(topSpinePosition.x, headPosition.y);
const arrowLength = headPosition.x - arrowStart.x;
const { topTail, topSpine, bottomSpine, head, tails } =
this.getReferencePositions(renderOptions);
const topTailOffsetX = topSpine.sub(topTail).x;
const arrowStart = new Vec2(topSpine.x, head.y);
const arrowLength = head.x - arrowStart.x;

pathBuilder.addMultitailArrowBase(
topSpinePosition.y,
bottomSpinePosition.y,
topSpinePosition.x,
topSpine.y,
bottomSpine.y,
topSpine.x,
topTailOffsetX,
);
headPathBuilder.addFilledTriangleArrowPathParts(arrowStart, arrowLength);
tails.forEach((tail) => {
pathBuilder.addLine(tail, { x: topSpinePosition.x, y: tail.y });
pathBuilder.addLine(tail, { x: topSpine.x, y: tail.y });
});

const path = reStruct.render.paper.path(pathBuilder.build());
Expand All @@ -111,32 +109,113 @@ export class ReMultitailArrow extends ReObject {
this.visel.add(header, Box2Abs.fromRelBox(util.relBox(header.getBBox())));
}

private calculateDistanceToNamedEntity(
point: Vec2,
entities: Array<[string, Vec2]>,
isLine: false,
): MultitailArrowClosestReferencePosition;

private calculateDistanceToNamedEntity(
point: Vec2,
entities: Array<[string, Line]>,
isLine: true,
): MultitailArrowClosestReferencePosition;

private calculateDistanceToNamedEntity(
point: Vec2,
entities: Array<[string, Vec2 | Line]>,
isLine: boolean,
): MultitailArrowClosestReferencePosition {
return entities.reduce(
(acc, [name, value]) => {
const distance = isLine
? point.calculateDistanceToLine(value as Line)
: Vec2.dist(point, value as Vec2);
const tailId = ReMultitailArrow.getTailIdFromRefName(name);
let refName: MultitailArrowRefName;
if (typeof tailId === 'number') {
refName = MultitailArrowRefName.TAILS;
} else if (
[
MultitailArrowRefName.HEAD,
MultitailArrowRefName.BOTTOM_TAIL,
MultitailArrowRefName.TOP_TAIL,
].includes(name as MultitailArrowRefName)
) {
refName = name as MultitailArrowRefName;
} else {
refName = MultitailArrowRefName.SPINE;
}

return distance < acc.distance
? {
distance,
ref: {
name: refName,
offset: new Vec2(0, 0),
isLine,
tailId,
},
}
: acc;
},
{
distance: Infinity,
ref: null,
} as MultitailArrowClosestReferencePosition,
);
}

private tailArrayFromPool<T>(tails: Pool<T>): Array<[string, T]> {
return Array.from(tails.entries()).map(([key, value]) => [
`${ReMultitailArrow.TAILS_NAME}-${key}`,
value,
]);
}

calculateDistanceToPoint(
point: Vec2,
renderOptions: RenderOptions,
): ClosestReferencePosition {
maxDistanceToPoint: number,
): MultitailArrowClosestReferencePosition {
const referencePositions = this.getReferencePositions(renderOptions);
const referenceLines = this.getReferenceLines(
renderOptions,
referencePositions,
);
const { tails, ...rest } = referenceLines;
const tailsLines: Array<[string, Line]> = Array.from(tails.entries()).map(
([key, value]) => [`${ReMultitailArrow.TAILS_NAME}-${key}`, value],
const lines: Array<[string, Line]> = Object.entries(rest).concat(
this.tailArrayFromPool(tails),
);
const lines: Array<[string, Line]> =
Object.entries(rest).concat(tailsLines);

const res = lines.reduce(
(acc, [name, value]): ClosestReferencePosition => {
const distance = point.calculateDistanceToLine(value);
return distance < acc.distance
? { distance, ref: { name, offset: new Vec2(0, 0) } }
: acc;
},
{ distance: Infinity, ref: null } as ClosestReferencePosition,
);
const lineRes = this.calculateDistanceToNamedEntity(point, lines, true);

if (lineRes.distance < maxDistanceToPoint) {
const {
topSpine: _t,
bottomSpine: _b,
tails: tailsPoints,
...validReferencePositions
} = referencePositions;

const points: Array<[string, Vec2]> = Object.entries(
validReferencePositions,
).concat(this.tailArrayFromPool(tailsPoints));

const pointsRes = this.calculateDistanceToNamedEntity(
point,
points,
false,
);
if (
pointsRes.distance < maxDistanceToPoint / 2 ||
(pointsRes.distance < maxDistanceToPoint &&
pointsRes.distance <= lineRes.distance)
) {
return pointsRes;
}
}

return res;
return lineRes;
}
}
Loading

0 comments on commit 3311539

Please sign in to comment.