Skip to content

Commit

Permalink
Fix positioning bug in 3-flap relay
Browse files Browse the repository at this point in the history
  • Loading branch information
MuTsunTsai committed Jan 25, 2024
1 parent e528a83 commit 74c15bd
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 86 deletions.
6 changes: 5 additions & 1 deletion src/core/design/layout/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,12 @@ export class Configuration implements ISerializable<JConfiguration> {
return this._patterns.$length;
}

public get $patterns(): readonly Pattern[] {
return this._patterns.$entries;
}

public get $pattern(): Pattern | null {
const patterns = this._patterns.$entries;
const patterns = this.$patterns;
if(patterns.length === 0) return null;
return patterns[this._index];
}
Expand Down
6 changes: 4 additions & 2 deletions src/core/design/layout/pattern/gadget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ export class Gadget implements JGadget {
this.anchors = clone(data.anchors); // Must clone!
}

@cache public get sx(): number {
/** The width of the stretch circumscribing rectangle (SCR). */
@cache public get scrX(): number {
return Math.ceil(this.$anchorMap[2][0].x) - Math.floor(this.$anchorMap[0][0].x);
}

@cache public get sy(): number {
/** The height of the stretch circumscribing rectangle (SCR). */
@cache public get scrY(): number {
return Math.ceil(this.$anchorMap[2][0].y) - Math.floor(this.$anchorMap[0][0].y);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class PositioningContext {
const r = this.$repo.$getMaxIntersectionDistance(j1, j2, oriented);
if(j2.ox > j1.ox) [j1, j2] = [j2, j1];
let p: IPoint = { x: r - j2.ox, y: r - j1.oy };
if(!oriented) p = { x: g.sx - p.x, y: g.sy - p.y };
if(!oriented) p = { x: g.scrX - p.x, y: g.scrY - p.y };
return new Point(p);
}

Expand Down Expand Up @@ -102,8 +102,8 @@ export class PositioningContext {
} else {
// If there're mutual connections, we don't need to setup
// the slack for real, but we keep a record for later usage.
const tx1 = g1.sx + g2.rx(q, corner.q!);
const tx2 = g2.sx + g1.rx(2 - q, opposite(corner.q!));
const tx1 = g1.scrX + g2.rx(q, corner.q!);
const tx2 = g2.scrX + g1.rx(2 - q, opposite(corner.q!));
if(tx2 > tx1) this._slackMap.set(corner, tx2 - tx1);
}
}
Expand All @@ -128,7 +128,7 @@ export class PositioningContext {
while(overlapIndices.size > 0) {
const first = getFirst(overlapIndices)!;
overlapIndices.delete(first);
const result = this.$gadgets[first].sx +
const result = this.$gadgets[first].scrX +
this._getSpan(first, 0, callback) +
this._getSpan(first, 2, callback);
if(result > maxSpan) maxSpan = result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ export function singleJunctionPositioner(context: PositioningContext): boolean {

if(devices.length == 1) {
// If there's only one GOPS, center it
devices[0].$offset = Math.floor((sx - devices[0].$gadgets[0].sx) / 2);
devices[0].$offset = Math.floor((sx - devices[0].$gadgets[0].scrX) / 2);
return true;
}

/* istanbul ignore else: not yet implemented */
if(devices.length == 2) {
const [g1, g2] = devices.map(d => d.$gadgets[0]);
const o2 = devices[1].$partition.$overlaps[0];
const tx = g2.sx + g1.rx(o2.c[0].q!, 0);
const tx = g2.scrX + g1.rx(o2.c[0].q!, 0);
// There's no need to check for total span here anymore,
// as the general checking covers it already.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ function makeTwoDeviceRelayPattern(context: PositioningContext): boolean {
if(g2.$intersects(deltaPt, oriented ? QV[0] : QV[2])) return false;

// Push them towards the shared corner as much as possible
const offsets = oriented ?
[Math.floor(g1.$slack[0]), 0] :
[j1.sx - context.$getSpan(g1, 2) - g1.sx, j2.sx - g2.sx];
const slack = Math.floor(g1.$slack[oriented ? 0 : 2]);
const offsets = oriented ? [slack, 0] :
[j1.sx - context.$getSpan(g1, 2) - g1.scrX - slack, j2.sx - g2.scrX];
if(reversed) offsets.reverse();
context.$devices.forEach((d, i) => d.$offset = offsets[i]);
return true;
Expand All @@ -70,7 +70,7 @@ function makeSpitJoinPattern(context: PositioningContext): boolean {
const j = context.$getJunctions(device)[0];
if(overlap.c[0].e! < 0) {
const gadget = device.$gadgets[0];
device.$offset = j.sx - context.$getSpan(gadget, 0) - gadget.sx;
device.$offset = j.sx - context.$getSpan(gadget, 0) - gadget.scrX;
}
}

Expand All @@ -82,6 +82,6 @@ function pushJoinDeviceTowardsJoint(context: PositioningContext, device: Device)
const oriented = j1.c[0].e == j2.c[0].e;
if(!oriented) {
const gadget = device.$gadgets[0];
device.$offset = j1.sx - context.$getSpan(gadget, 0) - gadget.sx;
device.$offset = j1.sx - context.$getSpan(gadget, 0) - gadget.scrX;
}
}
9 changes: 8 additions & 1 deletion src/shared/json/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface JConfiguration {
}

export interface JJunction extends JQuadrilateral {
/** The maximal space between the {@link Flap}s; always positive. */
/** The width of the flap rectangle (FR); always positive. */
sx: number;

/** Coefficients of transformation. */
Expand Down Expand Up @@ -105,9 +105,16 @@ export interface JAnchor {
}

export interface JPiece {
/** The width of the corresponding overlap rectangle (OR). */
ox: number;

/** The height of the corresponding overlap rectangle (OR). */
oy: number;

/** The width of the margin rectangle (MR). */
u: number;

/** The height of the margin rectangle (MR). */
v: number;

/** Detour in clockwise direction. The coordinates are before adding the shift. */
Expand Down
2 changes: 2 additions & 0 deletions test/specs/pattern/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import positioningSpec from "./positioning.spec";
import renderingSpec from "./rendering.spec";
import searchingSpec from "./searching.spec";

describe("Pattern", function() {

describe("Searching", searchingSpec);
describe("Positioning", positioningSpec);
describe("Rendering", renderingSpec);

});
16 changes: 16 additions & 0 deletions test/specs/pattern/positioning.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { parseTree } from "@utils/tree";
import { expectRepo } from "./util";

export default function() {
describe("Three flap relay", function() {

it("Pushes gadgets towards the shared corner as much as possible", function() {
parseTree("(0,1,1),(0,2,18),(0,3,1)", "(1,7,18,0,0),(2,0,0,0,0),(3,9,17,0,0)");
const devices = expectRepo("1,2,3", 1, 1, 2);
const device = devices.find(d => d.$gadgets[0].scrX == 3)!;
expect(device).to.be.not.undefined;
expect(device.$offset).to.equal(0);
});

});
}
56 changes: 11 additions & 45 deletions test/specs/pattern/threeFlap.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { State } from "core/service/state";
import { generateFromFlaps } from "./util";
import { expectRepo, generateFromFlaps } from "./util";
import { node, parseTree } from "@utils/tree";
import { toPath } from "core/math/geometry/rationalPath";

Expand All @@ -11,10 +10,7 @@ export default function() {
{ id: b, x: 0, y: 0, radius: 8 },
{ id: c, x: 6, y: 8, radius: 2 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
expect(stretch.$repo.$configurations[0].$length).to.equal(1);
expectRepo("1,2,3", 1, 1);

const B = node(b)!;
expect(B.$graphics.$patternContours.length).to.be.equal(1);
Expand All @@ -28,10 +24,7 @@ export default function() {
{ id: b, x: 0, y: 0, radius: 8 },
{ id: c, x: -6, y: 8, radius: 2 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
expect(stretch.$repo.$configurations[0].$length).to.equal(1);
expectRepo("1,2,3", 1, 1);

const B = node(b)!;
expect(B.$graphics.$patternContours.length).to.be.equal(1);
Expand Down Expand Up @@ -59,11 +52,7 @@ export default function() {
{ id: b, x: 8, y: 14, radius: 4 },
{ id: c, x: 15, y: 8, radius: 6 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
const config = stretch.$repo.$configurations[0];
expect(config.$length).to.equal(2, "Two half integral patterns");
expectRepo("1,2,3", 1, 2); // Two half integral patterns
}
});

Expand All @@ -74,14 +63,7 @@ export default function() {
{ id: b, x: 7, y: 14, radius: 4 },
{ id: c, x: 15, y: 8, radius: 6 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
const config = stretch.$repo.$configurations[0];
expect(config.$length).to.equal(1);
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(1);
const device = pattern.$devices[0];
const device = expectRepo("1,2,3", 1, 1, 1)[0];
expect(device.$addOns.length).to.equal(0, "Base joins have no addOn");
}
});
Expand All @@ -93,14 +75,10 @@ export default function() {
{ id: b, x: 5, y: 12, radius: 2 },
{ id: c, x: 15, y: 8, radius: 6 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
const config = stretch.$repo.$configurations[0];
expect(config.$length).to.equal(2, "Should find two standard joins.");
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(1, "Standard join creates 1 Device");
const device = pattern.$devices[0];
const device = expectRepo("1,2,3", 1,
2, //Should find two standard joins.
1 // Standard join creates 1 Device
)[0];
expect(device.$addOns.length).to.equal(1, "Standard join will have 1 addOn");
}
});
Expand All @@ -112,13 +90,7 @@ export default function() {
{ id: b, x: 7, y: 20, radius: 6 },
{ id: c, x: 16, y: 12, radius: 5 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
const config = stretch.$repo.$configurations[0];
expect(config.$length).to.equal(1);
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(2);
expectRepo("1,2,3", 1, 1, 2);
}

for(const [a, b, c] of THREE_PERMUTATION) {
Expand All @@ -127,13 +99,7 @@ export default function() {
{ id: b, x: 20, y: 7, radius: 6 },
{ id: c, x: 12, y: 16, radius: 5 },
]);
const stretch = State.$stretches.get("1,2,3")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
const config = stretch.$repo.$configurations[0];
expect(config.$length).to.equal(1);
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(2);
expectRepo("1,2,3", 1, 1, 2);
}
});
}
Expand Down
28 changes: 4 additions & 24 deletions test/specs/pattern/twoFlap.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { State } from "core/service/state";
import { generateFromFlaps } from "./util";
import { expectRepo, generateFromFlaps } from "./util";

export default function() {
it("Finds universal GPS patterns", function() {
Expand All @@ -8,14 +7,7 @@ export default function() {
{ id: a, x: 0, y: 0, radius: 6 },
{ id: b, x: 11, y: 5, radius: 6 },
]);
const stretch = State.$stretches.get("1,2")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(1);
const config = stretch.$repo.$configuration!;
expect(config.$length).to.equal(2);
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(1);
const device = pattern.$devices[0];
const device = expectRepo("1,2", 1, 2, 1)[0];
expect(device.$gadgets.length).to.equal(1);
const gadget = device.$gadgets[0];
expect(gadget.pieces.length).to.equal(2, "Universal GPS has two pieces");
Expand All @@ -29,27 +21,15 @@ export default function() {
{ id: a, x: 0, y: 0, radius: 8 },
{ id: b, x: 10, y: 7, radius: 4 },
]);
const stretch = State.$stretches.get("1,2")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(4);
const config = stretch.$repo.$configuration!;
expect(config.$length).to.equal(1);
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(2);
expectRepo("1,2", 4, 1, 2);
}

for(const [a, b] of TWO_PERMUTATION) {
generateFromFlaps([
{ id: a, x: 0, y: 0, radius: 8 },
{ id: b, x: 7, y: 10, radius: 4 },
]);
const stretch = State.$stretches.get("1,2")!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(4);
const config = stretch.$repo.$configuration!;
expect(config.$length).to.equal(1);
const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(2);
expectRepo("1,2", 4, 1, 2);
}
});
}
Expand Down
32 changes: 30 additions & 2 deletions test/specs/pattern/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createTree } from "@utils/tree";
import { State } from "core/service/state";

import type { Pattern } from "core/design/layout/pattern/pattern";
import type { Configuration } from "core/design/layout/configuration";
import type { Tree } from "core/design/context/tree";
import type { Device } from "core/design/layout/pattern/device";

export function complete(): void {
for(const stretch of State.$stretches.values()) stretch.$repo.$complete();
Expand All @@ -15,10 +18,35 @@ interface IFlap {
}

export function generateFromFlaps(flaps: IFlap[]): Tree {
const tree = createTree(
return createTree(
flaps.map(f => ({ n1: 0, n2: f.id, length: f.radius })),
flaps.map(f => ({ id: f.id, width: 0, height: 0, x: f.x, y: f.y }))
);
}

export function expectRepo(id: string, configCount: number): readonly Configuration[];
export function expectRepo(id: string, configCount: number, patternCount: number): readonly Pattern[];
export function expectRepo(
id: string, configCount: number, patternCount: number, deviceCount: number
): readonly Device[];
export function expectRepo(
id: string,
configCount: number,
patternCount?: number,
deviceCount?: number
): readonly Configuration[] | readonly Pattern[] | readonly Device[] {

complete();
return tree;
const stretch = State.$stretches.get(id)!;
expect(stretch).to.be.not.undefined;
expect(stretch.$repo.$configurations.length).to.equal(configCount);
if(patternCount === undefined) return stretch.$repo.$configurations;

const config = stretch.$repo.$configurations[0];
expect(config.$length).to.equal(patternCount);
if(deviceCount === undefined) return config.$patterns;

const pattern = config.$pattern!;
expect(pattern.$devices.length).to.equal(deviceCount);
return pattern.$devices;
}

0 comments on commit 74c15bd

Please sign in to comment.