在油漆程序中实现喷枪效果(使用压力板倾斜)

时间:2017-07-12 21:29:50

标签: paint trigonometry

我正在尝试在绘图程序中实现“喷枪”效果,它利用了支持tiltX / tiltY的平板电脑。在Corel Painter中,这种效果看起来像enter image description here

来自平板电脑的信息:tiltX是笔在XY平面中的角度,tiltY是笔在YZ平面中的角度。所以我想象喷枪的效果可以实现,好像有一个锥形笔连接到笔上,在锥形半径内的画布上喷出圆点。从侧面我想象这样的事情:

enter image description here

有人知道这样做的数学,计算在圆锥内以随机方式放在画布上的点的x / y坐标。

“传播”值也很不错,如下图所示:

enter image description here

1 个答案:

答案 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(非常不可能)的线性分布。有关详细信息,请参阅代码。

JS中的示例

由于鼠标不能倾斜,喷雾固定在中心,移动鼠标远离中心改变倾斜和方向。



// 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;
&#13;
&#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)。重新清除。

&#13;
&#13;
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;
&#13;
&#13;