2016-02-24 4 views
0

Я сделать карту, которая делает положение игровых объектов (Project Zomboid зомби):Создать цветовой градиент, описывающий дискретное распределение точек

zombies

Как пользователь отъезжает, одиночные точки больше не полезны. Вместо этого я хотел бы распределить зомби в области с использованием градиента красного цвета. Я попытался зациклиться на всех зомби для каждого визуализированного пикселя и покрасить его в ответ на сумму квадратов расстояний до зомби. Результат:

image description

Это слишком размыто. Кроме того, на результаты больше влияют зомби, которые являются AWAY из точек - мне нужно больше влиять на них зомби, которые закрыты. Так что это просто математика. Вот код, который я использовал:

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); 
    } 
} 

Я довольно хорошо с теоретическим объяснением, как это сделать, но если вы сделаете простой пример холста с некоторыми точками, что было бы удивительным.

+0

Если вы могли бы построить эту базу скрипку, я думаю, мало кто из нас ответил бы сейчас. Интересно, будет ли просто радиальный градиент, скажем, 0,6 globalAlpha, уже небезопасным. (используя ctx.scale перед рисованием дуг). – GameAlchemist

+1

Ваше описание звучит как [heatmap] (http://www.patrick-wied.at/static/heatmapjs/). – markE

ответ

2

Это пример тепловой карты. Это в основном градиентные шары над точками, а затем наращивание непрозрачности через тепловую рампу. Чем больше шар объединено, тем более твердый цвет, который может быть показан как усиленная область с соответствующим рампой.

обновление

я вымыл переменные немного и поставить zeeks в цикле анимации. Есть счетчик fps, чтобы посмотреть, как он работает. Круги градиента могут быть дорогими. Мы могли бы, возможно, сделать большие миры, если мы уменьшим карту тепла. Это будет не так гладко, но будет вычисляться намного быстрее.

обновление 2

Теплота карта теперь имеет регулируемую шкалу и как предсказывал мы получаем увеличение кадров в секунду.

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>

+0

Переменные режимы показывают приятную работу, которую вы сделали с тепловой картой. :-) – markE

+0

Ничего себе, это самый крутой ответ, который я когда-либо получал! Если у вас есть время, не могли бы вы также добавить объяснение? Я имею в виду ту часть, где вы создаете градиент. Это кажется во много раз более эффективным, чем все, о чем я думал. –

+0

Хорошо, на секунду подумал, что на самом деле это очень плохо с производительностью. Я создал тестовую скрипту, где вы можете добавить очки, нажав: https://jsfiddle.net/Darker/kfx117xv/6/ Холст, используемый на карте, еще больше. Скорее всего, ваше решение перестает исключать из памяти исключения. –