创建描述点的离散分布的颜色渐变

时间:2016-02-24 16:39:56

标签: javascript math html5-canvas gradient

我正在创建一个渲染游戏对象位置的地图(Project Zomboid zombies):

zombies

随着用户缩小,单点不再有用。相反,我想使用红色渐变在一个区域上渲染僵尸的分布。我试图为每个渲染的像素循环遍历所有僵尸,并将其相互着色为与僵尸的平方距离之和。结果:

image description

这太模糊了。此外,结果更受僵尸的影响,这些僵尸来自各点 - 我需要通过关闭的僵尸更多地影响它们。那么这就是数学。这是我使用的代码:

var h = canvas.height;
var w = canvas.width; 
// To loop over more than 1 pixel (performance)
var tileSize = 10;
var halfRadius = Math.floor(tileSize/2);
var time = performance.now();
// "Squared" because we didnt unsquare it   
function distanceSquared(A, B) {
  return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}
// Loop for every x,y pixel (or region of pixels)
for(var y=0; y<h; y+=tileSize) {
  for(var x=0; x<w; x+=tileSize) {
     // Time security - stop rendering after 1 second
     if(performance.now()-time>1000) {
        x=w;y=h;break;
     }
     // Convert relative canvas offset to absolute point on the map
     var point = canvasPixeltoImagePixel(x, y);
     // For every zombie add sqrt(distance from this point to zombie)
     var distancesRoot = 0;
     // Loop over the zombies
     var zombieCoords; 
     for(var i=0; i<zombies_length; i++) {
       // Get single zombie coordinates as {x:0, y:0}
       if((coords=zombies[i].pixel)==null)
         coords = zombies[i].pixel = tileToPixel(zombies[i].coordinates[0], zombies[i].coordinates[1], drawer);
       // square root is a) slow and b) probably not what I want anyway
       var dist = distanceSquared(coords, point);
       distancesRoot+=dist; 
     }
     // The higher the sum of distances is, the more intensive should the color be
     var style = 'rgba(255,0,0,'+300000000/distancesRoot+')';
     // Kill the console immediatelly
     //console.log(style);
     // Maybe we should sample and cache the transparency styles since there's limited ammount of colors?
     ctx.fillStyle = style;
     ctx.fillRect(x-halfRadius,y-halfRadius,tileSize,tileSize);
  } 
}

我对理论解释如何做得很好,不过如果你用一些点制作简单的画布示例,那将是多么棒的。

1 个答案:

答案 0 :(得分:2)

这是热图的一个例子。它基本上是在点上的渐变球,然后通过热斜坡使不透明度增加。轨道聚集在一起的颜色越多,颜色就越稳固,可以显示为具有适当斜坡的放大区域。

更新

我稍微清理了一些变量并将这些变量置于动画循环中。有一个fps计数器可以看到它的表现如何。渐变圆可能很昂贵。如果我们缩小热图,我们可能会做更大的世界。它看起来不会很流畅,但计算速度会快得多。

更新2

热图现在具有可调节的比例,正如预测的那样,我们得到了fps的增加。

if (typeof app === "undefined") {
  var app = {};
}

app.zeeks = 200;
app.w = 600;
app.h = 400;
app.circleSize = 50;
app.scale = 0.25;

init();

function init() {
  app.can = document.getElementById('can');
  app.ctx = can.getContext('2d');
  app.can.height = app.h;
  app.can.width = app.w;
  app.radius = Math.floor(app.circleSize / 2);
  
  app.z = genZ(app.zeeks, app.w, app.h);
  app.flip = false;

  // Make temporary layer once.
  app.layer = document.createElement('canvas');
  app.layerCtx = app.layer.getContext('2d');
  app.layer.width = Math.floor(app.w * app.scale);
  app.layer.height = Math.floor(app.h * app.scale);

  // Make the gradient canvas once.
  var sCircle = Math.floor(app.circleSize * app.scale);
  app.radius = Math.floor(sCircle / 2);
  app.gCan = genGradientCircle(sCircle);
  app.ramp = genRamp();
  
  // fps counter
  app.frames = 0;
  app.fps = "- fps";
  app.fpsInterval = setInterval(calcFps, 1000);
  
  // start animation
  ani();
  flicker();
}

function calcFps() {
  app.fps = app.frames + " fps";
  app.frames = 0;
}

// animation loop
function ani() {
  app.frames++;
  var ctx = app.ctx;
  var w = app.w;
  var h = app.h;
  moveZ();
  //ctx.clearRect(0, 0, w, h);
  ctx.fillStyle = "#006600";
  ctx.fillRect(0, 0, w, h);
  if (app.flip) {
    drawZ2();
    drawZ();
  } else {
    drawZ2();
  }
  ctx.fillStyle = "#FFFF00";
  ctx.fillText(app.fps, 10, 10);
  requestAnimationFrame(ani);
}

function flicker() {
  
  app.flip = !app.flip;
  if (app.flip) {
    setTimeout(flicker, 500);
  } else {
    setTimeout(flicker, 5000);
  }
  
}

function genGradientCircle(size) {
  // gradient image
  var gCan = document.createElement('canvas');
  gCan.width = gCan.height = size;
  var gCtx = gCan.getContext('2d');
  var radius = Math.floor(size / 2);
  var grad = gCtx.createRadialGradient(radius, radius, radius, radius, radius, 0);
  grad.addColorStop(1, "rgba(255,255,255,.65)");
  grad.addColorStop(0, "rgba(255,255,255,0)");
  gCtx.fillStyle = grad;
  gCtx.fillRect(0, 0, gCan.width, gCan.height);
  return gCan;
}

function genRamp() {
  // Create heat gradient
  var heat = document.createElement('canvas');
  var hCtx = heat.getContext('2d');
  heat.width = 256;
  heat.height = 5;
  var linGrad = hCtx.createLinearGradient(0, 0, heat.width, heat.height);
  linGrad.addColorStop(1, "rgba(255,0,0,.75)");
  linGrad.addColorStop(0.5, "rgba(255,255,0,.03)");
  linGrad.addColorStop(0, "rgba(255,255,0,0)");
  hCtx.fillStyle = linGrad;
  hCtx.fillRect(0, 0, heat.width, heat.height);

  // create ramp from gradient
  var ramp = [];
  var imageData = hCtx.getImageData(0, 0, heat.width, 1);
  var d = imageData.data;
  for (var x = 0; x < heat.width; x++) {
    var i = x * 4;
    ramp[x] = [d[i], d[i + 1], d[i + 2], d[i + 3]];
  }

  return ramp;
}

function genZ(n, w, h) {
  var a = [];
  for (var i = 0; i < n; i++) {
    a[i] = [
      Math.floor(Math.random() * w),
      Math.floor(Math.random() * h),
      Math.floor(Math.random() * 3) - 1,
      Math.floor(Math.random() * 3) - 1
    ];
  }
  return a;
}

function moveZ() {
  var w = app.w
  var h = app.h;
  var z = app.z;
  for (var i = 0; i < z.length; i++) {
    var s = z[i];
    s[0] += s[2];
    s[1] += s[3];
    if (s[0] > w || s[0] < 0) s[2] *= -1;
    if (s[1] > w || s[1] < 0) s[3] *= -1;
  }
}

function drawZ() {
  var ctx = app.ctx;
  var z = app.z;
  ctx.fillStyle = "#FFFF00";
  for (var i = 0; i < z.length; i++) {
    ctx.fillRect(z[i][0] - 2, z[i][1] - 2, 4, 4);
  }
}

function drawZ2() {
  var ctx = app.ctx;
  var layer = app.layer;
  var layerCtx = app.layerCtx;
  var gCan = app.gCan;
  var z = app.z;
  var radius = app.radius;
  
  // render gradients at coords onto layer
  for (var i = 0; i < z.length; i++) {
    var x = Math.floor((z[i][0] * app.scale) - radius);
    var y = Math.floor((z[i][1] * app.scale) - radius);
    layerCtx.drawImage(gCan, x, y);
  }

  // adjust layer for heat ramp
  var ramp = app.ramp;

  // apply ramp to layer
  var imageData = layerCtx.getImageData(0, 0, layer.width, layer.height);

  d = imageData.data;
  for (var i = 0; i < d.length; i += 4) {
    if (d[i + 3] != 0) {
      var c = ramp[d[i + 3]];
      d[i] = c[0];
      d[i + 1] = c[1];
      d[i + 2] = c[2];
      d[i + 3] = c[3];
    }
  }

  layerCtx.putImageData(imageData, 0, 0);

  // draw layer on world
  ctx.drawImage(layer, 0, 0, layer.width, layer.height, 0, 0, app.w, app.h);
}
<canvas id="can" width="600" height="400"></canvas>