drawing shapes on a canvas when the user clicks

This commit is contained in:
Gavin McDonald
2016-02-17 10:32:14 -05:00
commit f7cb7c094a
18 changed files with 1665 additions and 0 deletions

123
src/hex.js Normal file
View File

@@ -0,0 +1,123 @@
import Point from './point.js';
function computeY(x, z) {
return -x - z;
}
// convert real numbers to integers:
// round off coords
// throw out whichever one changed the most
// re-establish "x + y + z = 0"
function roundOff(x, y, z) {
let rX = Math.round(x);
let rY = Math.round(y);
let rZ = Math.round(z);
let xDiff = Math.abs(rX - x);
let yDiff = Math.abs(rY - y);
let zDiff = Math.abs(rZ - z);
if ((xDiff > yDiff) && (xDiff > zDiff)) {
rX = -rY-rZ;
}
else if (yDiff > zDiff) {
rY = -rX-rZ;
}
else {
rZ = -rX-rY;
}
x = rX === -0 ? 0 : rX;
y = rY === -0 ? 0 : rY;
z = rZ === -0 ? 0 : rZ;
return {x, y, z};
}
export default class Hex extends Point {
constructor() {
super();
if (arguments.length === 2) { // hex = Hex(q, r);
this.x = arguments[0];
this.z = arguments[1];
this.y = computeY(this.x, this.z);
}
else if (arguments.length === 3) { // hex = Hex(x, y, z);
this.x = arguments[0];
this.y = arguments[1];
this.z = arguments[2];
}
roundOff(this.x, this.y, this.z);
}
return {
getX: function() {return this.x;},
getY: function() {return this.y;},
getZ: function() {return this.z;},
setX: function(newX) {this.x = newX; return this;},
setY: function(newY) {this.y = newY; return this;},
setZ: function(newZ) {this.z = newZ; return this;},
moveX: function(byX) {this.x += byX; return this;},
moveY: function(byY) {this.y += byY; return this;},
moveZ: function(byZ) {this.z += byZ; return this;},
getQ: function() {return this.x;},
getR: function() {return this.z;},
setQ: function(newQ) {
this.x = newQ;
this.y = computeY(this.x, this.z);
return this;
},
setR: function(newR) {
this.z = newR;
this.y = computeY(this.x, this.z);
return this;
},
moveQ: function(byQ) {
this.x += byQ;
this.y = computeY(this.x, this.z);
return this;
},
moveR: function(byR) {
this.z += byR;
this.y = computeY(this.x, this.z);
return this;
},
getHex: function() { return {x: this.x, y: this.y, z: this.z}; },
setHex: function(newHex) {
this.x = newHex.x;
this.y = newHex.y;
this.z = newHex.z;
return this;
},
moveHex: function(byHex) {
this.x += byHex.x;
this.y += byHex.y;
this.z += byHex.z;
return this;
},
getAxial: function() {return {x: this.x, z: this.z};},
setAxial: function(newAxial) {
this.x = newAxial.q;
this.z = newAxial.r;
this.y = computeY(this.x, this.y);
return this;
},
moveAxial: function(byAxial) {
this.x += byAxial.q;
this.z += byAxial.r;
this.y = computeY(this.x, this.z);
return this;
}
};
};

7
src/main.js Normal file
View File

@@ -0,0 +1,7 @@
import Tessellate from './tessellate.js';
var tessellate = new Tessellate({
element: '#container'
});

24
src/onTap.js Normal file
View File

@@ -0,0 +1,24 @@
export default class OnTap {
constructor(settings) {
this.element = settings.element || document.body;
this.callbacks = {};
this.events = ['click'];
this.events.map(method => {
this[method] = this[method].bind(this);
if (typeof settings[method] === 'function') {
this.element.addEventListener(method, this[method]);
this.callbacks[method] = settings[method];
}
});
}
click(event) {
this.callbacks.click(event);
}
}

15
src/point.js Normal file
View File

@@ -0,0 +1,15 @@
export default class Point {
constructor(newX, newY) {
this.x = newX;
this.y = newY;
}
getX() { return this.x; }
getY() { return this.y; }
setX(newX) { this.x = newX; }
setY(newY) { this.y = newY; }
getPoint() { return {x: this.x, y: this.y}; }
};

37
src/sketch.js Normal file
View File

@@ -0,0 +1,37 @@
export default class Sketch {
constructor(settings) {
this.lastTime = null;
['getContext', 'onResize', 'render'].map(method => this[method] = this[method].bind(this));
this.draw = settings.draw || function() {}; // () => {};
this.container = settings.element || document.body;
window.addEventListener('optimizedResize', this.onResize);
this.canvas = document.createElement('canvas');
this.canvas.width = this.container.offsetWidth;
this.canvas.height = this.container.offsetHeight;
this.context = this.canvas.getContext('2d');
this.container.appendChild(this.canvas);
requestAnimationFrame(this.render);
}
getContext() { return this.context; }
onResize(event) {
console.log('sketch - onResize', arguments);
}
render(now) {
this.draw(this.context, now, this.lastTime);
this.lastTime = now;
requestAnimationFrame(this.render);
}
}

62
src/tessellate.js Normal file
View File

@@ -0,0 +1,62 @@
import {random} from './utils.js';
import OnTap from './onTap.js';
import Point from './point.js';
import Sketch from './sketch.js';
import TileCircle from './tileCircle.js';
import TileDiamond from './tileDiamond.js';
import TileHex from './tileHex.js';
import TileSquare from './tileSquare.js';
export default class Tessellate {
constructor(settings) {
['draw'].map(method => {this[method] = this[method].bind(this)});
this.element = document.querySelector(settings.element);
this.sketch = new Sketch({
element: this.element,
draw: this.draw
});
this.map = [];
this.onTap = new OnTap({
element: this.element,
click: (event) => {
let x = event.pageX;
let y = event.pageY;
let tiles = ['circle', 'diamond', 'hex', 'square'];
let styles = ['filled', 'outline'];
let scale = random(10, 50);
this.map.push({
tile: tiles[random(tiles.length-1)],
style: styles[random(styles.length-1)],
x,
y,
scale,
pointy: random(1) ? true : false,
color: `rgba(${ random(255) }, ${ random(255) }, ${ random(255) }, 0.5)`
});
}
});
this.circle = new TileCircle({context: this.sketch.getContext()});
this.diamond = new TileDiamond({context: this.sketch.getContext()});
this.hex = new TileHex({context: this.sketch.getContext()});
this.square = new TileSquare({context: this.sketch.getContext()});
}
draw(context, now, lastTime) {
let canvas = context.canvas;
let width = canvas.width;
let height = canvas.height;
context.clearRect(0, 0, width, height);
this.map.forEach(cell => this[cell.tile][cell.style](cell));
}
}

11
src/tile.js Normal file
View File

@@ -0,0 +1,11 @@
export default class Tile {
constructor(settings) {
this.context = settings.context || null;
}
setContext(newContext) {
this.context = newContext || this.context;
}
}

26
src/tileCircle.js Normal file
View File

@@ -0,0 +1,26 @@
import Tile from './tile.js';
let RGBA_DEFAULT = 'rgba(0, 0, 0, 0.5)';
export default class TileCircle extends Tile {
constructor(settings) {
super(settings);
}
filled(settings) {
this.context.beginPath();
this.context.arc(settings.x, settings.y, settings.scale, 0, 2*Math.PI, false);
this.context.fillStyle = settings.color || RGBA_DEFAULT;
this.context.fill();
}
outline(settings) {
this.context.beginPath();
this.context.arc(settings.x, settings.y, settings.scale, 0, 2*Math.PI, false);
this.context.lineWidth = settings.width ? settings.width : 1;
this.context.strokeStyle = settings.color || RGBA_DEFAULT;
this.context.stroke();
}
}

45
src/tileDiamond.js Normal file
View File

@@ -0,0 +1,45 @@
import Tile from './tile.js';
import {hypotenuse} from './utils.js';
let RGBA_DEFAULT = 'rgba(0, 0, 0, 0.5)';
export default class TileDiamond extends Tile {
constructor(settings) {
super(settings);
}
filled(settings) {
let x = settings.x;
let y = settings.y;
let scale = settings.scale;
this.context.beginPath();
this.context.moveTo(x+hypotenuse(scale), y);
this.context.lineTo(x, y-hypotenuse(scale));
this.context.lineTo(x-hypotenuse(scale), y);
this.context.lineTo(x, y+hypotenuse(scale));
this.context.fillStyle = settings.color || RGBA_DEFAULT;
this.context.fill();
}
outline(settings) {
let x = settings.x;
let y = settings.y;
let scale = settings.scale;
this.context.beginPath();
this.context.moveTo(x+hypotenuse(scale), y);
this.context.lineTo(x, y-hypotenuse(scale));
this.context.lineTo(x-hypotenuse(scale), y);
this.context.lineTo(x, y+hypotenuse(scale));
this.context.closePath();
this.context.lineWidth = settings.width ? settings.width : 1;
this.context.strokeStyle = settings.color || RGBA_DEFAULT;
this.context.stroke();
}
}

97
src/tileHex.js Normal file
View File

@@ -0,0 +1,97 @@
import Tile from './tile.js';
let RGBA_DEFAULT = 'rgba(0, 0, 0, 0.5)';
export default class TileHex extends Tile {
constructor(settings) {
super(settings);
this.sqrt3 = Math.sqrt(3);
this.hexSides = 6;
this.pointyTopCornerX = [];
this.pointyTopCornerY = [];
this.flatTopCornerX = [];
this.flatTopCornerY = [];
// dice dots, 0,0 is center
// TODO: if pointy: [0].concat(flatTop), if flat: [0].concat(pointy)
// this.hexDotX = [0];
// this.hexDotY = [0];
this.hexSlices = 24;
this.hexSliceX = [0];
this.hexSliceY = [0];
for (let hexSlice = 0; hexSlice < this.hexSlices; hexSlice++) {
this.hexSliceX[hexSlice] = parseInt(Math.cos(((hexSlice/this.hexSlices)*this.hexSides) * (2 * Math.PI) / this.hexSides) * 1000) / 1000;
this.hexSliceY[hexSlice] = parseInt(Math.sin(((hexSlice/this.hexSlices)*this.hexSides) * (2 * Math.PI) / this.hexSides) * 1000) / 1000;
if (((hexSlice-2) % 4) === 0) {
let cur = (hexSlice-2) / 4;
this.pointyTopCornerX[cur] = this.hexSliceX[hexSlice];
this.pointyTopCornerY[cur] = this.hexSliceY[hexSlice];
}
if ((hexSlice % 4) === 0) {
let cur = hexSlice / 4;
this.flatTopCornerX[cur] = this.hexSliceX[hexSlice];
this.flatTopCornerY[cur] = this.hexSliceY[hexSlice];
}
// if (((hexSlice-2) % 4) === 0) {
// let cur = (hexSlice-2) / 4;
// this.hexCornerX[cur] = this.hexSliceX[hexSlice];
// this.hexCornerY[cur] = this.hexSliceY[hexSlice];
// }
//
// if ((hexSlice % 4) === 0) {
// let cur = hexSlice / 4;
// this.hexDotX[cur+1] = this.hexSliceX[hexSlice];
// this.hexDotY[cur+1] = this.hexSliceY[hexSlice];
// }
}
}
outline(settings) {
let x = settings.x;
let y = settings.y;
let scale = settings.scale;
let hexCornerX = settings.pointy ? this.pointyTopCornerX : this.flatTopCornerX;
let hexCornerY = settings.pointy ? this.pointyTopCornerY : this.flatTopCornerY;
this.context.beginPath();
this.context.moveTo(x + scale * hexCornerX[0], y + scale * hexCornerY[0]);
this.context.lineTo(x + scale * hexCornerX[1], y + scale * hexCornerY[1]);
this.context.lineTo(x + scale * hexCornerX[2], y + scale * hexCornerY[2]);
this.context.lineTo(x + scale * hexCornerX[3], y + scale * hexCornerY[3]);
this.context.lineTo(x + scale * hexCornerX[4], y + scale * hexCornerY[4]);
this.context.lineTo(x + scale * hexCornerX[5], y + scale * hexCornerY[5]);
this.context.closePath();
this.context.lineWidth = settings.width ? settings.width : 1;
this.context.strokeStyle = settings.color || RGBA_DEFAULT;
this.context.stroke();
}
filled(settings) {
let x = settings.x;
let y = settings.y;
let scale = settings.scale;
let hexCornerX = settings.pointy ? this.pointyTopCornerX : this.flatTopCornerX;
let hexCornerY = settings.pointy ? this.pointyTopCornerY : this.flatTopCornerY;
this.context.beginPath();
this.context.moveTo(x + scale * hexCornerX[0], y + scale * hexCornerY[0]);
this.context.lineTo(x + scale * hexCornerX[1], y + scale * hexCornerY[1]);
this.context.lineTo(x + scale * hexCornerX[2], y + scale * hexCornerY[2]);
this.context.lineTo(x + scale * hexCornerX[3], y + scale * hexCornerY[3]);
this.context.lineTo(x + scale * hexCornerX[4], y + scale * hexCornerY[4]);
this.context.lineTo(x + scale * hexCornerX[5], y + scale * hexCornerY[5]);
this.context.fillStyle = settings.color || RGBA_DEFAULT;
this.context.fill();
}
}

43
src/tileSquare.js Normal file
View File

@@ -0,0 +1,43 @@
import Tile from './tile.js';
let RGBA_DEFAULT = 'rgba(0, 0, 0, 0.5)';
export default class TileSquare extends Tile {
constructor(settings) {
super(settings);
}
filled(settings) {
let x = settings.x;
let y = settings.y;
let scale = settings.scale;
this.context.beginPath();
this.context.moveTo(x+scale, y+scale);
this.context.lineTo(x+scale, y-scale);
this.context.lineTo(x-scale, y-scale);
this.context.lineTo(x-scale, y+scale);
this.context.fillStyle = settings.color || RGBA_DEFAULT;
this.context.fill();
}
outline(settings) {
let x = settings.x;
let y = settings.y;
let scale = settings.scale;
this.context.beginPath();
this.context.moveTo(x+scale, y+scale);
this.context.lineTo(x+scale, y-scale);
this.context.lineTo(x-scale, y-scale);
this.context.lineTo(x-scale, y+scale);
this.context.closePath();
this.context.lineWidth = settings.width ? settings.width : 1;
this.context.strokeStyle = settings.color || RGBA_DEFAULT;
this.context.stroke();
}
}

48
src/utils.js Normal file
View File

@@ -0,0 +1,48 @@
export function throttleEvent(type, name, obj) {
obj = obj || window;
let running = false;
let throttle = () => {
if (!running) {
running = true;
requestAnimationFrame(() => {
obj.dispatchEvent(new CustomEvent(name));
running = false;
});
}
}
obj.addEventListener(type, throttle);
}
throttleEvent('resize', 'optimizedResize');
export function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
export function extend(obj, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) obj[key] = src[key];
}
return obj;
}
export function hypotenuse(a, b) {
if (b == null) b = a;
return Math.sqrt(a*a + b*b);
}
export function random(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
}