在 Javascript 中计算粒子大小和它们之间的距离
Calculating particle size and distance between them in Javascript
我正在尝试显示带有粒子的图像。它有效,但粒子的数量取决于一个变量 (numberOfParticles),该变量的范围在 0 到 3000 之间。在任何值上,图像都应该以给定数量的粒子以最佳方式渲染。有一个嵌套的 for 循环遍历图像数据(高度和宽度)并创建这样的粒子。
for (var y = 0; y < data.height; y+=averageDistance) {
for (var x = 0; x < data.width; x+=averageDistance) {
if (particles.length < numberOfParticles){
var particle = {
x0: x,
y0: y,
color: "rgb("+data.data[(y * 4 * data.width)+ (x * 4)]+","+data.data[(y * 4 * data.width)+ (x * 4) +1]+","+data.data[(y * 4 * data.width)+ (x * 4) +2]+")"
};
particles.push(particle);
}
}
}
稍后在代码中,粒子以给定的大小渲染。
我的问题是,如何计算粒子的大小以及它们之间的距离?
我已经尝试计算 'average distance',计算未被粒子覆盖的像素数量并将其除以粒子数量,但我无法使其正常工作。在变量 numberOfParticles 的某些值上总是有剩余 space(因此底部不会被填充)或剩余粒子(因此只显示 40 个粒子,而不是 50 个)。
可以在 this answer 中找到数学部分的解决方案。
要找到 x (nx) 的点数,我们可以使用公式:
那么y的点数(ny):
ny = n / nx
在JavaScript代码中:
nx = Math.sqrt((w / h) * n + Math.pow(w - h, 2) / (4 * Math.pow(h, 2))) - (w - h) / (2 * h);
ny = n / nx;
然后使用数字 nx 和 ny 我们可以计算 x 和 y 的增量:
dx = w / nx;
dy = h / ny;
例子
var ctx = c.getContext("2d"), n, w, h, nx, ny, dx, dy, x, y;
// define values
n = 1600;
w = c.width - 1; // make inclusive
h = c.height - 1;
// plug values into formula
nx = Math.sqrt((w / h) * n + Math.pow(w - h, 2) / (4 * Math.pow(h, 2))) - (w - h) / (2 * h);
ny = n / nx;
// calculate deltas
dx = w / nx;
dy = h / ny;
// render proof-of-concept
for(y = 0; y < h; y += dy) {
for(x = 0; x < w; x += dx) {
ctx.fillStyle = "hsl(" + (360*Math.random()) + ",50%,50%";
ctx.fillRect(x, y, dx-1, dy-1);
}
}
o.innerHTML = "Points to place: " + n + "<br>" +
"<strong>n<sub>x</sub></strong>: " + nx.toFixed(2) + "<br>" +
"<strong>n<sub>y</sub></strong>: " + ny.toFixed(2) + "<br>" +
"ΔX: " + dy.toFixed(2) + "<br>" +
"ΔY: " + dy.toFixed(2) + "<br>" +
"Total (nx × ny): " + (nx * ny).toFixed(0);
<canvas id=c width=600 height=400></canvas>
<br><output id=o></output>
最适合保养方面。
这类包装问题在 CG 中经常出现。
要用较小的方框填充方框,您只需找到所需计数的平方根。
var c = 100; // number of boxes
var w = 10; // box width
var h = 10; // box height
var sW = 1000; // screen width
var sH = 1000; // screen height
拟合度为 sqrt(c) = sqrt(100) = 10
。那是 10,用屏幕宽度除以计数 sW/10 = 100 得到框的宽度,这样 100 就适合屏幕。
这适用于整数平方的计数,如果没有平方根,我们可以分解以找到更好的解决方案。但即便如此,也不总是有适合的解决方案。
质数永远放不下
凡是质数的计数都没有解,x行y列不可能填入质数。这是因为结果计数是 x * y,这意味着它不是质数。
妥协
最终你需要妥协。您要么将计数控制为仅允许使用解(不是素数)进行计数,要么您接受会出现一些错误并允许解超出范围。
此解决方案将适用,但允许更改计数并使屏幕外的区域最小化,同时仍保持框的纵横比。
宽度计数是 sqrt((c / sA) * bA),其中 sA 是屏幕纵横比,bA 是方框纵横比。
var c = 100; // number of boxes
var w = 10; // box width
var h = 10; // box height
var sW = 1000; // screen width
var sH = 1000; // screen height
var sA = sH / sW; // screen aspect
var bA = h / w; // box aspect
var wCount = Math.sqrt((c / sA) * bA); // get nearest fit for width
wCount = Math.round(wCount); // round to an integers. This forces the width to fit
现在您有了 wCount,只需将屏幕宽度除以该值即可得到框渲染宽度,然后将框渲染宽度乘以框纵横比即可得到框渲染高度。
var rW = w / wCount; // the size of the box to render
var rH = rW * bA; // the height is the width time aspect
有时你会得到一个完美的解决方案,但大多数时候你不会。由于四舍五入,实际计数将高于或低于请求的计数。但对于所需的盒子和屏幕方面来说,适合度将是最好的。
演示
每 3 秒更新一次,随机框大小,随机屏幕大小(覆盖绿色框以显示顶部和底部多余部分)文本显示列数和行数、请求数和实际数。
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resize(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
window.addEventListener("resize",resize);
resize();
var w = 10;
var h = 20;
var c = 100;
var sW = canvas.width*0.7;
var sH = canvas.height*0.5;
var sA = sH /sW;
var bA = h / w;
function getParticleWidth(particleImageX,particleImageY,w,h,particleCount){
var a = particleImageY / particleImageX; // get particle aspect
var c = particleCount; // same with this
var b = h/w
return Math.sqrt((c/b)*a)
}
function drawTheParticles(){
var x,y;
pCount = Math.round(Math.sqrt((c/sA)*bA));
var pWidth = sW / pCount;
var pHeight = pWidth * bA;
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
ctx.clearRect(0,0,canvas.width,canvas.height); // clear last result
var cc = 0;
var sx = (canvas.width-sW)/2;
var sy = (canvas.height-sH)/2;
var hc = ((Math.ceil(sH / pHeight)*pHeight)-sH)/2;
var wc =0;
for(y = 0; y < sH; y += pHeight){
for(x = 0; x < sW-(pWidth/2); x += pWidth){
ctx.fillRect(x + 1+sx-wc, y + 1+sy-hc, pWidth - 2, pHeight - 2);
ctx.strokeRect(x + 1+sx-wc, y + 1+sy-hc, pWidth - 2, pHeight - 2);
cc ++;
}
}
ctx.strokeStyle = "black";
ctx.fillStyle = "rgba(50,200,70,0.25)";
ctx.fillRect(sx, sy, sW, sH);
ctx.strokeRect(sx, sy, sW, sH);
// show the details
ctx.font = "20px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var str = ""+pCount+" by "+Math.ceil(sH / pHeight)+" need " + c + " got "+cc;
var width = ctx.measureText(str).width;
ctx.lineWidth = 2;
// clear an area for text
// with a shadow and not the stupid built in shadow
ctx.fillStyle = "rgba(0,0,0,0.4)";
ctx.fillRect((canvas.width / 2) - (width + 8) / 2+6, (canvas.height / 2) - 14+6, width + 8, 28 );
ctx.fillStyle = "#CCC";
ctx.fillRect((canvas.width / 2) - (width + 8) / 2, (canvas.height / 2) - 14, width + 8, 28 );
ctx.fillRect((canvas.width / 2) - (width + 8) / 2, (canvas.height / 2) - 14, width + 8, 28 );
// now draw the text with a bit of an outline
ctx.fillStyle = "blue"
ctx.strokeStyle = "white";
ctx.lineJoin = "round";
ctx.strokeText(str, canvas.width/2, canvas.height / 2);
ctx.fillText(str, canvas.width/2, canvas.height / 2);
// And set up to do it all again in 3 seconds
// get random particle image size
w = Math.floor(Math.random() * 100 + 10);
h = Math.floor(Math.random() * 100 + 10);
// get random particle count
c = Math.floor(Math.random() * 500 + 10);
// get random screen width height
sW = canvas.height*(Math.random()*0.4 + 0.6);
sH = canvas.height*(Math.random()*0.6 + 0.4);
// recaculate aspects
sA = sH /sW;
bA = h / w;
// redo it in 3 seconds
setTimeout(drawTheParticles,3000)
}
drawTheParticles()
我正在尝试显示带有粒子的图像。它有效,但粒子的数量取决于一个变量 (numberOfParticles),该变量的范围在 0 到 3000 之间。在任何值上,图像都应该以给定数量的粒子以最佳方式渲染。有一个嵌套的 for 循环遍历图像数据(高度和宽度)并创建这样的粒子。
for (var y = 0; y < data.height; y+=averageDistance) {
for (var x = 0; x < data.width; x+=averageDistance) {
if (particles.length < numberOfParticles){
var particle = {
x0: x,
y0: y,
color: "rgb("+data.data[(y * 4 * data.width)+ (x * 4)]+","+data.data[(y * 4 * data.width)+ (x * 4) +1]+","+data.data[(y * 4 * data.width)+ (x * 4) +2]+")"
};
particles.push(particle);
}
}
}
稍后在代码中,粒子以给定的大小渲染。
我的问题是,如何计算粒子的大小以及它们之间的距离?
我已经尝试计算 'average distance',计算未被粒子覆盖的像素数量并将其除以粒子数量,但我无法使其正常工作。在变量 numberOfParticles 的某些值上总是有剩余 space(因此底部不会被填充)或剩余粒子(因此只显示 40 个粒子,而不是 50 个)。
可以在 this answer 中找到数学部分的解决方案。
要找到 x (nx) 的点数,我们可以使用公式:
那么y的点数(ny):
ny = n / nx
在JavaScript代码中:
nx = Math.sqrt((w / h) * n + Math.pow(w - h, 2) / (4 * Math.pow(h, 2))) - (w - h) / (2 * h);
ny = n / nx;
然后使用数字 nx 和 ny 我们可以计算 x 和 y 的增量:
dx = w / nx;
dy = h / ny;
例子
var ctx = c.getContext("2d"), n, w, h, nx, ny, dx, dy, x, y;
// define values
n = 1600;
w = c.width - 1; // make inclusive
h = c.height - 1;
// plug values into formula
nx = Math.sqrt((w / h) * n + Math.pow(w - h, 2) / (4 * Math.pow(h, 2))) - (w - h) / (2 * h);
ny = n / nx;
// calculate deltas
dx = w / nx;
dy = h / ny;
// render proof-of-concept
for(y = 0; y < h; y += dy) {
for(x = 0; x < w; x += dx) {
ctx.fillStyle = "hsl(" + (360*Math.random()) + ",50%,50%";
ctx.fillRect(x, y, dx-1, dy-1);
}
}
o.innerHTML = "Points to place: " + n + "<br>" +
"<strong>n<sub>x</sub></strong>: " + nx.toFixed(2) + "<br>" +
"<strong>n<sub>y</sub></strong>: " + ny.toFixed(2) + "<br>" +
"ΔX: " + dy.toFixed(2) + "<br>" +
"ΔY: " + dy.toFixed(2) + "<br>" +
"Total (nx × ny): " + (nx * ny).toFixed(0);
<canvas id=c width=600 height=400></canvas>
<br><output id=o></output>
最适合保养方面。
这类包装问题在 CG 中经常出现。
要用较小的方框填充方框,您只需找到所需计数的平方根。
var c = 100; // number of boxes
var w = 10; // box width
var h = 10; // box height
var sW = 1000; // screen width
var sH = 1000; // screen height
拟合度为 sqrt(c) = sqrt(100) = 10
。那是 10,用屏幕宽度除以计数 sW/10 = 100 得到框的宽度,这样 100 就适合屏幕。
这适用于整数平方的计数,如果没有平方根,我们可以分解以找到更好的解决方案。但即便如此,也不总是有适合的解决方案。
质数永远放不下
凡是质数的计数都没有解,x行y列不可能填入质数。这是因为结果计数是 x * y,这意味着它不是质数。
妥协
最终你需要妥协。您要么将计数控制为仅允许使用解(不是素数)进行计数,要么您接受会出现一些错误并允许解超出范围。
此解决方案将适用,但允许更改计数并使屏幕外的区域最小化,同时仍保持框的纵横比。
宽度计数是 sqrt((c / sA) * bA),其中 sA 是屏幕纵横比,bA 是方框纵横比。
var c = 100; // number of boxes
var w = 10; // box width
var h = 10; // box height
var sW = 1000; // screen width
var sH = 1000; // screen height
var sA = sH / sW; // screen aspect
var bA = h / w; // box aspect
var wCount = Math.sqrt((c / sA) * bA); // get nearest fit for width
wCount = Math.round(wCount); // round to an integers. This forces the width to fit
现在您有了 wCount,只需将屏幕宽度除以该值即可得到框渲染宽度,然后将框渲染宽度乘以框纵横比即可得到框渲染高度。
var rW = w / wCount; // the size of the box to render
var rH = rW * bA; // the height is the width time aspect
有时你会得到一个完美的解决方案,但大多数时候你不会。由于四舍五入,实际计数将高于或低于请求的计数。但对于所需的盒子和屏幕方面来说,适合度将是最好的。
演示
每 3 秒更新一次,随机框大小,随机屏幕大小(覆盖绿色框以显示顶部和底部多余部分)文本显示列数和行数、请求数和实际数。
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resize(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
window.addEventListener("resize",resize);
resize();
var w = 10;
var h = 20;
var c = 100;
var sW = canvas.width*0.7;
var sH = canvas.height*0.5;
var sA = sH /sW;
var bA = h / w;
function getParticleWidth(particleImageX,particleImageY,w,h,particleCount){
var a = particleImageY / particleImageX; // get particle aspect
var c = particleCount; // same with this
var b = h/w
return Math.sqrt((c/b)*a)
}
function drawTheParticles(){
var x,y;
pCount = Math.round(Math.sqrt((c/sA)*bA));
var pWidth = sW / pCount;
var pHeight = pWidth * bA;
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
ctx.clearRect(0,0,canvas.width,canvas.height); // clear last result
var cc = 0;
var sx = (canvas.width-sW)/2;
var sy = (canvas.height-sH)/2;
var hc = ((Math.ceil(sH / pHeight)*pHeight)-sH)/2;
var wc =0;
for(y = 0; y < sH; y += pHeight){
for(x = 0; x < sW-(pWidth/2); x += pWidth){
ctx.fillRect(x + 1+sx-wc, y + 1+sy-hc, pWidth - 2, pHeight - 2);
ctx.strokeRect(x + 1+sx-wc, y + 1+sy-hc, pWidth - 2, pHeight - 2);
cc ++;
}
}
ctx.strokeStyle = "black";
ctx.fillStyle = "rgba(50,200,70,0.25)";
ctx.fillRect(sx, sy, sW, sH);
ctx.strokeRect(sx, sy, sW, sH);
// show the details
ctx.font = "20px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var str = ""+pCount+" by "+Math.ceil(sH / pHeight)+" need " + c + " got "+cc;
var width = ctx.measureText(str).width;
ctx.lineWidth = 2;
// clear an area for text
// with a shadow and not the stupid built in shadow
ctx.fillStyle = "rgba(0,0,0,0.4)";
ctx.fillRect((canvas.width / 2) - (width + 8) / 2+6, (canvas.height / 2) - 14+6, width + 8, 28 );
ctx.fillStyle = "#CCC";
ctx.fillRect((canvas.width / 2) - (width + 8) / 2, (canvas.height / 2) - 14, width + 8, 28 );
ctx.fillRect((canvas.width / 2) - (width + 8) / 2, (canvas.height / 2) - 14, width + 8, 28 );
// now draw the text with a bit of an outline
ctx.fillStyle = "blue"
ctx.strokeStyle = "white";
ctx.lineJoin = "round";
ctx.strokeText(str, canvas.width/2, canvas.height / 2);
ctx.fillText(str, canvas.width/2, canvas.height / 2);
// And set up to do it all again in 3 seconds
// get random particle image size
w = Math.floor(Math.random() * 100 + 10);
h = Math.floor(Math.random() * 100 + 10);
// get random particle count
c = Math.floor(Math.random() * 500 + 10);
// get random screen width height
sW = canvas.height*(Math.random()*0.4 + 0.6);
sH = canvas.height*(Math.random()*0.6 + 0.4);
// recaculate aspects
sA = sH /sW;
bA = h / w;
// redo it in 3 seconds
setTimeout(drawTheParticles,3000)
}
drawTheParticles()