我正在尝试在绘图程序中实现“喷枪”效果,它利用了支持tiltX / tiltY的平板电脑。在Corel Painter中,这种效果看起来像
来自平板电脑的信息:tiltX是笔在XY平面中的角度,tiltY是笔在YZ平面中的角度。所以我想象喷枪的效果可以实现,好像有一个锥形笔连接到笔上,在锥形半径内的画布上喷出圆点。从侧面我想象这样的事情:
有人知道这样做的数学,计算在圆锥内以随机方式放在画布上的点的x / y坐标。
“传播”值也很不错,如下图所示:
答案 0 :(得分:1)
来自喷枪的喷雾,具有半径,倾斜,中心位置和方向。
通过将问题转换为三维,您可以在一个斜率为倾斜角度的平面上创建一组点(均匀分布在一个圆中)。
因此,相对于中心的2D点x,y通过添加取决于x位置倾斜的z而移动到3d平面。远离源距离> =移动到圆的半径
z = radius + x * sin(tilt);
然后通过除以新z除以半径
将该点投影回2D平面x = x / (z / radius);
y = y / (z / radius);
现在您只需要将2D点旋转到正确的方向。
首先将喷雾方向作为标准化矢量。
nx = cos(direction);
ny = sin(direction);
然后旋转2D点以与该向量对齐
xx = x * nx - y * ny;
yy = x * ny + y * nx;
你有投射点添加中心并绘制。
setPixel(xx + centerX, yy + centerY);
在圆形区域上创建均匀喷涂需要非均匀随机函数
angle = rand(Math.PI * 2); // get a random direction
dist = randL(rad,0); // get a random distance.
x = cos(angle) * dist; // find the point.
y = sin(angle) * dist;
函数rand(num)
返回从0到num均匀分布的随机数。
函数randL(min,max)
返回一个随机数,该数字具有从min(最可能)到max(非常不可能)的线性分布。有关详细信息,请参阅代码。
// set up mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
const ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// the random functions
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randL = (min, max = min + (min = 0)) => Math.abs(Math.random() + Math.random() - 1) * (max - min) + min;
// shorthand for loop
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
// draws a set of points around cx,cy and a radius of rad
// density is the number of pixels set per pixel
// tilt is the spray tilt in radians
// dir is the direction
const rad = 40;
function spray(rad,cx,cy,density=0.2,tilt, dir){
const count = ((rad * rad * Math.PI) * density) | 0;
var xA = Math.cos(dir);
var yA = Math.sin(dir);
doFor(count,i=>{
const angle = rand(Math.PI * 2);
const dist = randL(rad,0);
var x = Math.cos(angle) * dist;
var y = Math.sin(angle) * dist;
const z = rad + x * Math.sin(tilt);
x = x / (z / rad);
y = y / (z/ rad);
var xx = x * xA - y * yA;
var yy = x * yA + y * xA;
ctx.fillRect(xx + cx, yy + cy,1,1);;
})
}
function circle(rad,cx,cy,tilt, dir){
var xA = Math.cos(dir);
var yA = Math.sin(dir);
ctx.beginPath();
for(var i = 0; i <= 100; i ++){
var ang = (i / 100) * Math.PI * 2;
var x = Math.cos(ang) * rad
var y = Math.sin(ang) * rad
var z = rad + x * Math.sin(tilt);
x = x / (z / rad);
y = y / (z/ rad);
var xx = x * xA - y * yA;
var yy = x * yA + y * xA;
ctx.lineTo(xx + cx,yy + cy);
}
ctx.stroke();
}
function update(){
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
}else{
ctx.clearRect(0,0,w,h);
}
var dist = Math.hypot(cw-mouse.x,ch-mouse.y);
var tilt = Math.atan2(dist,100);
var dir = Math.atan2(ch-mouse.y,cw-mouse.x);
circle(rad,cw,ch,tilt,dir);
spray(rad,cw,ch,0.2,tilt,dir)
requestAnimationFrame(update);
}
update();
&#13;
canvas { position : absolute; top : 0px; left: 0px; }
&#13;
<canvas id="canvas"></canvas>
&#13;
根据评论中的要求,可以很容易地将扩展值添加为倾斜角度的因子。当倾斜度增加时,它只会增加圆的y半径。
spreadRad = rad * (1 + (tilt / PI) * spread); // PI = 3.1415...
因此点函数变为
angle = rand(Math.PI * 2); // get a random direction
dist = randL(rad,0); // get a random distance.
x = cos(angle) * dist; // find the point.
y = sin(angle) * dist * (1 + (tilt / PI) * spread); // PI = 3.1415...;
但这不像喷雾那么容易实施,因为它会改变点的分布。我把它添加到喷雾功能中。喷雾的面积增加了radiusB
(椭圆的区域= PI * radiusA * radiusB
),因此我计算了椭圆上的密度。虽然我不能100%确定该区域的覆盖范围是否保持不变。我将不得不尝试确定解决方案是否合适。
该示例显示1.5的展宽系数,红色圆圈显示原始未扩展区域。我还包括鼠标向下添加喷雾,以便您可以看到它是如何累积的(我已将alpha值设置为0.25)。重新清除。
const rad = 40; // radius of spray
const spread = 1.5; // linear spread as tilt increases
// set up mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
const ctx = canvas.getContext("2d");
const image = document.createElement("canvas");
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// the random functions
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randL = (min, max = min + (min = 0)) => Math.abs(Math.random() + Math.random() - 1) * (max - min) + min;
// shorthand for loop
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
// draws a set of points around cx,cy and a radius of rad
// density is the number of pixels set per pixel
// tilt is the spray tilt in radians
// dir is the direction
function spray(ctx,rad,cx,cy,density=0.2,tilt, dir){
const spreadRad = rad * (1 + (tilt / Math.PI) * spread);
const count = ((rad * spreadRad * Math.PI) * density) | 0;
var xA = Math.cos(dir);
var yA = Math.sin(dir);
doFor(count,i=>{
const angle = rand(Math.PI * 2);
const dist = randL(rad,0);
var x = Math.cos(angle) * dist;
var y = Math.sin(angle) * dist * (1 + (tilt / Math.PI) * spread);
const z = rad + x * Math.sin(tilt);
x = x / (z / rad);
y = y / (z/ rad);
var xx = x * xA - y * yA;
var yy = x * yA + y * xA;
ctx.fillRect(xx + cx, yy + cy,1,1);;
})
}
function circle(rad,cx,cy,tilt, dir, spread){
var xA = Math.cos(dir);
var yA = Math.sin(dir);
const spreadRad = rad * (1 + (tilt / Math.PI) * spread);
ctx.globalAlpha = 0.5;
ctx.beginPath();
for(var i = 0; i <= 100; i ++){
var ang = (i / 100) * Math.PI * 2;
var x = Math.cos(ang) * rad;
var y = Math.sin(ang) * spreadRad;
var z = rad + x * Math.sin(tilt);
x = x / (z / rad);
y = y / (z/ rad);
var xx = x * xA - y * yA;
var yy = x * yA + y * xA;
ctx.lineTo(xx + cx,yy + cy);
}
ctx.stroke();
ctx.globalAlpha = 1;
}
function update(){
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
image.width = w;
image.height = h;
}else{
ctx.clearRect(0,0,w,h);
}
ctx.drawImage(image,0,0);
var dist = Math.hypot(cw-mouse.x,ch-mouse.y);
var tilt = Math.atan2(dist,100);
var dir = Math.atan2(ch-mouse.y,cw-mouse.x);
ctx.strokeStyle = "red";
circle(rad,cw,ch,tilt,dir,0);
ctx.strokeStyle = "black";
circle(rad,cw,ch,tilt,dir,spread);
if(mouse.button){
const ct = image.getContext("2d");
ct.globalAlpha = 0.25;
spray(ct,rad,cw,ch,0.2,tilt,dir,spread);
ct.globalAlpha = 1;
}else{
spray(ctx,rad,cw,ch,0.2,tilt,dir,spread);
}
requestAnimationFrame(update);
}
update();
&#13;
canvas { position : absolute; top : 0px; left: 0px; }
&#13;
<canvas id="canvas"></canvas>
&#13;