drawing shapes on a canvas when the user clicks
This commit is contained in:
123
src/hex.js
Normal file
123
src/hex.js
Normal 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
7
src/main.js
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
import Tessellate from './tessellate.js';
|
||||
|
||||
var tessellate = new Tessellate({
|
||||
element: '#container'
|
||||
});
|
||||
|
||||
24
src/onTap.js
Normal file
24
src/onTap.js
Normal 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
15
src/point.js
Normal 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
37
src/sketch.js
Normal 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
62
src/tessellate.js
Normal 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
11
src/tile.js
Normal 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
26
src/tileCircle.js
Normal 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
45
src/tileDiamond.js
Normal 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
97
src/tileHex.js
Normal 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
43
src/tileSquare.js
Normal 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
48
src/utils.js
Normal 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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user