Skip to content

Commit

Permalink
Merge pull request #38 from ProbableTrain/UpdateDocs
Browse files Browse the repository at this point in the history
Improve comments and documentation
  • Loading branch information
ProbableTrain authored May 2, 2020
2 parents 46d0b96 + 261b9cb commit 1a314cd
Show file tree
Hide file tree
Showing 25 changed files with 428 additions and 176 deletions.
271 changes: 209 additions & 62 deletions dist/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<body style="background-color:grey;">
<svg id="map-svg"></svg>
<div>
<canvas id="map-canvas"></canvas>
<canvas id="map-canvas"></canvas> <!-- Must match util.ts and style.css -->
<canvas id="img-canvas"></canvas>
</div>
<script src="bundle.js"></script>
Expand Down
1 change: 1 addition & 0 deletions src/html/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ html, body {
overflow: hidden;
}

/* Must match util.ts and index.html */
#map-canvas {
position:fixed;
left:0;
Expand Down
115 changes: 68 additions & 47 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as dat from 'dat.gui';
import TensorFieldGUI from './ts/ui/tensor_field_gui';
import {NoiseParams} from './ts/impl/tensor_field';
import MainGUI from './ts/ui/main_gui';
import CanvasWrapper from './ts/ui/canvas_wrapper';
import {DefaultCanvasWrapper, RoughCanvasWrapper} from './ts/ui/canvas_wrapper';
import {DefaultCanvasWrapper} from './ts/ui/canvas_wrapper';
import Util from './ts/util';
import DragController from './ts/ui/drag_controller';
import DomainController from './ts/ui/domain_controller';
Expand All @@ -17,57 +16,67 @@ import ModelGenerator from './ts/model_generator';
import { saveAs } from 'file-saver';

class Main {
private domainController = DomainController.getInstance();
private gui: dat.GUI = new dat.GUI({width: 300});
private tensorField: TensorFieldGUI;
private mainGui: MainGUI;
private dragController = new DragController(this.gui);
private readonly STARTING_WIDTH = 1440; // Initially zooms in if width > STARTING_WIDTH

// Options
private imageScale = 3;

// Folders
// UI
private gui: dat.GUI = new dat.GUI({width: 300});
private tensorFolder: dat.GUI;
private roadsFolder: dat.GUI;
private styleFolder: dat.GUI;
private optionsFolder: dat.GUI;
private downloadsFolder: dat.GUI;

// To force draw if needed
private previousFrameDrawTensor = true;
private domainController = DomainController.getInstance();
private dragController = new DragController(this.gui);
private tensorField: TensorFieldGUI;
private mainGui: MainGUI; // In charge of glueing everything together

// Options
private imageScale = 3; // Multiplier for res of downloaded image
public highDPI = false; // Increases resolution for hiDPI displays

// Style options
private canvas: HTMLCanvasElement;
private tensorCanvas: DefaultCanvasWrapper;
private _style: Style;
private styleFolder: dat.GUI;
private colourScheme: string = "Default";
private zoomBuildings: boolean = false;
private buildingModels: boolean = false;
private colourScheme: string = "Default"; // See colour_schemes.json
private zoomBuildings: boolean = false; // Show buildings only when zoomed in?
private buildingModels: boolean = false; // Draw pseudo-3D buildings?
private showFrame: boolean = false;
public highDPI = false;

// 3D settings
// Force redraw of roads when switching from tensor vis to map vis
private previousFrameDrawTensor = true;

// 3D camera position
private cameraX = 0;
private cameraY = 0;

private readonly STARTING_WIDTH = 1440;
private firstGenerate = true;

private firstGenerate = true; // Don't randomise tensor field on first generate
private modelGenerator: ModelGenerator;

constructor() {
// Canvas setup
this.canvas = document.getElementById(Util.CANVAS_ID) as HTMLCanvasElement;
this.tensorCanvas = new DefaultCanvasWrapper(this.canvas);
// GUI Setup
const zoomController = this.gui.add(this.domainController, 'zoom');
this.domainController.setZoomUpdate(() => zoomController.updateDisplay());
this.gui.add(this, 'generate');

this.tensorFolder = this.gui.addFolder('Tensor Field');
this.roadsFolder = this.gui.addFolder('Map');
this.styleFolder = this.gui.addFolder('Style');
this.optionsFolder = this.gui.addFolder('Options');
this.downloadsFolder = this.gui.addFolder('Download');

// Canvas setup
this.canvas = document.getElementById(Util.CANVAS_ID) as HTMLCanvasElement;
this.tensorCanvas = new DefaultCanvasWrapper(this.canvas);

// Make sure we're not too zoomed out for large resolutions
const screenWidth = this.domainController.screenDimensions.x;
if (screenWidth > this.STARTING_WIDTH) {
this.domainController.zoom = screenWidth / this.STARTING_WIDTH;
}

// GUI Setup
this.gui.add(this, 'generate');
this.styleFolder = this.gui.addFolder('Style');
// Style setup
this.styleFolder.add(this, 'colourScheme', Object.keys(ColourSchemes)).onChange((val: string) => this.changeColourScheme(val));

this.styleFolder.add(this, 'zoomBuildings').onChange((val: boolean) => {
Expand All @@ -91,37 +100,36 @@ class Main {
this.styleFolder.add(this, 'cameraX', -15, 15).step(1).onChange(() => this.setCameraDirection());
this.styleFolder.add(this, 'cameraY', -15, 15).step(1).onChange(() => this.setCameraDirection());

const noiseParams: NoiseParams = {

const noiseParamsPlaceholder: NoiseParams = { // Placeholder values for park + water noise
globalNoise: false,
noiseSizePark: 20,
noiseAnglePark: 90,
noiseSizeGlobal: 30,
noiseAngleGlobal: 20
};

this.tensorFolder = this.gui.addFolder('Tensor Field');
this.tensorField = new TensorFieldGUI(this.tensorFolder, this.dragController, true, noiseParams);
this.roadsFolder = this.gui.addFolder('Map');
this.tensorField = new TensorFieldGUI(this.tensorFolder, this.dragController, true, noiseParamsPlaceholder);
this.mainGui = new MainGUI(this.roadsFolder, this.tensorField, () => this.tensorFolder.close());

const optionsFolder = this.gui.addFolder('Options');
optionsFolder.add(this.tensorField, 'drawCentre');
const canvasScaleController = optionsFolder.add(this, 'highDPI');
canvasScaleController.onChange((high: boolean) => this.changeCanvasScale(high));

const downloadsFolder = this.gui.addFolder('Download');
downloadsFolder.add(this, 'imageScale', 1, 5).step(1);
downloadsFolder.add({"PNG": () => this.downloadPng()}, 'PNG');
downloadsFolder.add({"SVG": () => this.downloadSVG()}, 'SVG');
downloadsFolder.add({"STL": () => this.downloadSTL()}, 'STL');
downloadsFolder.add({"Heightmap": () => this.downloadHeightmap()}, 'Heightmap');
this.optionsFolder.add(this.tensorField, 'drawCentre');
this.optionsFolder.add(this, 'highDPI').onChange((high: boolean) => this.changeCanvasScale(high));

this.downloadsFolder.add(this, 'imageScale', 1, 5).step(1);
this.downloadsFolder.add({"PNG": () => this.downloadPng()}, 'PNG'); // This allows custom naming of button
this.downloadsFolder.add({"SVG": () => this.downloadSVG()}, 'SVG');
this.downloadsFolder.add({"STL": () => this.downloadSTL()}, 'STL');
this.downloadsFolder.add({"Heightmap": () => this.downloadHeightmap()}, 'Heightmap');

this.changeColourScheme(this.colourScheme);
this.tensorField.setRecommended();
requestAnimationFrame(this.update.bind(this));
requestAnimationFrame(() => this.update());
}

generate() {
/**
* Generate an entire map with no control over the process
*/
generate(): void {
if (!this.firstGenerate) {
this.tensorField.setRecommended();
} else {
Expand All @@ -131,7 +139,10 @@ class Main {
this.mainGui.generateEverything();
}

changeColourScheme(scheme: string) {
/**
* @param {string} scheme Matches a scheme name in colour_schemes.json
*/
changeColourScheme(scheme: string): void {
const colourScheme: ColourScheme = (ColourSchemes as any)[scheme];
this.zoomBuildings = colourScheme.zoomBuildings;
this.buildingModels = colourScheme.buildingModels;
Expand All @@ -145,17 +156,23 @@ class Main {
this.changeCanvasScale(this.highDPI);
}

/**
* Scale up canvas resolution for hiDPI displays
*/
changeCanvasScale(high: boolean): void {
const value = high ? 2 : 1;
this._style.canvasScale = value;
this.tensorCanvas.canvasScale = value;
}

/**
* Change camera position for pseudo3D buildings
*/
setCameraDirection(): void {
this.domainController.cameraDirection = new Vector(this.cameraX / 10, this.cameraY / 10);
}

downloadSTL(zip=true): void {
downloadSTL(): void {
// All in screen space
const extendScreenX = this.domainController.screenDimensions.x * ((Util.DRAW_INFLATE_AMOUNT - 1) / 2);
const extendScreenY = this.domainController.screenDimensions.y * ((Util.DRAW_INFLATE_AMOUNT - 1) / 2);
Expand Down Expand Up @@ -207,6 +224,9 @@ class Main {
link.click();
}

/**
* Same as downloadPng but uses Heightmap style
*/
downloadHeightmap(): void {
const oldColourScheme = this.colourScheme;
this.changeColourScheme("Heightmap");
Expand Down Expand Up @@ -298,6 +318,7 @@ class Main {
}
}

// Add log to window so we can use log.setlevel from the console
(window as any).log = log;
window.addEventListener('load', (): void => {
new Main();
Expand Down
3 changes: 3 additions & 0 deletions src/ts/impl/basis_field.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Tensor from './tensor';
import Vector from '../vector';

/**
* Grid or Radial field to be combined with others to create the tensor field
*/
export abstract class BasisField {
abstract readonly FOLDER_NAME: string;
protected static folderNameIndex: number = 0;
Expand Down
13 changes: 13 additions & 0 deletions src/ts/impl/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ interface Intersection {
segments: Segment[];
}

/**
* Node located along any intersection or point along the simplified road polylines
*/
export class Node {
public segments = new Set<Segment>();
public adj: Node[];
Expand All @@ -43,6 +46,10 @@ export default class Graph {
public nodes: Node[];
public intersections: Vector[];

/**
* Create a graph from a set of streamlines
* Finds all intersections, and creates a list of Nodes
*/
constructor(streamlines: Vector[][], dstep: number, deleteDangling=false) {
const intersections = isect.bush(this.streamlinesToSegment(streamlines)).run();
const quadtree = (d3.quadtree() as d3.Quadtree<Node>).x(n => n.value.x).y(n => n.value.y);
Expand Down Expand Up @@ -100,6 +107,9 @@ export default class Graph {
for (const i of intersections) this.intersections.push(new Vector(i.point.x, i.point.y));
}

/**
* Remove dangling edges from graph to facilitate polygon finding
*/
private deleteDanglingNodes(n: Node, quadtree: d3.Quadtree<Node>) {
if (n.neighbors.size === 1) {
quadtree.remove(n);
Expand All @@ -110,6 +120,9 @@ export default class Graph {
}
}

/**
* Given a segment, step along segment and find all nodes along it
*/
private getNodesAlongSegment(segment: Segment, quadtree: d3.Quadtree<Node>, radius: number, step: number): Node[] {
// Walk dstep along each streamline, adding nodes within dstep/2
// and connected to this streamline (fuzzy - nodeAddRadius) to list, removing from
Expand Down
4 changes: 4 additions & 0 deletions src/ts/impl/grid_storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import * as log from 'loglevel';
import Vector from '../vector';

/**
* Cartesian grid accelerated data structure
* Grid of cells, each containing a list of vectors
*/
export default class GridStorage {

private gridDimensions: Vector;
Expand Down
2 changes: 1 addition & 1 deletion src/ts/impl/integrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default abstract class FieldIntegrator {
return tensor.getMinor();
}

onLand(point: Vector) {
onLand(point: Vector): boolean {
return this.field.onLand(point);
}
}
Expand Down
25 changes: 13 additions & 12 deletions src/ts/impl/polygon_finder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export interface PolygonParams {
chanceNoDivide: number;
}

/**
* Finds polygons in a graph, used for finding lots and parks
*/
export default class PolygonFinder {
private _polygons: Vector[][] = [];
private _shrunkPolygons: Vector[][] = [];
Expand All @@ -34,7 +37,7 @@ export default class PolygonFinder {
return this._polygons;
}

reset() {
reset(): void {
this.toShrink = [];
this.toDivide = [];
this._polygons = [];
Expand All @@ -45,7 +48,7 @@ export default class PolygonFinder {
update(): boolean {
let change = false;
if (this.toShrink.length > 0) {
let resolve = this.toShrink.length === 1;
const resolve = this.toShrink.length === 1;
if (this.stepShrink(this.toShrink.pop())) {
change = true;
}
Expand All @@ -54,21 +57,19 @@ export default class PolygonFinder {
}

if (this.toDivide.length > 0) {
let resolve = this.toDivide.length === 1;
const resolve = this.toDivide.length === 1;
if (this.stepDivide(this.toDivide.pop())) {
change = true;
}
// const divided = PolygonUtil.subdividePolygon(this.toDivide.pop(), this.params.minArea);

// if (divided.length > 0) {
// this._dividedPolygons.push(...divided);
// change = true;
// }
if (resolve) this.resolveDivide();
}
return change;
}

/**
* Properly shrink polygon so the edges are all the same distance from the road
*/
async shrink(animate=false): Promise<void> {
return new Promise<void>(resolve => {
if (this._polygons.length === 0) {
Expand Down Expand Up @@ -98,7 +99,7 @@ export default class PolygonFinder {
if (shrunk.length > 0) {
this._shrunkPolygons.push(shrunk)
return true;
};
}
return false;
}

Expand Down Expand Up @@ -159,9 +160,9 @@ export default class PolygonFinder {
this._dividedPolygons = [];
const polygons = [];

for (let node of this.nodes) {
for (const node of this.nodes) {
if (node.adj.length < 2) continue;
for (let nextNode of node.adj) {
for (const nextNode of node.adj) {
const polygon = this.recursiveWalk([node, nextNode]);
if (polygon !== null && polygon.length < this.params.maxLength) {
this.removePolygonAdjacencies(polygon);
Expand Down Expand Up @@ -223,7 +224,7 @@ export default class PolygonFinder {
let rightmostNode = null;
let smallestTheta = Math.PI * 2;

for (let nextNode of nodeTo.adj) {
for (const nextNode of nodeTo.adj) {
if (nextNode !== nodeFrom) {
const nextVector = nextNode.value.clone().sub(nodeTo.value);
let nextAngle = Math.atan2(nextVector.y, nextVector.x) - transformAngle;
Expand Down
Loading

0 comments on commit 1a314cd

Please sign in to comment.