Im not sure how the value is being unpacked in:
for (let {y,x, value} of matrix) {
console.log(x, y, value);
}
how does it know to pull it from the next().value.value of the Matrix.prototype[Symbol.iterator]?
class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
}
}
}
get(x, y) {
return this.content[y * this.width + x];
}
set(x, y, value) {
this.content[y * this.width + x] = value;
}
}
let obj = new Matrix(2, 2, (x, y) => `value ${x},${y}`)
class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return {done: true};
let value = {x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y)};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return {value, done: false};
}
}
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
for (let {y,x, value} of matrix) {
console.log(x, y, value);
}
// → 0 0 value 0,0
// → 1 0 value 1,0
// → 0 1 value 0,1
// → 1 1 value 1,1
Because for (let obj of matrix) gives you the object you're building in this line let value = {x: this.x, y: this.y, value: this.matrix.get(this.x, this.y)};, into every loop iteration, then the destructuring syntax {y, x, value} extracts every field.
Related
I'm kinda confused of how this statement works this.content[y * width + x] = element(x, y));
class Matrix {
constructor(width, height, element = (x, y) => undefined) { this.width = width; this.height = height; this.content = [];
//console.log(this.content);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
console.log(this.content[y * width + x] = element(x, y));
}
}
}
get(x, y) { return this.content[y * this.width + x]; } set(x, y, value) { this.content[y * this.width + x] = value; } }
class MatrixIterator { constructor(matrix) { this.x = 0; this.y = 0; this.matrix = matrix; } next() { if (this.y == this.matrix.height) return {done: true}; let value = {x: this.x, y: this.y, value: this.matrix.get(this.x, this.y)};
this.x++; if (this.x == this.matrix.width) { this.x = 0; this.y++; } return {value, done: false}; } }
Matrix.prototype[Symbol.iterator] = function() { return new MatrixIterator(this); };
//We can now loop over a matrix with for/of.
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
console.log(matrix);
for (let {x, y, value} of matrix) { console.log(x, y, value);
}
// → 0 0 value 0,0
// → 1 0 value 1,0
// → 0 1 value 0,1
// → 1 1 value 1,1
To identify each element of a matrix you need to use an index obviously , here the writer of the book you where you found this, chose this approach to do it :
y * this.width + x
This allows you to access a determined element of your Matrix while identifient all elements and guarantees that nothing will overlap .
Alternatively you can use :
[y + '_' + x] this does exactly the same purpus of y * this.width + x ,
but converting to string and concatenation uses loops for something that could be done by simply 2 operations ! y * this.width and result + x .
Here is a rewrite of your code with my way of indexing :
class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = {};//you need this for string keys !
//console.log(this.content);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y +'_'+ x] = element(x, y);
console.log((this.content[y +'_'+ x] = element(x, y)));
}
}
}
get(x, y) {
return this.content[y +'_'+ x];
}
set(x, y, value) {
this.content[y +'_'+ x] = value;
}
}
class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return { done: true };
let value = {
x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y),
};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return { value, done: false };
}
}
//We can now loop over a matrix with for/of.
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
console.log('Matrix formed is : ',matrix);
I am working on a circle packing algorithm in p5.js and vue 3. For some reason it is adding an item to an array that it shouldn't be able to add. I've written some collisions with other objects, and while the program is running it works fine, but when I initialise an object it doesn't seem to care about them. The collisions always return false on initialisation, and even before updating the object again, it says it does collide, but only after adding it to my array. Which is the thing I don't want to be happening, since this clutters my array.
My code:
export function circlePacking(element, imageToShow) {
return function (p) {
const p5Canvas = element;
let circles = [];
let image;
p.preload = function () {
image = p.loadImage(imageToShow);
};
p.setup = function () {
p.colorMode('RGB');
p.disableFriendlyErrors = true;
p.frameRate(60);
this.averageColor = averageColor(p, image);
const canvas = p.createCanvas(p5Canvas.offsetWidth - 1, p5Canvas.offsetHeight - 1, 'P2D');
canvas.parent(p5Canvas);
image.resize(p.width, p.height);
image.loadPixels();
};
p.draw = function () {
if (p5Canvas.offsetWidth - 1 !== p.width || p5Canvas.offsetHeight - 1 !== p.height) {
p.resizeCanvas(p5Canvas.offsetWidth - 1, p5Canvas.offsetHeight - 1);
}
p.background(this.averageColor);
for (let i = 0; i < 5; i++) {
let x = p.round(getRandom(0, image.width));
let y = p.round(getRandom(0, image.height));
let location = (x + y * image.width) * 4;
let color = p.color(image.pixels[location], image.pixels[location + 1], image.pixels[location + 2])
let c = new Circle(x, y, color);
console.log(c);
//here collidesWithCircle is false, even though it should be true
if (!c.collidesWithCircle() && !c.collidesWithWall()) {
circles.push(c);
}
}
circles.forEach(circle => {
circle.draw();
})
circles.forEach(circle => {
circle.checkCollision();
})
};
class Circle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.size = 1;
this.growing = true;
this.collidesWall = this.collidesWithWall();
this.collidesCircle = this.collidesWithCircle();
}
draw() {
p.stroke(0);
p.strokeWeight(1);
p.fill(this.color);
p.circle(this.x, this.y, this.size)
if (this.growing) {
this.grow()
}
}
grow() {
this.size += 0.3;
}
checkCollision() {
if (this.growing) {
this.collidesWithCircle();
this.collidesWithWall();
}
}
collidesWithCircle() {
circles.forEach(circle => {
if (p.dist(this.x, this.y, circle.x, circle.y) < (this.size / 2) + (circle.size / 2) && this !== circle) {
this.growing = false;
return true;
}
});
return false;
}
collidesWithWall() {
if (this.x + this.size / 2 > p.width || this.x - this.size / 2 < 0 || this.y + this.size / 2 > p.height || this.y - this.size / 2 < 0) {
this.growing = false;
return true;
}
return false;
}
}
};
}
I am trying to put a word search game on my web page.
I see here is a beautiful example: Embed a Word Search in Your Site
I want to make similar gui, so that when the user selects the word letters from the grid by dragging mouse, it will draw a red elastic rubber band around the letters selected.
How do they do it on their web page? I tried to "inspect" the html after the band is drawn, however did not see any element added.
Can somebody give me any clue?
Here is a JS example
class App {
constructor(div) {
this.div = div;
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext('2d');
this.dirty = true;
this.prev = +new Date();
this.resize();
window.addEventListener('resize', () => this.resize(), false);
this.canvas.addEventListener("mousedown", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mousemove", (event) => {
this.touchmove(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mouseup", (event) => {
this.touchup(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchstart", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchmove", (event) => {
this.touchmove(App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchend", (event) => {
this.touchup(App.getmousePos(event));
event.preventDefault();
});
}
resize() {
this.canvas.width = this.div.clientWidth;
this.canvas.height = this.div.clientWidth;
this.draw();
}
loop() {
let now = +new Date();
let dt = now - this.prev;
this.prev = now;
this.update()
if (this.dirty)
this.draw();
window.requestAnimationFrame(() => this.loop());
}
update(dt) {}
draw() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.ctx.rect(0, 0, 100, 100);
this.ctx.fill();
}
touchdown(x, y) { console.log("down", x, y); }
touchmove(x, y) {}
touchup(x, y) {}
static getmousePos(event) {
if (event.changedTouches) {
return [event.changedTouches[0].pageX, event.changedTouches[0].pageY];
}
else {
var rect = event.target.getBoundingClientRect();
return [event.clientX- rect.left, event.clientY - rect.top];
}
}
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(v_x, y) {
if (y != undefined) {
return new Vector(this.x + v_x, this.y + y);
}
else {
return new Vector(this.x + v_x.x, this.y + v_x.y);
}
}
sub(v_x, y) {
if (y != undefined) {
return new Vector(this.x - v_x, this.y - y);
}
else {
return new Vector(this.x - v_x.x, this.y - v_x.y);
}
}
mul(s) {
return new Vector(this.x * s, this.y * s);
}
get slength() {
return this.x * this.x + this.y * this.y;
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
get angle() {
return Math.atan2(this.y, this.x);
}
}
class Grid {
constructor(width, height, data) {
this.width = width;
this.height = height;
this.data = data || new Array(width*height).fill("_");
}
get length() {
return this.width*this.height;
}
get(x_pos, y) {
if (y != undefined)
return this.data[x_pos+y*this.width];
else
return this.data[x_pos];
}
set(value, x_pos, y) {
if (y != undefined)
this.data[x_pos+y*this.width] = value;
else
this.data[x_pos] = value;
}
clone() {
return new Grid(this.width, this.height, this.data.slice());
}
finalize() {
this.data = this.data.map(v => v == "_" ? String.fromCharCode(Math.floor(Math.random() * 26) + 97) : v);
}
print() {
for (let i = 0; i < this.height; i++)
console.log(this.data.slice(i*this.width, (i+1)*this.width).join(","));
}
}
class Puzzle {
constructor(width, height, words, directions) {
this.grid = new Grid(width, height);
this.words = words;
this.placeWords(words, directions);
}
placeWords(words, directions) {
const range = (N) => Array.from({length: N}, (v, k) => k);
const shuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
words = words.slice();
let positions = range(this.grid.length);
let stack = [ {
grid: this.grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice()) } ];
while (true) {
let current = stack[stack.length-1];
if (!current)
throw Error("impossible");
let dir = current.directions.pop();
if (!dir) {
current.positions.pop();
current.directions = shuffle(directions.slice());
dir = current.directions.pop();
}
if (current.positions.length <= 0) {
words.unshift(current.word);
stack.pop();
}
else {
let pos = current.positions[current.positions.length-1];
let grid = this.placeWord(current.grid, current.word, pos, dir);
if (grid) {
if (words.length > 0) {
stack.push({grid: grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice())});
}
else {
grid.finalize();
this.grid = grid;
break;
}
}
}
}
}
placeWord(grid, word, position, direction) {
let copy = grid.clone();
position = new Vector(position % grid.width, Math.floor(position / grid.width));
let letters = [...word];
while (0 <= position.x && position.x < grid.width && 0 <= position.y && position.y < grid.height) {
if (letters.length <= 0)
break;
let letter = letters.shift();
if (copy.get(position.x, position.y) == "_" || copy.get(position.x, position.y) == letter) {
copy.set(letter, position.x, position.y);
position = position.add(direction);
}
else {
return null;
}
}
if (letters.length > 0)
return null;
else
return copy;
}
wordAt(position, direction, length) {
let word = new Array(length);
for (let i = 0; i < length; i++) {
word[i] = this.grid.get(position.x, position.y);
position = position.add(direction);
}
return word.join("");
}
print() {
this.grid.print();
}
}
class Selection {
constructor(position, fill=false) {
this.position = position;
this._fill = fill;
this.direction = new Vector(1, 0);
this.length = 0;
this.flength = 0;
}
clone() {
return new Selection(this.position).to(this.position.add(this.direction.mul(this.length)));
}
to(position) {
let direction = position.sub(this.position);
if (Math.abs(direction.y) == 0) {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, 0);
this.length = direction.x * this.direction.x;
}
else if (Math.abs(direction.x) == 0) {
this.direction = new Vector(0, direction.y >= 0 ? 1 : -1);
this.length = direction.y * this.direction.y;
}
else {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, direction.y >= 0 ? 1 : -1);
this.length = direction.x * this.direction.x;
}
this.flength = direction.length;
return this;
}
fill(fill=true) {
this._fill = fill;
return this;
}
draw(ctx, wsize, hsize) {
let pos = this.position.mul(wsize);
let length = wsize * (this.flength+1);
let angle = this.direction.angle;
//console.log(this.x, this.y, x, y, length, angle, this.dx, this.dy);
ctx.save();
ctx.strokeStyle = "black";
ctx.fillStyle = "yellow";
ctx.translate(pos.x+hsize*0.5, pos.y+hsize*0.5);
ctx.rotate(angle);
this.drawHighlight(ctx, -hsize*0.5, -hsize*0.5, length, hsize, hsize*0.5, this._fill, true);
ctx.restore();
}
drawHighlight(ctx, x, y, width, height, radius=20, fill=true, stroke=false) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
}
class PuzzleApp extends App {
constructor(div, puzzle) {
super(div);
this.puzzle = puzzle;
this.renderList(document.getElementById("list"));
this.selections = new Array();
this.loop();
}
renderList(parent) {
this.puzzle.words.forEach(word => {
let li = document.createElement("li");
let text = document.createTextNode(word);
li.appendChild(text);
parent.appendChild(li);
});
}
gridSize() {
let wsize = Math.floor(this.canvas.width/this.puzzle.grid.width);
let hsize = Math.floor(this.canvas.width/this.puzzle.grid.height);
return [wsize, hsize];
}
clientToGrid(x, y) {
let [wsize, hsize] = this.gridSize();
x = Math.floor(x / wsize);
y = Math.floor(y / hsize);
return [x, y];
}
draw() {
if (!this.puzzle)
return;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
let [wsize, hsize] = this.gridSize();
this.selections.forEach(s => s.draw(this.ctx, wsize, hsize));
if (this.selection)
this.selection.draw(this.ctx, wsize, hsize);
let x = 0;
let y = 0;
this.ctx.fillStyle = "black";
this.ctx.font = (wsize * 0.5) + 'px sans-serif';
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
for (let j = 0; j < this.puzzle.grid.height; j++) {
for (let i = 0; i < this.puzzle.grid.width; i++) {
let letter = this.puzzle.grid.get(i, j);
this.ctx.fillText(letter, x+wsize * 0.5, y+wsize * 0.5);
x += wsize;
}
x = 0;
y += wsize;
}
}
touchdown(x, y) {
[x, y] = this.clientToGrid(x, y);
this.selection = new Selection(new Vector(x, y));
this.dirty = true;
}
touchmove(x, y) {
if (!this.selection)
return;
[x, y] = this.clientToGrid(x, y);
this.selection.to(new Vector(x, y));
}
touchup(x, y) {
if (!this.selection)
return;
let word = this.puzzle.wordAt(this.selection.position, this.selection.direction, this.selection.length+1);
console.log(word);
if (word) {
let list = document.getElementById("list");
let elements = list.getElementsByTagName("li");
Array.prototype.some.call(elements, li => {
if (li.innerText == word) {
li.classList.add("found");
this.selections.push(this.selection.clone().fill());
return true;
}
return false;
});
}
this.selection = null;
}
}
let app = null;
document.addEventListener("DOMContentLoaded", function(event) {
const wordLists = [
["pear", "apple", "banana", "peach", "kiwi", "prune", "persimon"]
]
const pick = (array) => array[Math.floor(Math.random() * (array.length))];
let params = (new URL(document.location)).searchParams;
let directions = [new Vector(1,0), new Vector(0,1)];
if (params.get("diagonal")) {
directions.push(new Vector(1,1));
}
if (params.get("backwards")) {
directions.push(new Vector(-1,0));
directions.push(new Vector(0,-1));
if (params.get("diagonal")) {
directions.push(new Vector(-1,-1));
}
}
let puzzle = new Puzzle(8, 8, pick(wordLists), directions);
puzzle.print();
app = new PuzzleApp(document.getElementById("canvasContainer"), puzzle);
});
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
ul {
display: inline-block;
font-size: 7vmin;
padding-left: 14vmin;
}
li.found {
text-decoration: line-through;
}
div#canvasContainer {
width: 100%;
display: inline-block;
}
#media (min-aspect-ratio: 1/1) {
ul {
font-size: 4vmin;
padding-left: 7vmin;
}
div#canvasContainer {
width: 50%;
}
}
<div id="canvasContainer"><canvas id="canvas" width="320" height="320"></canvas></div>
<ul id="list"></ul>
I'll let you figure out how to change the thickness and color of the rubber band.
The red band is an svg. It doesn't get added when you click, but rather it gets redrawn.
I have inspected the referenced website and it seems like they draw rubber band thing in SVG. If you inspect this SVG tag while drawing:
you will notice it shows some updates.
You can use same SVG methodology. Or Another way is to use an absolute positioned div and update top, left css values based on mousedown, width based on mousemove event.
I have seen "what" they have done.
They have created an svg element. This svg sits exactly above and covers the entire table that contains the grid with letters. When the user clicks any letter and starts drag, they add a rectangle to the svg. Depending on how the cursor will move they do the angle transform to the rectangle. If the word is selected correctly, the rectangle survives; else, it is removed.
I need to find out now, "how" part of it.
Thanks to all for a very quick response.
I am having a bit of difficulty understanding a few lines of code from page 108 of Marijn Haverbeke's "Eloquent JavaScript" book.
Namely, in the example below, I don't understand what "element = (x, y) => undefined" in the constructor parameter list is doing, when the Matrix object is instantiated with "let matrix = new Matrix(3, 3, (x, y) => value ${x}, ${y});"
Can some step-by-step it for me? It seems like we are passing a function to the constructor, but I don't get why the constructor parameter list is setup the way it is with another arrow function.
var Matrix = class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
}
}
}
get(x, y) {
return this.content[y * this.width + x];
}
set(x, y, value) {
this.content[y * this.width + x] = value;
}
}
var MatrixIterator = class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return {done: true};
let value = {x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y)};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return {value, done: false};
}
}
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
let matrix = new Matrix(3, 3, (x, y) => `value ${x}, ${y}`);
for (let {x, y, value} of matrix) {
console.log(x, y, value);
}
Ok, I forgot in ES6+ an equal sign after a parameter is a default value if the parameter is missing or undefined. The way he did this is just a little awkward.
My friend and I are working on a small project, but are struggling with bin-packing problem using canvas drawing, to let the customers imagine, what they're ordering.
It's basically a PCB order form. What we are struggling with is that the canvas is somehow drawing "stairs" in the middle of the image, when adding patterns.
I can find no good reason why this is happening.
/*-- CLASSES --*/
let GrowingPacker = function() { };
GrowingPacker.prototype = {
fit: function(blocks) {
var n, node, block, len = blocks.length;
var w = len > 0 ? blocks[0].w : 0;
var h = len > 0 ? blocks[0].h : 0;
this.root = { x: 0, y: 0, w: w, h: h };
for (n = 0; n < len ; n++) {
block = blocks[n];
if (node = this.findNode(this.root, block.w, block.h))
block.fit = this.splitNode(node, block.w, block.h);
else
block.fit = this.growNode(block.w, block.h);
}
},
findNode: function(root, w, h) {
if (root.used)
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
else if ((w <= root.w) && (h <= root.h))
return root;
else
return null;
},
splitNode: function(node, w, h) {
node.used = true;
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
return node;
},
growNode: function(w, h) {
//var possibleGrowDown = (limit.area.max.height >= this.root.h + h);
var possibleGrowRight = (limit.area.max.width >= this.root.w + w);
var canGrowDown = (w <= this.root.w);
var canGrowRight = (h <= this.root.h);
var shouldGrowDown = possibleGrowRight && canGrowDown && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down when width is much greater than height
var shouldGrowRight = possibleGrowRight && canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width
if (shouldGrowDown)
return this.growDown(w, h);
else if (shouldGrowRight)
return this.growRight(w, h);
else if (canGrowDown)
return this.growDown(w, h);
else if (canGrowRight)
return this.growRight(w, h);
else
return null; // need to ensure sensible root starting size to avoid this happening
},
growRight: function(w, h) {
this.root = {
used: true,
x: 0,
y: 0,
w: this.root.w + w,
h: this.root.h,
down: this.root,
right: { x: this.root.w, y: 0, w: w, h: this.root.h }
};
var node;
if (node = this.findNode(this.root, w, h))
return this.splitNode(node, w, h);
else
return null;
},
growDown: function(w, h) {
this.root = {
used: true,
x: 0,
y: 0,
w: this.root.w,
h: this.root.h + h,
down: { x: 0, y: this.root.h, w: this.root.w, h: h },
right: this.root
};
var node;
if (node = this.findNode(this.root, w, h))
return this.splitNode(node, w, h);
else
return null;
}
}
class Line {
strokeStyle = '#ddd';
constructor(fX, fY, tX, tY) {
this.fX = fX;
this.fY = fY;
this.tX = tX;
this.tY = tY;
}
get length() {
const hL = Math.pow(this.tX - this.fX, 2);
const vL = Math.pow(this.tY - this.fY, 2);
const l = Math.sqrt(hL + vL);
return l;
}
draw(ctx) {
ctx.beginPath();
ctx.moveTo(this.fX, this.fY);
ctx.lineTo(this.tX, this.tY);
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
}
}
class Base {
x = 0;
y = 0;
w = 0;
h = 0;
fillStyle = '#e0ede0';
strokeStyle = '#0d0';
constructor() {}
resize(canvas, motifs) {
let x = canvas.width;
let y = canvas.height;
let w = 0;
let h = 0;
if(motifs && motifs.length > 0) {
motifs.forEach((motif) => {
if(motif.x < x) { x = motif.x; }
if(motif.y < y) { y = motif.y; }
if(motif.x + motif.w > x + w) { w = motif.x - x + motif.w; }
if(motif.y + motif.h > y + h) { h = motif.y - y + motif.h; }
});
}
else {
x = 0;
y = 0;
}
this.x = x - form.option.panelization[1].padding;
this.y = y - form.option.panelization[1].padding;
this.w = w + form.option.panelization[1].padding * 2;
this.h = h + form.option.panelization[1].padding * 2;
}
draw(ctx) {
ctx.beginPath();
ctx.rect(this.x, this.y, this.w, this.h);
ctx.fillStyle = this.fillStyle;
ctx.fill();
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
}
}
class Motif {
name = 'A';
subname = 1;
quantity = 1;
x = 0;
y = 0;
w = 0;
h = 0;
fillStyle = '#ede0e0';
strokeStyle = '#d00';
constructor(id, subname='1') {
this.id = id;
this.subname = subname;
}
get fullName() {
return this.name + this.subname;
}
get id() {
return this.number;
}
set id(id) {
this.number = id;
this.generateName();
}
get area() {
return this.w * this.h;
}
generateName() {
let num = this.number;
let s = '', t;
while(num > 0) {
t = (num - 1) % 26;
s = String.fromCharCode(65 + t) + s;
num = (num - t) / 26 | 0;
}
this.name = s;
}
rotate() {
const w = this.w;
this.w = this.h;
this.h = w;
}
draw(ctx) {
// Rectangle
ctx.beginPath();
ctx.rect(this.x, this.y, this.w, this.h);
ctx.fillStyle = this.fillStyle;
ctx.fill();
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
// Text
ctx.fillStyle = this.strokeStyle;
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.fullName, this.x + this.w / 2, this.y + this.h / 2);
}
}
class Canvas {
canvas;
ctx;
gridCellSize = 10;
gridLines = [];
base = new Base();
motifs = [];
millingLines = [];
constructor(canvas) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d');
// Create grid lines
for(let i = 0; i < this.canvas.width / this.gridCellSize + 1; i++) {
this.gridLines.push(new Line(i * this.gridCellSize, 0, i * this.gridCellSize, this.canvas.height));
}
for(let i = 0; i < this.canvas.height / this.gridCellSize + 1; i++) {
this.gridLines.push(new Line(0, i * this.gridCellSize, this.canvas.width, i * this.gridCellSize));
}
this.update();
}
update(motifs) {
this.motifs = motifs;
this.arrangeMotifs();
this.base.resize(this.canvas, this.motifs);
this.draw();
}
arrangeMotifs() {
if(form.option.panelization[1].motifs.length > 0) {
// Copy motifs
let motifs = [];
form.option.panelization[1].motifs.forEach((motif) => {
if(motif.w > 0 && motif.h > 0) {
for(let i = 0; i < motif.quantity; i++) {
const motifCopy = JSON.parse(JSON.stringify(motif));
const motifCopyKeys = Object.keys(motifCopy);
const motifCopyValues = Object.values(motifCopy);
let newMotif = new Motif(motifCopy.id);
for(let i = 0; i < motifCopyKeys.length; i++) {
newMotif[motifCopyKeys[i]] = motifCopyValues[i];
}
newMotif.subname = i+1;
newMotif.x = 0;
newMotif.y = 0;
if(newMotif.w > newMotif.h) {
newMotif.rotate();
}
// Add milling padding
newMotif.w += limit.milling;
newMotif.h += limit.milling;
motifs.push(newMotif);
}
}
});
// Place motifs
const baseW = limit.area.max.width - limit.milling - form.option.panelization[1].padding * 2;
const baseH = limit.area.max.height - limit.milling - form.option.panelization[1].padding * 2;
this.motifs = this.placeMotifs(motifs, baseW, baseH);
}
}
placeMotifs(motifs, baseW, baseH) {
// Sort by area
motifs.sort((a, b) => b.area - a.area);
// Sort by max(width, height)
motifs.sort((a, b) => Math.max(b.w, b.h) - Math.max(a.w, a.h));
// Packing motifs
const packer = new GrowingPacker();
packer.fit(motifs);
const finalMotifs = [];
motifs.forEach((motif) => {
const motifCopy = JSON.parse(JSON.stringify(motif));
const motifCopyKeys = Object.keys(motifCopy);
const motifCopyValues = Object.values(motifCopy);
let newMotif = new Motif(motifCopy.id);
for(let i = 0; i < motifCopyKeys.length; i++) {
newMotif[motifCopyKeys[i]] = motifCopyValues[i];
}
// Fix x & y
if(motif.fit) {
newMotif.x = motif.fit.x;
newMotif.y = motif.fit.y;
}
// Add padding
newMotif.x += form.option.panelization[1].padding;
newMotif.y += form.option.panelization[1].padding;
// Milling
newMotif.w -= limit.milling;
newMotif.h -= limit.milling;
finalMotifs.push(newMotif);
});
return finalMotifs;
}
draw() {
// Clear
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Grid lines
if(this.gridLines && this.gridLines.length > 0) {
this.gridLines.forEach((line) => {
line.draw(this.ctx);
});
}
// Text
this.ctx.fillStyle = '#888';
this.ctx.font = '12px Arial';
this.ctx.textAlign = 'right';
this.ctx.textBaseline = 'baseline';
this.ctx.fillText(this.canvas.width + 'x' + this.canvas.height + 'mm', this.canvas.width - 10, this.canvas.height - 10);
// Base
this.base.draw(this.ctx);
// Motifs
if(this.motifs && this.motifs.length > 0) {
this.motifs.forEach((motif) => {
motif.draw(this.ctx);
});
}
// Milling lines
if(this.millingLines && this.millingLines.length > 0) {
this.millingLines.forEach((millingLine) => {
millingLine.draw(this.ctx);
});
}
}
}
Live preview:
http://jadvo.eu/
Panelized pattern -> Add pattern (Keep clicking it to add more patterns and you will see what's the problem)