Skip to content

Commit

Permalink
Viewport (#12)
Browse files Browse the repository at this point in the history
* stub some plainer primitives

* fix imports

* simplify math and primitives

* remove tests

* stub graph

* format vector

* flesh out vw graph editor
  • Loading branch information
GaryB432 committed Mar 17, 2024
1 parent b29a646 commit e418d66
Show file tree
Hide file tree
Showing 9 changed files with 878 additions and 17 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module.exports = {
rules: {
"no-inner-declarations": "off",
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
},
},
],
Expand Down
17 changes: 17 additions & 0 deletions src/lib/vector/vector.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, it, expect, beforeEach } from "vitest";
import { Vector } from "./vector";

describe("vector test", () => {
let sut: Vector;
beforeEach(() => {
sut = new Vector(0, 0);
});
it("adds 1 + 2 to equal 3", () => {
const v23 = new Vector(2, 3);
expect(sut.add(v23).toString()).toEqual("(2,3)");
expect(sut.clone().add(new Vector(1, 1)).toString()).toEqual("(3,4)");
expect(sut.clone().sub(new Vector(2, 3)).toString()).toEqual("(0,0)");
sut.sub(v23);
expect(sut.toString()).toEqual("(0,0)");
});
});
53 changes: 53 additions & 0 deletions src/lib/vector/vector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export class Vector {
public constructor(public x: number, public y: number) {}
public add(other: Vector): this {
this.x += other.x;
this.y += other.y;
return this;
}
public clone(): Vector {
return new Vector(this.x, this.y);
}
public copy(other: Vector): this {
this.x = other.x;
this.y = other.y;
return this;
}
public divide(other: Vector): this {
this.x /= other.x;
this.y /= other.y;
return this;
}
public equals(other: Vector): boolean {
return this.x === other.x && this.y === other.y;
}
public length(): number {
return Math.sqrt(this.lengthSquared());
}
public lengthSquared(): number {
return this.x * this.x + this.y * this.y;
}
public mix(other: Vector, amount = 0.5): this {
const interp = (start: number, end: number): number => {
const tot = end - start;
const dx = tot * amount;
return start + dx;
};
this.x = interp(this.x, other.x);
this.y = interp(this.y, other.y);
return this;
}
public norm(): this {
const length = this.length();
this.divide(new Vector(length, length));
return this;
}
public sub(other: Vector): this {
this.x -= other.x;
this.y -= other.y;
return this;
}
public toString(): string {
return `(${this.x},${this.y})`;
}
}
174 changes: 171 additions & 3 deletions src/routes/virtual-world/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,181 @@
<script lang="ts">
import { onMount } from "svelte";
import type { PageData } from "./$types";
import { Graph, getNearestPoint } from "./world/math";
import { Point, Segment } from "./world/primitives";
import { Viewport } from "./world/viewport";
export let data: PageData;
let el: HTMLCanvasElement;
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D | null;
export class GraphEditor {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D | null;
selected: Point | null = null;
hovered: Point | null = null;
dragging = false;
mouse: Point | null = null;
constructor(public viewport: Viewport, public graph: Graph) {
this.canvas = viewport.canvas;
this.ctx = this.canvas.getContext("2d");
this.#addEventListeners();
}
#addEventListeners() {
this.canvas.addEventListener(
"mousedown",
this.#handleMouseDown.bind(this)
);
this.canvas.addEventListener(
"mousemove",
this.#handleMouseMove.bind(this)
);
this.canvas.addEventListener("contextmenu", (evt) =>
evt.preventDefault()
);
this.canvas.addEventListener("mouseup", () => (this.dragging = false));
}
#handleMouseMove(evt: MouseEvent) {
this.mouse = this.viewport.getMouse(evt, true);
if (!this.mouse) {
return;
}
this.hovered = getNearestPoint(
this.mouse,
this.graph.points,
10 * this.viewport.zoom
);
if (this.dragging == true) {
if (!this.selected) {
this.selected = new Point(0, 0);
}
this.selected.x = this.mouse.x;
this.selected.y = this.mouse.y;
}
}
#handleMouseDown(evt: MouseEvent) {
if (evt.button == 2) {
// right click
if (this.selected) {
this.selected = null;
} else if (this.hovered) {
this.#removePoint(this.hovered);
}
}
if (evt.button == 0) {
// left click
if (this.hovered) {
this.#select(this.hovered);
this.dragging = true;
return;
}
if (this.mouse) {
this.graph.addPoint(this.mouse);
}
this.#select(this.mouse);
this.hovered = this.mouse;
}
}
#select(point: Point | null) {
if (this.selected && point) {
this.graph.tryAddSegment(new Segment(this.selected, point));
}
this.selected = point;
}
#removePoint(point: Point) {
this.graph.removePoint(point);
this.hovered = null;
if (this.selected == point) {
this.selected = null;
}
}
dispose(): void {
this.graph.dispose();
this.selected = null;
this.hovered = null;
}
display(): void {
if (!this.ctx) return;
this.graph.draw(this.ctx);
if (this.hovered) {
this.hovered.draw(this.ctx, { fill: true });
}
if (this.selected) {
const intent = this.hovered ? this.hovered : this.mouse;
if (intent)
new Segment(this.selected, intent).draw(this.ctx, { dash: [3, 3] });
this.selected.draw(this.ctx, { outline: true });
}
}
}
onMount(() => {
console.log(data.bbox);
ctx = canvas.getContext("2d");
if (!ctx) {
return;
}
// offset = new Point(myCanvas.width / 2, myCanvas.height / 2);
// ctx.translate(offset.x, offset.y);
// const f = getComputedStyle(myCanvas);
// console.log(myCanvas.offsetTop);
canvas.width = 600;
canvas.height = 600;
// const ctx = myCanvas.getContext("2d");
// const graphString = localStorage.getItem("graph");
// const graphInfo = graphString ? JSON.parse(graphString) : null;
const graph = new Graph(data.points, data.segments);
// const world = new World(graph);
const viewport = new Viewport(canvas);
const graphEditor = new GraphEditor(viewport, graph);
let oldGraphHash = graph.hash();
animate();
function animate() {
viewport.reset();
// if (graph.hash() != oldGraphHash) {
// world.generate();
// oldGraphHash = graph.hash();
// }
// const viewPoint = scale(viewport.getOffset(), -1);
// if (ctx) {
// world.draw(ctx, viewPoint);
// ctx.globalAlpha = 0.3;
// }
graphEditor.display();
requestAnimationFrame(animate);
}
function dispose() {
graphEditor.dispose();
}
function save() {
localStorage.setItem("graph", JSON.stringify(graph));
}
// console.log(f.top);
// document.onmousemove = (event) => {
// point.x = event.x - offset.x - myCanvas.offsetLeft;
// point.y = event.y - offset.y - myCanvas.offsetTop;
// update();
// };
});
</script>

Expand All @@ -16,7 +184,7 @@
</svelte:head>

<article class="container">
<canvas bind:this={el} />
<canvas bind:this={canvas} />
</article>

<style>
Expand Down
27 changes: 13 additions & 14 deletions src/routes/virtual-world/+page.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import type { PageLoad, RouteParams } from "./$types";
import type { BBox, MultiPolygon, Position } from "geojson";
import type { PageLoad } from "./$types";
import { Point, Segment } from "./world/primitives";

export const load = (async ({ params, fetch }) => {
const resp = await fetch("/districts/states/MO/shape.geojson");
let coordinates: Position[][][] = [];
let bbox: BBox | undefined;
if (resp.statusText === "OK") {
const d = (await resp.json()) as MultiPolygon;
if (d.type === "MultiPolygon") {
coordinates = d.coordinates;
bbox = d.bbox;
}
}
const points: Point[] = [
new Point(10, 10),
new Point(20, 20),
new Point(30, 20),
];
const segments: Segment[] = [
new Segment(points[0], points[1]),
new Segment(points[1], points[2]),
];

return {
coordinates,
bbox,
points,
segments,
};
}) satisfies PageLoad;
Loading

0 comments on commit e418d66

Please sign in to comment.