用糖画 [英] Drawing with sugar

查看:147
本文介绍了用糖画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用糖的东西来画图,就像您将一些糖放在桌子上,然后用手指擦"糖颗粒以形成图像一样.

有人知道我可以使用js类型的工具吗?

我想我可以拍一张桌子的照片,以及一张上面放着糖的桌子的照片,然后仅擦除顶层,但是我担心这不会产生真正的效果.

我目前正在考虑拍摄一张办公桌的照片,然后使用JS生成许多必需的"粒子,然后可以将其擦除.这听起来很难做到.是吗?有人可以指出我的正确方向吗?

解决方案

沙还是糖?

一个有趣的问题,我不得不花点时间.

这可以通过创建多个缓冲区来容纳沙粒(糖)并在需要移动时赋予它们生命来起作用.

Javascript无法完成一百万个以上谷物的整个屏幕,因此此演示仅通过更新很少的内容并确定新动作的优先级来作弊,而不是允许较旧的谷物浪费CPU时间.

数组activesandStatus保留了沙子收益. active具有32Bit整数的像素地址,而sandStatus具有年龄.数组sand保留每个像素处的沙子量,并用于计算阴影效果(使用webGL着色器,阴影效果会更好),并确定沙子在受到干扰或掉落到表面时应该向哪个方向滑动. /p>

var activeMax保存最大数量的活性砂粒.增加以取得更好的效果,如果SIM变慢则减小.

使用鼠标右键放下沙子.在一个地方抱一堆.左键推动沙子.当您碰到更大的桩时,机器可能会滞后(取决于CPU能力和浏览器(Firefox中最好)).

push函数检查沙子阵列中是否有沙子.如果发现,它将沙子推离中心,并将其堆积在边缘周围.一些沙子会掉回来.

函数sprinkle添加沙粒(一个是按像素坐标或按索引的时间).函数push执行打砂FX. update通过检查周围像素的高度并在下坡处移动沙粒来移动沙粒. renderPix处理渲染颗粒,创建阴影并停用沙粒.数组shadowChange保存具有更改的像素的索引,以便可以更新阴影.

演示的后半部分只是用于设置鼠标和画布的样板.有关答案的所有代码都在上半部分.

 "use strict";

var activeMax = 2280;  // this is the number of sand grains that are processed at
                   // at time. Increase for better looking effect. decrease
                   // if the machine is not keeping up with the load

var cw;
var ch;
var w;  //  
var h;
var canvasBuf = document.createElement("canvas");
var ctxB
var globalTime;  // global to this 
var pixels
var sand;
var sandToFall;
var sandToFallCount = 36000;
var shadow; // shadow pixels
var activeMax = 2280;
var active; // index of pixel for active grain
var sandStatus; // status of active grain
var shadowChange; // holds index of pixels that have a shadow change
var pixels;
var buf;
var grain = 0xFFFFFFFF;
var shadowGrain = 0x00000000;
var ready = false;
var sandReady = 0;
var nextActive = 0;
var nextActiveShadow = 0;

var onResize = function(){
cw = canvas.width;
ch = canvas.height;
w = cw;  //  
h = ch;
pixels = w*h;
canvasBuf.width = w;
canvasBuf.height = h;
ctxB = canvasBuf.getContext("2d");
sand = new Uint8ClampedArray(pixels);
shadow = new Uint8ClampedArray(pixels); // shadow pixels
sandToFall = new Uint32Array(sandToFallCount);
activeMax = 2280;
active = new Uint32Array(activeMax); // index of pixel for active grain
sandStatus = new Uint16Array(activeMax); // status of active grain
shadowChange= new Uint32Array(activeMax); // holds index of pixels that have a shadow change
sandStatus.fill(0); // clear
active.fill(0);
shadowChange.fill(0);
ctxB.clearRect(0,0,w,h);
ctxB.fillStyle = "white";
ctxB.font = "84px arial";
ctxB.textAlign = "center";
ctxB.globalAlpha = 0.01;
for(var i = 0; i < 12; i ++){
    ctxB.fillText("Sand Doodler!",w/2 + (Math.random()-0.5)*5,h/2 + (Math.random()-0.5)*5);
}
ctxB.globalAlpha = 1;
pixels = ctxB.getImageData(0,0,w,h);
buf = new Uint32Array(pixels.data.buffer);   
for(i = 0; i < buf.length; i += 3){
    if(buf[i] !== 0){
        var c = buf[i] >>> 24;
        buf[i] = 0;
        while(c > 0){
            var ind = Math.floor(Math.random()*sandToFallCount);
            if(sandToFall[ind] === 0){
                sandToFall[ind] = i;
            }
            c = c >>> 1;
        }
    }
}
buf.fill(0);
offsets = [1,w-1,w,w+1,-w-1,-w,-w+1,-1];
shadowOffsets = [-w-1,-w,-1];
ready = true;
sandReady=0;
}
function sprinkle(x,y){
var ind;
if(y === undefined){
    ind = x;
}else{
    ind = x + y*w;
}
var alreadyExists = active.indexOf(ind);
var ac = nextActive;
if(alreadyExists > -1){
    sand[ind] += 1;
    shadow[ind] = 0; 
    sandStatus[alreadyExists] = 66;
}else{
    active[nextActive] = ind;
    sandStatus[nextActive] = 66;
    shadowChange[nextActiveShadow];
    nextActiveShadow = (nextActiveShadow+1)%activeMax;
    nextActive = (nextActive +1)%activeMax;
    sand[ind] += 1;
    shadow[ind] = 0;
}
return ac;
}

var offsets = [1,w-1,w,w+1,-w-1,-w,-w+1,-1];
var offsetCount = 8;
function update(){
var min,max,minDir,maxDir,dir,start,jj,j,ind,level,i,l1;
for( i = 0; i <activeMax; i ++){
    if(sandStatus[i] !== 0){
        ind = active[i];
        level = sand[ind];
        if(level === 1){
            sandStatus[i] = 1; // deactive is cant move (level ground)
        }else{
            min = level;
            var d;
            minDir = offsets[Math.floor(Math.random()*16)];
            dir = null;
            start = Math.floor(Math.random()*16); // start at a random direction
            for(j=0;j < offsetCount; j++){
                jj = offsets[(j + start)%offsetCount];
                l1 = sand[ind+jj];
                if(l1 < min){
                    min = l1;
                    minDir = jj;
                    d = (j + start)%offsetCount;
                }
            }
            dir = null;
            if(min >= level - 1){ // nowhere to move
                sandStatus[i] = 1;
                
            }else
            if(min < level-1){ // move to lowest
                dir = minDir
            }
            if(dir !== null){
                var lv = level-min;
                while(lv > 2){
                    active[i] = ind + dir;
                    if(sand[ind] > 1){
                        sand[ind] -= 2;
                        sprinkle(ind)
                    }else{
                        sand[ind] -=1;
                    }
                    ind = ind+dir;
                    sand[active[i]] += 1;
                    if(sand[active[i] + offsets[d]] >=level){
                        d+= Math.random()<0.5? 1 : offsetCount -1;
                        d %=offsetCount;
                    }
                    lv -= 1;
                }
                if(sand[ind]>0){
                    active[i] = ind + dir;
                    sand[ind] -= 1;
                }
                sand[active[i]] += 1;
            }
        }
    }
}   
}
var shadowOffsets = [-w-1,-w,-1];
var shadowCols = [0xFFf0f0f0,0xFFd0d0d0,0xFFb0b0b0,0xFF909090];
var shadowDist = [0xf0000000,0xd0000000,0xb0000000,0x90000000]; // shadow col no sand

// renders grains and adds shadows. Deactivates gains when they are done
function renderPix(){
var ac = 0;
for(var i = 0; i < activeMax; i ++){
    if(sandStatus[i] !== 0){
        ac += 1;
        var ind = active[i];
        buf[ind] = grain;
    }
}
for(var i = 0; i < activeMax; i ++){
    if(sandStatus[i] !== 0){
        var ind = active[i];
        var level = sand[ind];
        var col =0;
        if(sand[ind + shadowOffsets[0]] > level ){
            col = 2;
        }else
        if(sand[ind + shadowOffsets[1]] > level ){
            col =1;
        }else
        if(sand[ind + shadowOffsets[2]] > level ){
            col = 1;
        }
        buf[ind] = grain;  // add a sand grain to the image
        shadow[ind] = col;
        shadowChange[nextActiveShadow] = ind;
        nextActiveShadow = (nextActiveShadow + 1)%activeMax;

        var c = 4;
        while(c > 0){
            c-=1;
            ind += w + 1;
            var s = sand[ind];
            var dif = level - s;
            if(dif > 0){
                c-= dif;
            }
            shadow[ind] += 1;
            shadowChange[nextActiveShadow] = ind;
            nextActiveShadow = (nextActiveShadow + 1)%activeMax;
        }
        sandStatus[i] -= 1;
        if(sandStatus[i] === 1){
            sandStatus[i] = 0;
            active[i] = 0;                
        }
    }
}
// add calculated shadows
for(var i = 0; i < activeMax; i ++){
    if(shadowChange[i] !== 0){
        var ind = shadowChange[i];
        var s = shadow[ind] <4 ? shadow[ind]-1:3;
        if(sand[ind] > 0){
            buf[ind]=shadowCols[s];
        }else{
            buf[ind]=shadowDist[s];
        }
        shadowChange[i] = 0;
    }
}
   
}

// push sand about
function push(x,y,radius){
var iyy,iny
var rr = radius * radius ; 
x = Math.floor(x);
y = Math.floor(y);
for(var iy = -radius + 1; iy < radius; iy ++){
    iyy = iy * iy;
    iny = (y+iy) * w;
    for(var ix = -radius + 1; ix < radius; ix ++){
        if(ix*ix + iyy <= rr){ // is inside radius
            var ind = (x + ix) + iny; 
            if(sand[ind] > 0){
                var dir = Math.random() * Math.PI * 2;
                dir = Math.atan2(iy,ix)
                var r = radius + Math.random() * radius *0.2
                var xx = Math.cos(dir) * r;
                var yy = Math.sin(dir) * r;
                buf[ind] = 0x000000;
                sand[ind] = 0;
              
               ind = Math.floor(xx + x) + Math.floor(yy + y) * w;
               sprinkle(ind);
            }else{
                buf[ind] = 0;
            }
        }
    }
}
}

function showHeight(){  // for debugging only
for(var i = 0; i < sand.length; i ++){
    buf[i] = 0xff000000;
    var k = sand[i];
    buf[i] +=(k <<16) + (k<<8) + (k);
}
}

// main update function
function display(){
if(!ready){  // only when ready
    return;
}
//ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,cw,ch);
var mx = Math.floor((mouse.x/cw)*w); // canvas buf mouse pos
var my = Math.floor((mouse.y/ch)*h);

// drop sand
if(mouse.buttonRaw & 4){
    for(var i = 0; i < 120; i ++){
        var dir = Math.random()*Math.PI;
        var dist = ((Math.random()+Math.random()+Math.random())/3-0.5) * 62;
        var x = Math.cos(dir) * dist;
        var y = Math.sin(dir) * dist;
        x += mx;
        y += my;
        x = Math.floor(x); // floor
        y = Math.floor(y); // floor
       
        sprinkle(x,y);
    }
}else{
    // drop sand for intro FX
    if(sandReady <sandToFallCount){
        for(var i = 0; i < 120; i ++){        
            if(sandToFall[sandReady] !== 0){
                sprinkle(sandToFall[sandReady] + offsets[Math.floor(Math.random()*8)%offsets.length]);
            }
            sandReady += 1;
        }
    }
}
   // push sand about.
   if(mouse.buttonRaw & 1){
   push(((mouse.x/cw)*w),((mouse.y/ch)*h),32); // scale mouse to canvasBuf size
}
update();
renderPix();
//showHeight();
ctxB.putImageData(pixels,0,0);
ctx.drawImage(canvasBuf,0,0,cw,ch);

}








//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable 
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0; 

createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d"); 
if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = w = canvas.width; ch = h = canvas.height; mouse.updateBounds(); }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
    x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3], 
    active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
    var t = e.type;
    m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
    m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
    if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }  
    else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
    else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
    else if (t === "mouseover") { m.over = true; }
    else if (t === "mousewheel") { m.w = e.wheelDelta; }
    else if (t === "DOMMouseScroll") { m.w = -e.detail; }
    if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
    if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}        
    e.preventDefault();
}
m.updateBounds = function(){
    if(m.active){
        m.bounds = m.element.getBoundingClientRect();
    }
    
}
m.addCallback = function (callback) {
    if (typeof callback === "function") {
        if (m.callbacks === U) { m.callbacks = [callback]; }
        else { m.callbacks.push(callback); }
    } else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
    if (m.element !== U) { m.removeMouse(); }        
    m.element = element === U ? document : element;
    m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
    m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
    if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
    m.active = true;
    m.updateBounds();
}
m.remove = function () {
    if (m.element !== U) {
        m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
        if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
        m.element = m.callbacks = m.contextMenuBlocked = U;
        m.active = false;
    }
}
return mouse;
})();
function main(timer){ // Main update loop
globalTime = timer;
display();  // call demo code
requestAnimationFrame(main);
}
resizeCanvas(); 
mouse.start(canvas,true); 
window.addEventListener("resize",resizeCanvas); 
requestAnimationFrame(main); 

 body {
  background:#49D;
}
.help {
  text-align : center;
  font-family : Arial,"Helvetica Neue",Helvetica,sans-serif;
  font-size : 18px;
} 

 <div class="help">Right mouse to drop sand, left button to push it around.</div> 

I want to create a drawing in sugar thing, much the same as you would throw some sugar on a table and using your fingers to "erase" the sugar particles to form an image.

Does anyone know of a js type tool I can use to make this happen?

I suppose I can take a photo of a desk, and a photo of desk with some sugar on it, and then just erase the top layer, but I'm worried that this won't give a real effect.

I'm currently thinking of having a photo of desk, and then using JS to generate a lot of "sugary" particles, which I can then erase. This sounds incredibly hard to do though. Is it? Can someone point me in a good direction?

解决方案

Sand or is it sugar?

An interesting problem that I had to give a little time.

This works by creating several buffers to hold grains of sand (sugar) and give them life when they need to move.

There is no way that Javascript could do a whole screen of a million plus grains so this demo cheats by only updating a very few and prioritising for new movement rather than allow older moving grains to hog CPU time.

The arrays active, sandStatus, holds the sand gains. active has the pixel address as a 32Bit int and sandStatus has age. The Array sand holds the amount of sand at each pixel and is used to calculate the shadow effect (shadow could be much better using a webGL shader) and to work out which direction sand should slide if disturbed or dropped to the surface.

the var activeMax holds the max number of active sand grains. Increase for a better effect, decrease if the sim runs to slow.

To drop sand use the right mouse button. Hold at one spot to make a pile. Left button pushes the sand about. When you hit a bigger pile the machine may lag (depending on CPU power and browser (best in firefox)).

The push function checks the sand array for any sand. If found it pushes the sand away from the center and piles it up around the edge. Some sand will fall back.

The function sprinkle adds grains of sand (one are a time by pixel coordinate or by index). The function push does the sand drawing FX. update moves the sand grains by checking surrounding pixels heights and moving grains down hill. renderPix handles rendering grains, creating the shadows and deactivating sand grains. The Array shadowChange holds the index of pixels that have had changes so that the shadows can be updated.

Bottom half of the demo is just boilerplate for mouse and canvas setup. All the code in regard to the answer is in the first half.

"use strict";

var activeMax = 2280;  // this is the number of sand grains that are processed at
                   // at time. Increase for better looking effect. decrease
                   // if the machine is not keeping up with the load

var cw;
var ch;
var w;  //  
var h;
var canvasBuf = document.createElement("canvas");
var ctxB
var globalTime;  // global to this 
var pixels
var sand;
var sandToFall;
var sandToFallCount = 36000;
var shadow; // shadow pixels
var activeMax = 2280;
var active; // index of pixel for active grain
var sandStatus; // status of active grain
var shadowChange; // holds index of pixels that have a shadow change
var pixels;
var buf;
var grain = 0xFFFFFFFF;
var shadowGrain = 0x00000000;
var ready = false;
var sandReady = 0;
var nextActive = 0;
var nextActiveShadow = 0;

var onResize = function(){
cw = canvas.width;
ch = canvas.height;
w = cw;  //  
h = ch;
pixels = w*h;
canvasBuf.width = w;
canvasBuf.height = h;
ctxB = canvasBuf.getContext("2d");
sand = new Uint8ClampedArray(pixels);
shadow = new Uint8ClampedArray(pixels); // shadow pixels
sandToFall = new Uint32Array(sandToFallCount);
activeMax = 2280;
active = new Uint32Array(activeMax); // index of pixel for active grain
sandStatus = new Uint16Array(activeMax); // status of active grain
shadowChange= new Uint32Array(activeMax); // holds index of pixels that have a shadow change
sandStatus.fill(0); // clear
active.fill(0);
shadowChange.fill(0);
ctxB.clearRect(0,0,w,h);
ctxB.fillStyle = "white";
ctxB.font = "84px arial";
ctxB.textAlign = "center";
ctxB.globalAlpha = 0.01;
for(var i = 0; i < 12; i ++){
    ctxB.fillText("Sand Doodler!",w/2 + (Math.random()-0.5)*5,h/2 + (Math.random()-0.5)*5);
}
ctxB.globalAlpha = 1;
pixels = ctxB.getImageData(0,0,w,h);
buf = new Uint32Array(pixels.data.buffer);   
for(i = 0; i < buf.length; i += 3){
    if(buf[i] !== 0){
        var c = buf[i] >>> 24;
        buf[i] = 0;
        while(c > 0){
            var ind = Math.floor(Math.random()*sandToFallCount);
            if(sandToFall[ind] === 0){
                sandToFall[ind] = i;
            }
            c = c >>> 1;
        }
    }
}
buf.fill(0);
offsets = [1,w-1,w,w+1,-w-1,-w,-w+1,-1];
shadowOffsets = [-w-1,-w,-1];
ready = true;
sandReady=0;
}
function sprinkle(x,y){
var ind;
if(y === undefined){
    ind = x;
}else{
    ind = x + y*w;
}
var alreadyExists = active.indexOf(ind);
var ac = nextActive;
if(alreadyExists > -1){
    sand[ind] += 1;
    shadow[ind] = 0; 
    sandStatus[alreadyExists] = 66;
}else{
    active[nextActive] = ind;
    sandStatus[nextActive] = 66;
    shadowChange[nextActiveShadow];
    nextActiveShadow = (nextActiveShadow+1)%activeMax;
    nextActive = (nextActive +1)%activeMax;
    sand[ind] += 1;
    shadow[ind] = 0;
}
return ac;
}

var offsets = [1,w-1,w,w+1,-w-1,-w,-w+1,-1];
var offsetCount = 8;
function update(){
var min,max,minDir,maxDir,dir,start,jj,j,ind,level,i,l1;
for( i = 0; i <activeMax; i ++){
    if(sandStatus[i] !== 0){
        ind = active[i];
        level = sand[ind];
        if(level === 1){
            sandStatus[i] = 1; // deactive is cant move (level ground)
        }else{
            min = level;
            var d;
            minDir = offsets[Math.floor(Math.random()*16)];
            dir = null;
            start = Math.floor(Math.random()*16); // start at a random direction
            for(j=0;j < offsetCount; j++){
                jj = offsets[(j + start)%offsetCount];
                l1 = sand[ind+jj];
                if(l1 < min){
                    min = l1;
                    minDir = jj;
                    d = (j + start)%offsetCount;
                }
            }
            dir = null;
            if(min >= level - 1){ // nowhere to move
                sandStatus[i] = 1;
                
            }else
            if(min < level-1){ // move to lowest
                dir = minDir
            }
            if(dir !== null){
                var lv = level-min;
                while(lv > 2){
                    active[i] = ind + dir;
                    if(sand[ind] > 1){
                        sand[ind] -= 2;
                        sprinkle(ind)
                    }else{
                        sand[ind] -=1;
                    }
                    ind = ind+dir;
                    sand[active[i]] += 1;
                    if(sand[active[i] + offsets[d]] >=level){
                        d+= Math.random()<0.5? 1 : offsetCount -1;
                        d %=offsetCount;
                    }
                    lv -= 1;
                }
                if(sand[ind]>0){
                    active[i] = ind + dir;
                    sand[ind] -= 1;
                }
                sand[active[i]] += 1;
            }
        }
    }
}   
}
var shadowOffsets = [-w-1,-w,-1];
var shadowCols = [0xFFf0f0f0,0xFFd0d0d0,0xFFb0b0b0,0xFF909090];
var shadowDist = [0xf0000000,0xd0000000,0xb0000000,0x90000000]; // shadow col no sand

// renders grains and adds shadows. Deactivates gains when they are done
function renderPix(){
var ac = 0;
for(var i = 0; i < activeMax; i ++){
    if(sandStatus[i] !== 0){
        ac += 1;
        var ind = active[i];
        buf[ind] = grain;
    }
}
for(var i = 0; i < activeMax; i ++){
    if(sandStatus[i] !== 0){
        var ind = active[i];
        var level = sand[ind];
        var col =0;
        if(sand[ind + shadowOffsets[0]] > level ){
            col = 2;
        }else
        if(sand[ind + shadowOffsets[1]] > level ){
            col =1;
        }else
        if(sand[ind + shadowOffsets[2]] > level ){
            col = 1;
        }
        buf[ind] = grain;  // add a sand grain to the image
        shadow[ind] = col;
        shadowChange[nextActiveShadow] = ind;
        nextActiveShadow = (nextActiveShadow + 1)%activeMax;

        var c = 4;
        while(c > 0){
            c-=1;
            ind += w + 1;
            var s = sand[ind];
            var dif = level - s;
            if(dif > 0){
                c-= dif;
            }
            shadow[ind] += 1;
            shadowChange[nextActiveShadow] = ind;
            nextActiveShadow = (nextActiveShadow + 1)%activeMax;
        }
        sandStatus[i] -= 1;
        if(sandStatus[i] === 1){
            sandStatus[i] = 0;
            active[i] = 0;                
        }
    }
}
// add calculated shadows
for(var i = 0; i < activeMax; i ++){
    if(shadowChange[i] !== 0){
        var ind = shadowChange[i];
        var s = shadow[ind] <4 ? shadow[ind]-1:3;
        if(sand[ind] > 0){
            buf[ind]=shadowCols[s];
        }else{
            buf[ind]=shadowDist[s];
        }
        shadowChange[i] = 0;
    }
}
   
}

// push sand about
function push(x,y,radius){
var iyy,iny
var rr = radius * radius ; 
x = Math.floor(x);
y = Math.floor(y);
for(var iy = -radius + 1; iy < radius; iy ++){
    iyy = iy * iy;
    iny = (y+iy) * w;
    for(var ix = -radius + 1; ix < radius; ix ++){
        if(ix*ix + iyy <= rr){ // is inside radius
            var ind = (x + ix) + iny; 
            if(sand[ind] > 0){
                var dir = Math.random() * Math.PI * 2;
                dir = Math.atan2(iy,ix)
                var r = radius + Math.random() * radius *0.2
                var xx = Math.cos(dir) * r;
                var yy = Math.sin(dir) * r;
                buf[ind] = 0x000000;
                sand[ind] = 0;
              
               ind = Math.floor(xx + x) + Math.floor(yy + y) * w;
               sprinkle(ind);
            }else{
                buf[ind] = 0;
            }
        }
    }
}
}

function showHeight(){  // for debugging only
for(var i = 0; i < sand.length; i ++){
    buf[i] = 0xff000000;
    var k = sand[i];
    buf[i] +=(k <<16) + (k<<8) + (k);
}
}

// main update function
function display(){
if(!ready){  // only when ready
    return;
}
//ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,cw,ch);
var mx = Math.floor((mouse.x/cw)*w); // canvas buf mouse pos
var my = Math.floor((mouse.y/ch)*h);

// drop sand
if(mouse.buttonRaw & 4){
    for(var i = 0; i < 120; i ++){
        var dir = Math.random()*Math.PI;
        var dist = ((Math.random()+Math.random()+Math.random())/3-0.5) * 62;
        var x = Math.cos(dir) * dist;
        var y = Math.sin(dir) * dist;
        x += mx;
        y += my;
        x = Math.floor(x); // floor
        y = Math.floor(y); // floor
       
        sprinkle(x,y);
    }
}else{
    // drop sand for intro FX
    if(sandReady <sandToFallCount){
        for(var i = 0; i < 120; i ++){        
            if(sandToFall[sandReady] !== 0){
                sprinkle(sandToFall[sandReady] + offsets[Math.floor(Math.random()*8)%offsets.length]);
            }
            sandReady += 1;
        }
    }
}
   // push sand about.
   if(mouse.buttonRaw & 1){
   push(((mouse.x/cw)*w),((mouse.y/ch)*h),32); // scale mouse to canvasBuf size
}
update();
renderPix();
//showHeight();
ctxB.putImageData(pixels,0,0);
ctx.drawImage(canvasBuf,0,0,cw,ch);

}








//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable 
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0; 

createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d"); 
if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = w = canvas.width; ch = h = canvas.height; mouse.updateBounds(); }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
    x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3], 
    active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
    var t = e.type;
    m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
    m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
    if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }  
    else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
    else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
    else if (t === "mouseover") { m.over = true; }
    else if (t === "mousewheel") { m.w = e.wheelDelta; }
    else if (t === "DOMMouseScroll") { m.w = -e.detail; }
    if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
    if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}        
    e.preventDefault();
}
m.updateBounds = function(){
    if(m.active){
        m.bounds = m.element.getBoundingClientRect();
    }
    
}
m.addCallback = function (callback) {
    if (typeof callback === "function") {
        if (m.callbacks === U) { m.callbacks = [callback]; }
        else { m.callbacks.push(callback); }
    } else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
    if (m.element !== U) { m.removeMouse(); }        
    m.element = element === U ? document : element;
    m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
    m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
    if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
    m.active = true;
    m.updateBounds();
}
m.remove = function () {
    if (m.element !== U) {
        m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
        if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
        m.element = m.callbacks = m.contextMenuBlocked = U;
        m.active = false;
    }
}
return mouse;
})();
function main(timer){ // Main update loop
globalTime = timer;
display();  // call demo code
requestAnimationFrame(main);
}
resizeCanvas(); 
mouse.start(canvas,true); 
window.addEventListener("resize",resizeCanvas); 
requestAnimationFrame(main);

body {
  background:#49D;
}
.help {
  text-align : center;
  font-family : Arial,"Helvetica Neue",Helvetica,sans-serif;
  font-size : 18px;
}

<div class="help">Right mouse to drop sand, left button to push it around.</div>

这篇关于用糖画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆