diff options
| -rw-r--r-- | notes.md | 62 | ||||
| -rw-r--r-- | src/Geometry/Geometry.js | 5 | ||||
| -rw-r--r-- | src/Map/Canvas.bak.js | 122 | ||||
| -rw-r--r-- | src/Map/Canvas.js | 132 | ||||
| -rw-r--r-- | src/Map/Map.js | 1 | ||||
| -rw-r--r-- | src/Map/Node.js | 15 | ||||
| -rw-r--r-- | src/Map/Shapes.js | 113 | ||||
| -rw-r--r-- | src/Map/World.js | 14 | ||||
| -rw-r--r-- | src/Util/Util.js | 11 | ||||
| -rw-r--r-- | src/index.html | 4 | ||||
| -rw-r--r-- | src/main.js | 26 | 
11 files changed, 483 insertions, 22 deletions
| @@ -69,25 +69,55 @@ and those kinds of nodes should be on their own layers, so that the user can vie  ignore relevant data at any time. -nodes ------ +points +------ + +Points are objects with the following properties **only**: + +	- x: the x-coordinate in meters +	- y: the y-coordinate in meters + + +shapes +------ + +A shape is an object containing the following properties **only**: + +	- type: a string, either 'point', 'path', or 'polygon' +	- points: an array of `points`, or, if type is 'point', a single point object. +	- boundingBox: AABB that contains every point in `points` -node types: -	- point -	- path -	- polygon -all nodes can be selected to view properties in the right pane. They can then be edited by clicking a button on the pane. +mapObject +--------- + +mapObjects are objects with the following properties: + +	- core: a point +	- shapes: an array of `shape` objects +	- mapObjects: an array of child mapObjects +	- boundingBox: AABB that contains the bounding boxes of every shape in shapes +	- minZoom: a number from 0 to 6 (see maxZoom) +	- maxZoom: a number from 0 to 7: +		0. global +		1. regional +		2. national +		3. provincial +		4. city +		5. neighborhood +		6. building + + +tiles +----- + +A tile is an object with the following properties: -All nodes can be moved when editing. Path and polygon nodes can have their constituent points moved and can insert new nodes. +	- region: an AABB bounding the tile area +	- mapObjects: an array of references to the mapObjects that intersect this square -Nodes have an associated "relevance level" +worlds +------ -	- building (smallest) -	- neighborhood -	- city -	- province -	- nation -	- global (largest) +a world is an object with the following properties: -depending on a map's scale, some of these may not be relevant (i.e. a map only showing a city would not use higher relevance levels). diff --git a/src/Geometry/Geometry.js b/src/Geometry/Geometry.js index 6c296ad..110d764 100644 --- a/src/Geometry/Geometry.js +++ b/src/Geometry/Geometry.js @@ -152,6 +152,11 @@ class QTNode {  						}  						this.subnode = undefined;  					} +					/* no need to check if we have more than 1 leaf or 0 leaves -- +					 * for more than 1 leaf we stay as a branch (so no need to do anything) +					 * and 0 leaves cannot happen, because this started as a branch (meaning 2+ leaves +					 * or at least one child branch) and we only removed 1 point from the tree. +					 */  					break;  			}  		} diff --git a/src/Map/Canvas.bak.js b/src/Map/Canvas.bak.js new file mode 100644 index 0000000..efc6c00 --- /dev/null +++ b/src/Map/Canvas.bak.js @@ -0,0 +1,122 @@ +import h from '../Util/DomUtil.js'; +import { dist, AABB, QuadTree } from '../Geometry/Geometry.js'; + +class Canvas { +	constructor(parentId) { +		const parentElement= document.getElementById(parentId); + +		this.canvas = h('canvas', parentElement.width, parentElement.height); +		this.context = this.canvas.getContext('2d'); + +		/* state */ +		this.movingPoint = false; +		this.selectedPoint = null; + +		/* callbacks */ +		this.onMovePoint = (original, now) => console.log(original, now); + +		/* transform */ +		this.scale = 1; +		this.pan = { x: 0, y: 0 }; + +		/* mouse */ +		this.mouse = { x: 0, y: 0 }; + +		/* retrieving points */ +		this.points = []; +		this.tree = new QuadTree(this.canvas.width, this.canvas.height); + +		/* event listeners */ +		this.canvas.addEventListener('mousemove', e => { +			this.mouse.x = e.offsetX; +			this.mouse.y = e.offsetY; +			if (this.movingPoint) this.render(); +		}); + +		this.canvas.addEventListener('click', e => { +			if (this.movingPoint) { +				this.dropPoint(); +			} +			else { +				const pt = this.getClickedPoint( +					{ x: e.offsetX, y: e.offsetY }, 10 +				); +				if (pt) this.grabPoint(pt); +			} +		}); + +		/* attach to parent */ +		parentElement.appendChild(this.canvas); +	} + +	/* transform a point to internal coordinates */ +	transform(x, y) { +		return [ +			(this.scale * x) - this.pan.x, +			(this.scale * y) - this.pan.y, +		]; +	} + +	/* data updates */ +	updatePoints(points) { +		this.points = []; +		this.tree = new QuadTree(this.canvas.width, this.canvas.height); +		for (let pt of points) this.insertPoint(pt); +		this.render(); +	} + +	insertPoint(pt) { +		this.tree.insert(pt); +		this.points.push(pt); +	} + +	/* drawing */ +	drawPoint(pt) { +		const ct = this.context; +		ct.save(); +		ct.beginPath(); +		const [x, y] = this.transform(pt.x, pt.y); +		console.log(x,y); +		ct.arc(x, y, 10/this.scale, 0, 2*Math.PI); +		ct.closePath(); +		ct.fill(); +		ct.restore(); +	} + +	render() { +		this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); +		for (let pt of this.points) this.drawPoint(pt); +		if (this.movingPoint) { +			const [x, y] = this.transform(this.mouse.x, this.mouse.y); +			this.drawPoint({x, y}); +		} +	} + +	/* handling input */ +	grabPoint(pt) { +		this.tree.root.remove(pt); +		this.points = this.points.filter(point => (point.x !== pt.x || point.y !== pt.y)); +		console.log(this.points); +		this.selectedPoint = pt; +		this.movingPoint = true; +		this.render(); +	} + +	dropPoint() { +		const [x, y] = this.transform(this.mouse.x, this.mouse.y); +		this.onMovePoint(this.selectedPoint, {x, y}); +		this.movingPoint = false; +		this.selectedPoint = null; +	} +	 +	getClickedPoint(clickLocation, maxDistance) { +		const [ x, y ] = this.transform(clickLocation.x, clickLocation.y); +		const clickPoint = { x, y }; +		const closest = this.tree.closest(clickPoint); +		if (!closest) return null; +		if (dist(closest, clickPoint) < maxDistance) return closest; +		return null; +	} +} + +export default Canvas; diff --git a/src/Map/Canvas.js b/src/Map/Canvas.js new file mode 100644 index 0000000..cbd6e9e --- /dev/null +++ b/src/Map/Canvas.js @@ -0,0 +1,132 @@ +import h from '../Util/DomUtil.js'; +import { dist, AABB, QuadTree } from '../Geometry/Geometry.js'; +import { Shape } from './Shapes.js'; + + +class Transform { +	constructor() { +		this.scale = 1; +		this.pan = { +			x: 0, +			y: 0, +		}; +	} + +	screenToDrawing(x, y) { +		return [ +			(this.scale * x) - this.pan.x, +			(this.scale * y) - this.pan.y, +		]; +	} + +	drawingToScreen(x, y) { +		return [ +			(x + this.pan.x) / this.scale, +			(y + this.pan.y) / this.scale, +		]; +	} +} + + +class Mouse { +	constructor(canvas) { +		this.position = { x: 0, y: 0 }; + +		this.onMouseMove = null; +		this.onClick = null; + +		canvas.addEventListener('mousemove', e => { +			this.position.x = e.offsetX; +			this.position.y = e.offsetY; +			if (this.onMouseMove) this.onMouseMove(); +		}); + +		canvas.addEventListener('click', e => { +			if (this.onClick) this.onClick(); +		}); +	} + +	getPosition(transform) { +		const { x, y } = this.position; +		return transform.screenToDrawing(x, y); +	} +} + + +class Canvas { +	constructor(parentId) { +		const parentElement = document.getElementById(parentId); +		this.canvas = h('canvas',  +			{ +				width: parentElement.clientWidth,  +				height: parentElement.clientHeight +			} +		); +		this.context = this.canvas.getContext('2d'); + +		parentElement.appendChild(this.canvas); +	} + +	/* ~~~~~~~~~~~~~~~~ drawing ~~~~~~~~~~~~~~~~ */ + +	render(transform, shapes) { +		this.context.save(); +		this.context.setTransform(1, 0, 0, 1, 0, 0); +		this.context.scale(transform.scale, transform.scale); +		this.context.translate(transform.pan.x, transform.pan.y); + +		for (let shape of shapes) { +			switch(shape.type) { +				case Shape.Point: +					this.drawPoint(shape); +					break; + +				case Shape.Path: +					this.drawPath(shape); +					break; + +				case Shape.Polygon: +					this.drawPolygon(shape); +					break; + +				default: +					break; +			} +		} +		this.context.restore(); +	} + +	drawPoint(shape) { +		const ct = this.context; +		ct.beginPath(); +		ct.arc(shape.nodes[0].x, shape.nodes[0].y, 2, 0, 2*Math.PI); +		ct.closePath(); +		ct.fill(); +	} + +	drawPath(shape) { +		const ct = this.context; +		ct.beginPath(); +		ct.moveTo(shape.nodes[0].x, shape.nodes[0].y); +		for(let point of shape.nodes.slice(1)) { +			ct.lineTo(point.x, point.y); +		} +		ct.stroke(); +		ct.closePath(); +	} + +	drawPolygon(shape) { +		const ct = this.context; +		ct.beginPath(); +		ct.moveTo(shape.nodes[0].x, shape.nodes[0].y); +		for (let point of shape.nodes.slice(1)) { +			ct.lineTo(point.x, point.y); +		} +		ct.closePath(); +		ct.stroke(); +		ct.fill(); +	} +} + + +export { Transform, Mouse, Canvas }; diff --git a/src/Map/Map.js b/src/Map/Map.js deleted file mode 100644 index 8b13789..0000000 --- a/src/Map/Map.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Map/Node.js b/src/Map/Node.js new file mode 100644 index 0000000..12308ca --- /dev/null +++ b/src/Map/Node.js @@ -0,0 +1,15 @@ +class PointNode { +	constructor(x, y, attributes) { +		this.setPosition(x, y); +		this.attributes = attributes; +	} + +	setPosition(x, y) { +		this.x = x; +		this.y = y; +	} + +	setAttribute(key, value) { +		this.attributes[key] = value; +	} +} diff --git a/src/Map/Shapes.js b/src/Map/Shapes.js new file mode 100644 index 0000000..3443f73 --- /dev/null +++ b/src/Map/Shapes.js @@ -0,0 +1,113 @@ +import { Enum } from '../Util/Util.js'; +import { dist, AABB } from '../Geometry/Geometry.js'; + + +class Node { +	constructor(x, y) { +		this.moveTo(x, y); +	} + +	moveTo(x, y) { +		this.x = x; +		this.y = y; +	} +} + + +class ShapeType extends Enum { +	constructor(name) { super('Shape', name); } +} + + +class Shape { +	static Point = new ShapeType('Point'); +	static Path = new ShapeType('Path'); +	static Polygon = new ShapeType('Polygon'); + +	constructor(type, nodes) { +		this.type = type; +		this.nodes = nodes; + +		this.boundingBox = this.computeBoundingBox(); +	} + +	computeBoundingBox() { +		let xMin = Infinity; +		let xMax = -Infinity; +		let yMin = Infinity; +		let yMax = -Infinity; + +		for (let node of this.nodes) { +			if (node.x < xMin) xMin = node.x; +			if (node.x > xMax) xMax = node.x; + +			if (node.y < yMin) yMin = node.y; +			if (node.y > yMax) yMax = node.y; +		} + +		return new AABB(xMin, yMin, xMax-xMin, yMax-yMin); +	} +} + +class Point extends Shape { +	constructor(node) { +		super(Shape.Point, [node]); +	} +} + +class Path extends Shape { +	constructor(nodes) { +		super(Shape.Path, nodes); +	} +} + +class Polygon extends Shape { +	constructor(nodes) { +		super(Shape.Polygon, nodes); +	} +} + + +class ZoomLevel extends Enum { +	static Globe = new ZoomLevel('Globe'); +	static Nation = new ZoomLevel('Nation'); +	static Province = new ZoomLevel('Province'); +	static City = new ZoomLevel('City'); +	static Neighborhoot = new ZoomLevel('Neighborhood'); +	static Building = new ZoomLevel('Building'); + +	constructor(name) { super('ZoomLevel', name); } +} + +class MapObject { +	constructor(core, layerId, minZoom, maxZoom=minZoom) { +		this.core = core; +		this.layerId = layerId; +		this.minZoom = minZoom; +		this.maxZoom = maxZoom; + +		this.shapes = []; +		this.boundingBox = this.computeBoundingBox(); +	} + +	computeBoundingBox() { +		let xMin = Infinity; +		let xMax = -Infinity; +		let yMin = Infinity; +		let yMax = -Infinity; + +		for (let shape of this.shapes) { +			const box = shape.boundingBox; +			if (box.x < xMin) xMin = box.x; +			if (box.x + box.width > xMax) xMax = box.x + box.width; + +			if (box.y < yMin) yMin = box.y; +			if (box.y + box.height > yMax) yMax = box.y + box.height; +		} + +		return new AABB(xMin, yMin, xMax-xMin, yMax-yMin); +	} +} + + +export { Node, Shape, Point, Path, Polygon, ZoomLevel, MapObject }; diff --git a/src/Map/World.js b/src/Map/World.js new file mode 100644 index 0000000..b9db4e9 --- /dev/null +++ b/src/Map/World.js @@ -0,0 +1,14 @@ +import { Shape, ZoomLevel } from './Shape.js'; + + +class Tile { + +} + + +class World { + +} + + +export default World; diff --git a/src/Util/Util.js b/src/Util/Util.js index 165d1d0..88ed59f 100644 --- a/src/Util/Util.js +++ b/src/Util/Util.js @@ -17,4 +17,13 @@ function clamp(value, min, max) {  function lerp(a, b, alpha) {  	return ((1-alpha)*a) + (alpha*b);  } -export { useAverage, clamp, lerp }; + +class Enum { +	constructor(prefix, name) {  +		this.prefix = prefix; +		this.name = name; +	} +	toString() { return `${this.prefix}.${this.name}`; } +} + +export { useAverage, clamp, lerp, Enum }; diff --git a/src/index.html b/src/index.html index 55e00b1..9ff8fd3 100644 --- a/src/index.html +++ b/src/index.html @@ -11,6 +11,10 @@  				background-color: #222;  				color: white;  			} +			#root { +				width: 500px; +				height: 500px; +			}  			button {  				font-family: monospace;  			} diff --git a/src/main.js b/src/main.js index 0474d7a..5c6db59 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,23 @@ -import { LayerData, LayerView, LayerController } from './Map/Layer.js'; +import { Transform, Canvas } from './Map/Canvas.js'; +import { Node, Shape, Point, Path, Polygon } from './Map/Shapes.js'; -const view = new LayerView('root'); -const data = new LayerData(); -const con = new LayerController(data, view); +function point(x, y) { this.x = x; this.y = y; }; + +const canvas = new Canvas('root'); +const transform = new Transform(); + +const shapes = [ +	new Point(new Node(30, 30)), +	new Path([ +		new Node(100, 100), +		new Node(100, 200), +		new Node(150, 250), +	]), +	new Polygon([ +		new Node(200, 100), +		new Node(200, 200), +		new Node(300, 200), +	]), +]; + +canvas.render(transform, shapes); | 
