Skip to content

Commit 0e2bee0

Browse files
fixed scaling issues with hex grid on face, improved fillet code, added extra functions to points class to determine what is the safe radius between three points both in full and in half-line situations.
1 parent 08de1f2 commit 0e2bee0

File tree

9 files changed

+738
-87
lines changed

9 files changed

+738
-87
lines changed

packages/dev/base/lib/api/inputs/point-inputs.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,62 @@ export namespace Point {
411411
*/
412412
reverseNormal = false;
413413
}
414+
export class ThreePointsToleranceDto {
415+
constructor(start?: Base.Point3, center?: Base.Point3, end?: Base.Point3, tolerance?: number) {
416+
if (start !== undefined) { this.start = start; }
417+
if (center !== undefined) { this.center = center; }
418+
if (end !== undefined) { this.end = end; }
419+
if (tolerance !== undefined) { this.tolerance = tolerance; }
420+
}
421+
/**
422+
* Start point
423+
* @default undefined
424+
*/
425+
start?: Base.Point3;
426+
/**
427+
* Center point
428+
* @default undefined
429+
*/
430+
center?: Base.Point3;
431+
/**
432+
* End point
433+
* @default undefined
434+
*/
435+
end?: Base.Point3;
436+
/**
437+
* Tolerance for the calculation
438+
* @default 1e-7
439+
* @minimum -Infinity
440+
* @maximum Infinity
441+
* @step 1e-7
442+
*/
443+
tolerance = 1e-7;
444+
}
445+
export class PointsMaxFilletsHalfLineDto {
446+
constructor(points?: Base.Point3[], checkLastWithFirst?: boolean, tolerance?: number) {
447+
if (points !== undefined) { this.points = points; }
448+
if (checkLastWithFirst !== undefined) { this.checkLastWithFirst = checkLastWithFirst; }
449+
if (tolerance !== undefined) { this.tolerance = tolerance; }
450+
}
451+
/**
452+
* Points to transform
453+
* @default undefined
454+
*/
455+
points?: Base.Point3[];
456+
/**
457+
* Check first and last point for duplicates
458+
* @default false
459+
*/
460+
checkLastWithFirst? = false;
461+
/**
462+
* Tolerance for the calculation
463+
* @default 1e-7
464+
* @minimum -Infinity
465+
* @maximum Infinity
466+
* @step 1e-7
467+
*/
468+
tolerance? = 1e-7;
469+
}
414470
export class RemoveConsecutiveDuplicatesDto {
415471
constructor(points?: Base.Point3[], tolerance?: number, checkFirstAndLast?: boolean) {
416472
if (points !== undefined) { this.points = points; }
@@ -579,8 +635,8 @@ export namespace Point {
579635
constructor(wdith?: number, height?: number, nrHexagonsU?: number, nrHexagonsV?: number, centerGrid?: boolean, pointsOnGround?: boolean) {
580636
if (wdith !== undefined) { this.width = wdith; }
581637
if (height !== undefined) { this.height = height; }
582-
if (nrHexagonsU !== undefined) { this.nrHexagonsU = nrHexagonsU; }
583-
if (nrHexagonsV !== undefined) { this.nrHexagonsV = nrHexagonsV; }
638+
if (nrHexagonsU !== undefined) { this.nrHexagonsInHeight = nrHexagonsU; }
639+
if (nrHexagonsV !== undefined) { this.nrHexagonsInWidth = nrHexagonsV; }
584640
if (centerGrid !== undefined) { this.centerGrid = centerGrid; }
585641
if (pointsOnGround !== undefined) { this.pointsOnGround = pointsOnGround; }
586642
}
@@ -598,20 +654,20 @@ export namespace Point {
598654
* @step 0.1
599655
*/
600656
height? = 10;
601-
/** Number of hexagons desired horizontally (U direction).
657+
/** Number of hexagons desired in width.
602658
* @default 10
603659
* @minimum 0
604660
* @maximum Infinity
605661
* @step 1
606662
*/
607-
nrHexagonsU? = 10;
608-
/** Number of hexagons desired vertically (V direction).
663+
nrHexagonsInWidth? = 10;
664+
/** Number of hexagons desired in height.
609665
* @default 10
610666
* @minimum 0
611667
* @maximum Infinity
612668
* @step 1
613669
*/
614-
nrHexagonsV? = 10;
670+
nrHexagonsInHeight? = 10;
615671
/** If true, shift the entire grid up by half hex height.
616672
* @default false
617673
*/
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Base } from "../../inputs/base-inputs";
22

33
export class HexGridData {
4-
points: Base.Point3[];
4+
centers: Base.Point3[];
55
hexagons: Base.Point3[][];
6+
shortestDistEdge: number;
7+
longestDistEdge: number;
8+
maxFilletRadius: number;
69
}

packages/dev/base/lib/api/services/point.test.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,184 @@ describe("Point unit tests", () => {
668668
const result = point.sortPoints(input);
669669
expect(result).toEqual(expected);
670670
});
671+
});
672+
673+
describe("calculateMaxFilletRadius", () => {
674+
const defaultTolerance = 1e-7; // Default tolerance for the function if not provided
675+
const precision = 6; // Decimal places for toBeCloseTo assertions
676+
677+
// Helper to create input object matching the confusing naming convention
678+
const createInput = (p1: Inputs.Base.Point3, corner: Inputs.Base.Point3, p2: Inputs.Base.Point3, tolerance: number = defaultTolerance): Inputs.Point.ThreePointsToleranceDto => ({
679+
start: p1,
680+
center: p2,
681+
end: corner,
682+
tolerance: tolerance
683+
});
684+
685+
it("should calculate correct radius for a simple 90-degree corner (2D)", () => {
686+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
687+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
688+
const geoP2: Inputs.Base.Point3 = [0, 3, 0];
689+
const input = createInput(geoP1, geoC, geoP2);
690+
expect(point.maxFilletRadius(input)).toBeCloseTo(3.0, precision);
691+
});
692+
693+
it("should calculate correct radius for a symmetric 90-degree corner (2D)", () => {
694+
const geoP1: Inputs.Base.Point3 = [4, 0, 0];
695+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
696+
const geoP2: Inputs.Base.Point3 = [0, 4, 0];
697+
const input = createInput(geoP1, geoC, geoP2);
698+
expect(point.maxFilletRadius(input)).toBeCloseTo(4.0, precision);
699+
});
700+
701+
it("should calculate correct radius for an acute angle (60 degrees, 2D)", () => {
702+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
703+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
704+
const geoP2: Inputs.Base.Point3 = [5 * Math.cos(Math.PI / 3), 5 * Math.sin(Math.PI / 3), 0];
705+
const input = createInput(geoP1, geoC, geoP2);
706+
const expected = 5 * Math.tan(Math.PI / 6);
707+
expect(point.maxFilletRadius(input)).toBeCloseTo(expected, precision);
708+
});
709+
710+
it("should calculate correct radius for an obtuse angle (120 degrees, 2D)", () => {
711+
const geoP1: Inputs.Base.Point3 = [4, 0, 0];
712+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
713+
const geoP2: Inputs.Base.Point3 = [6 * Math.cos(2 * Math.PI / 3), 6 * Math.sin(2 * Math.PI / 3), 0];
714+
const input = createInput(geoP1, geoC, geoP2);
715+
const expected = 4 * Math.tan(Math.PI / 3);
716+
expect(point.maxFilletRadius(input)).toBeCloseTo(expected, precision);
717+
});
718+
719+
it("should return 0 for collinear points (0 degrees)", () => {
720+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
721+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
722+
const geoP2: Inputs.Base.Point3 = [10, 0, 0];
723+
const input = createInput(geoP1, geoC, geoP2);
724+
expect(point.maxFilletRadius(input)).toBeCloseTo(0, precision);
725+
});
726+
727+
it("should return 0 for collinear points (180 degrees)", () => {
728+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
729+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
730+
const geoP2: Inputs.Base.Point3 = [-3, 0, 0];
731+
const input = createInput(geoP1, geoC, geoP2);
732+
expect(point.maxFilletRadius(input)).toBeCloseTo(0, precision);
733+
});
734+
735+
it("should return 0 if one segment has near-zero length (P1=C)", () => {
736+
const geoP1: Inputs.Base.Point3 = [defaultTolerance / 2, 0, 0]; // Very close to C
737+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
738+
const geoP2: Inputs.Base.Point3 = [0, 3, 0];
739+
const input = createInput(geoP1, geoC, geoP2);
740+
expect(point.maxFilletRadius(input)).toBeCloseTo(0, precision);
741+
});
742+
743+
it("should return 0 if one segment has near-zero length (P2=C)", () => {
744+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
745+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
746+
const geoP2: Inputs.Base.Point3 = [0, defaultTolerance / 3, 0]; // Very close to C
747+
const input = createInput(geoP1, geoC, geoP2);
748+
expect(point.maxFilletRadius(input)).toBeCloseTo(0, precision);
749+
});
750+
751+
it("should use the provided tolerance if specified", () => {
752+
const customTolerance = 1e-4;
753+
const geoP1: Inputs.Base.Point3 = [customTolerance / 2, 0, 0]; // Near zero relative to custom tolerance
754+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
755+
const geoP2: Inputs.Base.Point3 = [0, 3, 0];
756+
const input = createInput(geoP1, geoC, geoP2, customTolerance);
757+
// Expect 0 because len1 < customTolerance
758+
expect(point.maxFilletRadius(input)).toBeCloseTo(0, precision);
759+
760+
const geoP1_ok: Inputs.Base.Point3 = [customTolerance * 2, 0, 0]; // OK relative to custom tolerance
761+
const input_ok = createInput(geoP1_ok, geoC, geoP2, customTolerance);
762+
// Expect non-zero result here
763+
expect(point.maxFilletRadius(input_ok)).toBeGreaterThan(0);
764+
});
765+
766+
it("should return 0 if all points coincide", () => {
767+
const geoP1: Inputs.Base.Point3 = [0, 0, 0];
768+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
769+
const geoP2: Inputs.Base.Point3 = [0, 0, 0];
770+
const input = createInput(geoP1, geoC, geoP2);
771+
expect(point.maxFilletRadius(input)).toBeCloseTo(0, precision);
772+
});
773+
774+
it("should calculate correct radius for a corner in 3D space", () => {
775+
const geoP1: Inputs.Base.Point3 = [1, 1, 0];
776+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
777+
const geoP2: Inputs.Base.Point3 = [0, 2, 0];
778+
const input = createInput(geoP1, geoC, geoP2);
779+
const expected = Math.sqrt(2) * Math.tan(Math.PI / 8);
780+
expect(point.maxFilletRadius(input)).toBeCloseTo(expected, precision);
781+
});
782+
});
783+
784+
describe("calculateMaxFilletRadiusHalfLine", () => {
785+
const defaultTolerance = 1e-7;
786+
const precision = 6;
671787

788+
const createInput = (p1: Inputs.Base.Point3, corner: Inputs.Base.Point3, p2: Inputs.Base.Point3, tolerance: number = defaultTolerance): Inputs.Point.ThreePointsToleranceDto => ({
789+
start: p1, center: p2, end: corner, tolerance: tolerance
790+
});
791+
792+
it("should calculate correct radius for a simple 90-degree corner (2D)", () => {
793+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
794+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
795+
const geoP2: Inputs.Base.Point3 = [0, 3, 0];
796+
const input = createInput(geoP1, geoC, geoP2);
797+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(1.5, precision);
798+
});
799+
800+
it("should calculate correct radius for a symmetric 90-degree corner (2D)", () => {
801+
const geoP1: Inputs.Base.Point3 = [4, 0, 0];
802+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
803+
const geoP2: Inputs.Base.Point3 = [0, 4, 0];
804+
const input = createInput(geoP1, geoC, geoP2);
805+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(2.0, precision);
806+
});
672807

808+
it("should calculate correct radius for an acute angle (60 degrees, 2D)", () => {
809+
const geoP1: Inputs.Base.Point3 = [6, 0, 0];
810+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
811+
const geoP2: Inputs.Base.Point3 = [4 * Math.cos(Math.PI / 3), 4 * Math.sin(Math.PI / 3), 0];
812+
const input = createInput(geoP1, geoC, geoP2);
813+
const expected = 2 * Math.tan(Math.PI / 6);
814+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(expected, precision);
815+
});
816+
817+
it("should calculate correct radius for an obtuse angle (120 degrees, 2D)", () => {
818+
const geoP1: Inputs.Base.Point3 = [4, 0, 0];
819+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
820+
const geoP2: Inputs.Base.Point3 = [6 * Math.cos(2 * Math.PI / 3), 6 * Math.sin(2 * Math.PI / 3), 0];
821+
const input = createInput(geoP1, geoC, geoP2);
822+
const expected = 2 * Math.tan(Math.PI / 3);
823+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(expected, precision);
824+
});
825+
826+
it("should return 0 for collinear points (0 degrees)", () => {
827+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
828+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
829+
const geoP2: Inputs.Base.Point3 = [10, 0, 0];
830+
const input = createInput(geoP1, geoC, geoP2);
831+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(0, precision);
832+
});
833+
834+
it("should return 0 if one segment has near-zero length", () => {
835+
const geoP1: Inputs.Base.Point3 = [5, 0, 0];
836+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
837+
const geoP2: Inputs.Base.Point3 = [0, defaultTolerance / 3, 0];
838+
const input = createInput(geoP1, geoC, geoP2);
839+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(0, precision);
840+
});
841+
842+
it("should calculate correct radius for a corner in 3D space", () => {
843+
const geoP1: Inputs.Base.Point3 = [1, 1, 0];
844+
const geoC: Inputs.Base.Point3 = [0, 0, 0];
845+
const geoP2: Inputs.Base.Point3 = [0, 2, 0];
846+
const input = createInput(geoP1, geoC, geoP2);
847+
const expected = (Math.sqrt(2) / 2.0) * Math.tan(Math.PI / 8);
848+
expect(point.maxFilletRadiusHalfLine(input)).toBeCloseTo(expected, precision);
849+
});
673850
});
674851
});

0 commit comments

Comments
 (0)