Skip to content

Commit

Permalink
feat: add first draft of pseudo hilbert curve
Browse files Browse the repository at this point in the history
  • Loading branch information
vighnesh153 committed Nov 21, 2023
1 parent f27b78b commit ffce964
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts">
import { onMount } from 'svelte';
import { PseudoHilbertCurveGame, CanvasWrapperImpl } from '@vighnesh153/graphics-programming';
let canvasElement: HTMLCanvasElement;
let game: PseudoHilbertCurveGame;
onMount(() => {
const canvasWrapper = new CanvasWrapperImpl(canvasElement);
game = new PseudoHilbertCurveGame(canvasWrapper);
const frames = game.start();
function showNextFrame() {
if (!frames.next().done) {
requestAnimationFrame(showNextFrame);
}
}
showNextFrame();
});
</script>

<canvas class="mt-6 mx-auto w-full max-w-3xl aspect-video bg-text" bind:this={canvasElement}>
Sorry your browser doesn't support the canvas element
</canvas>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
import { graphicsProjectsMap } from '@vighnesh153/graphics-programming';
import { classes, verifyGraphicsProjectPath } from '@/utils';
import { projectNavItems } from '@/constants';
import ContentLayout from '@/layouts/ContentLayout.astro';
import ProjectRoot from '@/components/projects/graphics/pseudo-hilbert-curve/ProjectRoot.svelte';
const project = graphicsProjectsMap.pseudoHilbertCurve;
verifyGraphicsProjectPath(project, Astro.request.url);
const title = `Vighnesh Raut | Graphics Projects - Pseudo Hilbert Curve`;
const description = `A Hilbert curve is a continuous fractal space-filling curve`;
---

<ContentLayout title={title} description={description} navItems={projectNavItems} showFooter={false}>
<div class={classes(`mt-28 mb-12 max-w-xl mx-auto lg:max-w-[unset] scroll-mt-8`)}>
<h1 class="text-3xl mb-6 text-center">Pseudo Hilbert Curve</h1>
<ProjectRoot client:load />
</div>
</ContentLayout>
1 change: 1 addition & 0 deletions nodejs-tools/nodejs-lib/graphics-programming/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './bonding-particles';
export * from './grid-path-finder';
export * from './pseudo-hilbert-curve';
export * from './sierpinskis-triangle';
export * from './symmetric-binary-tree';
export * from './tower-of-hanoi';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { CanvasWrapper } from '@/canvas-wrapper';
import { generateCurve } from './generateCurve';
import { Point } from './point';
import { Line } from './line';

export class PseudoHilbertCurveGame {
#canvasWrapper: CanvasWrapper;

constructor(canvasWrapper: CanvasWrapper) {
this.#canvasWrapper = canvasWrapper;
}

*start() {
for (const level of [3]) {
const p1 = new Point(100, 100);
const p2 = new Point(100, 200);
const p3 = new Point(200, 200);
const p4 = new Point(200, 100);
const curves = generateCurve(p1, p2, p3, p4, level);

this.drawLines(curves);
}

yield;
}

private drawLines(lines: Line[]): void {
for (const line of lines) {
this.#canvasWrapper.drawLine(line.point1.x, line.point1.y, line.point2.x, line.point2.y, 2, 'black');
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { range } from '@vighnesh153/utils';

export function euclidianDistance(point1: number[], point2: number[]): number {
if (point1.length !== point2.length) {
throw new Error(`Point dimensions are not the same. Dimension1=${point1.length}, Dimension2=${point2.length}`);
}
const squared = Array.from(range(0, point1.length - 1))
.map((index) => {
const diff = Math.abs(point1[index] - point2[index]);
return diff * diff;
})
.reduce((prev, curr) => prev + curr, 0);
return Math.sqrt(squared);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { generateTwoMidPoints } from './generateTwoMidpoints';
import { getDistantCorner } from './getDistantCorner';
import { Line } from './line';
import { Point } from './point';

/**
*
* c1---------a b---------c4
* | |
* p1 | | p4
* | | | |
* c----------i j---------e
* | | | |
* | | | |
* | | | |
* d | k----------l | f
* | | | | | |
* | p2----|----------|----p3 |
* | | | |
* c2---------g h---------c3
*
*/
export function generateCurve(p1: Point, p2: Point, p3: Point, p4: Point, level: number): Line[] {
if (level < 1) {
throw new Error('Level cannot be less than 1');
}
if (level === 1) return [new Line(p1, p2), new Line(p2, p3), new Line(p3, p4)];

// corners
const c1 = getDistantCorner(p1, p2, p4);
const c2 = getDistantCorner(p2, p3, p1);
const c3 = getDistantCorner(p3, p4, p2);
const c4 = getDistantCorner(p4, p1, p3);

// return [new Line(c1, c2), new Line(c2, c3), new Line(c3, c4)];

// mid points
const { midPoint1: c, midPoint2: d } = generateTwoMidPoints(c1, c2);
const { midPoint1: g, midPoint2: h } = generateTwoMidPoints(c2, c3);
const { midPoint1: f, midPoint2: e } = generateTwoMidPoints(c3, c4);
const { midPoint1: b, midPoint2: a } = generateTwoMidPoints(c4, c1);
const { midPoint1: i, midPoint2: k } = generateTwoMidPoints(a, g);
const { midPoint1: j, midPoint2: l } = generateTwoMidPoints(b, h);

// curve near p1
const p1Curve = generateCurve(c, i, a, c1, level - 1);
// curve near p2
const p2Curve = generateCurve(d, c2, g, k, level - 1);
// curve near p3
const p3Curve = generateCurve(l, h, c3, f, level - 1);
// curve near p3
const p4Curve = generateCurve(c4, b, j, e, level - 1);

return [
...p1Curve.toReversed(),
new Line(p1Curve[0].point1, p2Curve[0].point1),
...p2Curve,
new Line(p2Curve[p2Curve.length - 1].point2, p3Curve[0].point1),
...p3Curve,
new Line(p3Curve[p3Curve.length - 1].point2, p4Curve[p3Curve.length - 1].point2),
...p4Curve.toReversed(),
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Point } from './point';

interface TwoMidpoints {
midPoint1: Point;
midPoint2: Point;
}

export function generateTwoMidPoints(startPoint: Point, endPoint: Point): TwoMidpoints {
const isXDifferent = startPoint.x !== endPoint.x;

// one of distX or distY will be 0
const distX = Math.abs(startPoint.x - endPoint.x);
const distY = Math.abs(startPoint.y - endPoint.y);
const dist = (isXDifferent ? distX : distY) / 3;

const start = isXDifferent ? startPoint.x : startPoint.y;
const end = isXDifferent ? endPoint.x : endPoint.y;
const sign = start < end ? 1 : -1;

const midPoint1 = Math.round(start + sign * dist);
const midPoint2 = Math.round(start + sign * dist * 2);

if (isXDifferent) {
return {
midPoint1: new Point(midPoint1, startPoint.y),
midPoint2: new Point(midPoint2, startPoint.y),
};
}
return {
midPoint1: new Point(startPoint.x, midPoint1),
midPoint2: new Point(startPoint.x, midPoint2),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { euclidianDistance } from './euclidian-distance';
import { Point } from './point';

// repel -> as in repel like a magnet
export function getDistantCorner(point: Point, repelPoint1: Point, repelPoint2: Point): Point {
const distance = euclidianDistance([point.x, point.y], [repelPoint1.x, repelPoint1.y]) / 4;

const potentialPoints = [
new Point(point.x + distance, point.y + distance),
new Point(point.x + distance, point.y - distance),
new Point(point.x - distance, point.y + distance),
new Point(point.x - distance, point.y - distance),
];

const farthestPointsFromRepelPoint1 = findFarthestPoints(repelPoint1, potentialPoints);
const farthestPointsFromRepelPoint2 = findFarthestPoints(repelPoint2, potentialPoints);

const intersection = findIntersectionPoints(farthestPointsFromRepelPoint1, farthestPointsFromRepelPoint2);

if (intersection.length !== 1) {
throw new Error('intersection length should be 1');
}

return intersection[0];
}

function findFarthestPoints(pivotPoint: Point, points: Point[]): Point[] {
let farthestPoints: Point[] = [];
let maxDistance = 0;
for (const point of points) {
const dist = euclidianDistance([point.x, point.y], [pivotPoint.x, pivotPoint.y]);
if (dist > maxDistance) {
maxDistance = dist;
farthestPoints = [point];
} else if (dist === maxDistance) {
farthestPoints.push(point);
}
}
return farthestPoints;
}

function findIntersectionPoints(list1: Point[], list2: Point[]): Point[] {
const common: Point[] = [];
for (const point of list1) {
if (list2.includes(point)) {
common.push(point);
}
}
return common;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PseudoHilbertCurveGame } from './Game';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Point } from './point';

export class Line {
readonly point1: Point;
readonly point2: Point;

constructor(point1: Point, point2: Point) {
this.point1 = point1;
this.point2 = point2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class Point {
readonly x: number;
readonly y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}

clone(): Point {
return new Point(this.x, this.y);
}
}

0 comments on commit ffce964

Please sign in to comment.