Prosedürel olarak oluşturulmuş bir dünya parçasını başka bir dünya grubuyla eşleştirme


18

Roger Zelazny'nin The Chronicles of Amber'i okudun mu?

Kendinizi 3. şahıs MMO oyununda oynadığınızı hayal edin. Dünyada yumurtlarsınız ve dolaşmaya başlarsınız. Bir süre sonra haritayı öğrendiğinizi, daha önce hiç görmediğiniz bir yerde olduğunuzu fark edersiniz. Bildiğinizden emin olduğunuz son yere geri dönüyorsunuz ve hala orada. Ancak dünyanın geri kalanı değişti ve nasıl olduğunu bile fark etmediniz.

Prosedürel dünya kuşağı hakkında okudum. Perlin gürültüsü ve oktavları, Simpleks gürültüsü, Elmas-kare algoritması, tektonik plakaların simülasyonu ve su erozyonu hakkında okudum. Prosedürel dünya kuşağında genel yaklaşım hakkında belirsiz bir anlayışım olduğuna inanıyorum.

Ve bu bilgiyle yukarıda yazılı gibi bir şeyi nasıl yapabileceğiniz hakkında hiçbir fikrim yok. Aklıma gelen her fikir bazı teorik problemlerle karşılaşır. Aklıma gelen bazı fikirler:

1) Giriş olarak bir tohum numarası ve bazı tam olarak tanımlayan bir yığın numarası ile "Tersinir" dünya üretimi

Bunun bile mümkün olduğundan şüpheliyim, ancak bir tohum alacak ve parçaların üzerine inşa edildiği bir sayı matrisi üretecek bir fonksiyon hayal ediyorum. Ve her benzersiz numara için benzersiz bir yığın var. Ve bu eşsiz yığın numarasını alan ve bu sayıyı içeren bir tohum üreten ikinci bir fonksiyon. Aşağıdaki resimde bir şema yapmaya çalıştım:

resim açıklamasını buraya girin

2) Parçaları tamamen rastgele yapmak ve aralarında geçiş yapmak.

As Aracthor önerdi. Bu yaklaşımın faydaları mümkün ve sihirli bir işlev gerektirmiyor :)

Bence bu yaklaşımın sahip olduğu eksiler, farklı bir dünyaya sahip olmanın mümkün olmamasıdır. Eğer hem takımadaları hem de sadece bir sayı ile temsil edilen bir kıtayı ve bitişik parçalar olduğunu varsayalım, o zaman bir yığının boyutu kıtaya eşit olmaz. Ve parçalar arasında iyi görünümlü bir geçiş yapılabileceğinden şüpheliyim. Bir şey mi kaçırıyorum?

Başka bir deyişle, prosedürel olarak oluşturulmuş bir dünya ile bir MMO geliştiriyorsunuz. Ama tek bir dünyaya sahip olmak yerine, birçok dünyaya sahipsiniz . Dünyalar yaratmak için hangi yaklaşımı benimsersiniz ve oyuncu geçişi fark etmeden oyuncunun bir dünyadan diğerine geçişini nasıl uygularsınız?

Her neyse, genel fikre sahip olduğuna inanıyorum. Bunu nasıl yapardın?


Buradaki cevaplarla ilgili bazı problemlerim var. @Aracthor Sizinle daha önce düzgün manifoldlar hakkında konuşmuştum, burada bu tür bir uygulama var. Ancak 2 oldukça yüksek cevap var, bu yüzden bir nokta olup olmadığını merak ediyorum ...
Alec Teal

@AlecTeal eklemek için bir şey varsa lütfen yapın. Herhangi bir fikir ve öneri duymaktan memnuniyet duyarım.
netaholic

Yanıtlar:


23

Bir üst düzey parazit kullanın. Daha önce bir yükseklik haritası için 2d parazit kullandıysanız, bunun yerine son koordinat sabitken 3D paraziti kullanın. Artık araziyi değiştirmek için son boyuttaki konumu yavaşça değiştirebilirsiniz. Perlin gürültüsü tüm boyutlarda sürekli olduğundan, gürültü işlevini örneklediğiniz konumu sorunsuz bir şekilde değiştirdiğiniz sürece yumuşak geçişler alırsınız.

Araziyi yalnızca oynatıcıya olan uzaklıktan çok ofset olarak değiştirmek istiyorsanız. Ayrıca, her koordinat için ofseti haritada saklayabilir ve yalnızca artırabilir, ancak asla azaltamazsınız. Bu şekilde harita sadece daha yeni olur ama asla yaşlanmaz.

Bu fikir, zaten 3D parazit kullanıyorsanız da işe yarıyor, sadece 4D'den örnek alın. Ayrıca Simplex gürültüsüne de bir göz atın. Perlin gürültüsünün geliştirilmiş versiyonudur ve daha fazla boyut için daha iyi çalışır.


2
Bu ilginç. 3 boyutlu bir gürültü oluşturmanızı, belirli bir z'de bir yükseklik haritası olarak bir xy dilimi kullanmanızı ve oynatıcıdan uzaklığı arttıkça z koordinatını değiştirerek başka bir dilime yumuşak bir geçiş yapmayı önerdiğinizi doğru anladım mı?
netaholic

@netaholic Kesinlikle. Bir dilim olarak tanımlamak çok iyi bir sezgidir. Ayrıca, haritanın her yerinde son koordinat için en yüksek değeri takip edebilir ve yalnızca artırabilir, ancak asla düşüremezsiniz.
danijar

1
Bu parlak bir fikir. Temel olarak, arazi haritanız bir 3B cilt boyunca parabolik (veya başka bir eğri) dilim olacaktır.
Sahte Ad

Bu gerçekten zekice bir fikir.
user253751

5

Dünyayı birkaç parçaya bölme fikriniz kötü değil. Sadece eksik.

Tek sorun, parçalar arasındaki bağlantılardır. Örneğin, rahatlama sağlamak için perlin gürültüsü ve her yığın için farklı bir tohum kullanırsanız ve bunun gerçekleşme riski varsa:

Yığın kabartma hatası

Bir çözüm, sadece Perlin gürültü tohumundan değil, etrafındaki diğer parçalardan da yığın rahatlaması sağlamak olacaktır.

Perlin algoritması, kendilerini "düzleştirmek" için etraflarındaki rastgele haritanın değerlerini kullanır. Eğer ortak bir harita kullanırlarsa, birlikte yumuşatılır.

Tek sorun, oyuncu geri çekildiğinde bir yığın tohumunu farklı yapmak için değiştirirseniz, parçaları da yeniden yüklemeniz gerekir, çünkü sınırları da değişmelidir.

Bu, parçaların boyutunu değiştirmez, ancak oynatıcıdan yüklenmekte / boşaltılmak için minimum mesafeyi artıracaktır, çünkü oyuncu onu gördüğünde bir yığın yüklenmeli ve bu yöntemle bitişik parçalar da olmalı .

GÜNCELLEME:

Dünyanızın her bir parçası farklı tipteyse, sorun büyür. Bu sadece rahatlama ile ilgili değil. Pahalı bir çözüm şu olabilir:

Parçalar kesilmiş

Diyelim ki yeşil parçalar orman dünyaları, mavi olanlar takımadalar ve sarı olanlar düz çöller.
Buradaki çözüm, rahatlama ve zemin doğanızın (yanı sıra topraklanmış nesnelerin veya istediğiniz herhangi bir şeyin) kademeli olarak bir türden diğerine dönüşeceği "geçiş" bölgeleri oluşturmaktır.

Ve bu resimde görebileceğiniz gibi, kodlanacak cehennem kısmı, yığın köşelerde küçük kareler olurdu: 4 parça arasında, potansiyel olarak farklı doğalarda bir bağlantı kurmaları gerekiyor.

Yani bu karmaşıklık düzeyi için, ben Perlin2D gibi klasik 2D dünya nesilleri sadece düşün edemez kullanılabilir. Seni bunun için @danijar cevabına yönlendiriyorum.


Bir tohumdan bir "merkez" oluşturmayı ve bitişik parçalar temelinde kenarlarını "düzleştirmeyi" önerir misiniz? Bu bir anlam ifade eder, ancak bir yığının boyutunu artıracaktır, çünkü bir alanın büyüklüğü olması gerekir, bu oyuncu bir geçiş alanının genişliğini bitişik yığınlara göre artırabilir. Ve yığın alanı, dünya ne kadar çeşitli olursa o kadar büyür.
netaholic

@netaholic Daha büyük olmazdı ama bir nevi. Üzerine bir paragraf ekledim.
Aracthor

Sorumu güncelledim. Sahip olduğum bazı fikirleri anlatmaya çalıştım
netaholic

Yani buradaki diğer cevap, grafik olarak üçüncü bir boyutu kullanıyor (biraz değil). Ayrıca uçağı bir manifold olarak görüyorsunuz ve fikirlerinizi seviyorum. Biraz daha genişletmek için gerçekten pürüzsüz bir manifold istiyorsunuz. Geçişlerinizin sorunsuz olduğundan emin olmanız gerekir. Daha sonra buna bir bulanıklık veya gürültü uygulayabilirsiniz ve cevap mükemmel olacaktır.
Alec Teal

0

Danijar'ın fikri oldukça sağlam olsa da, yerel alanın aynı olmasını ve mesafe kaymasını istiyorsanız, çok fazla veri depolayabilirsiniz. Ve daha fazla ve daha karmaşık gürültü dilimlerini talep. Tüm bunları daha standart bir 2d tarzında elde edebilirsiniz.

Kısmen hem sonsuz hem de deterministik olarak sabitlediğim elmas kare algoritmasına dayanarak , prosedürel olarak rastgele fraktal gürültü üretmek için bir algoritma geliştirdim . Elmas kare sonsuz bir manzara ve kendi oldukça bloklu algoritmamı yaratabilir.

Fikir temelde aynı. Ancak, yüksek boyutlu gürültüyü örneklemek yerine, değerleri farklı yinelemeli seviyelerde yineleyebilirsiniz.

Böylece, daha önce istediğiniz değerleri saklar ve önbelleğe alırsınız (bu şema, zaten süper hızlı bir algoritmayı hızlandırmak için bağımsız olarak kullanılabilir). Ve yeni alan istendiğinde, yeni bir y değeri ile oluşturulur. ve bu istekte istenmeyen tüm alanlar kaldırılır.

Bu yüzden ek boyutlarda farklı alanlardan geçmek yerine. Farklı miktarlarda (farklı seviyelerde giderek daha büyük miktarlarda) karıştırmak için ekstra bir monotonik veri depolarız.

Kullanıcı bir yönde hareket ederse, değerler buna göre (ve her seviyede) taşınır ve yeni kenarlarda yeni değerler üretilir. En üst yinelemeli tohum değiştirilirse, tüm dünya büyük ölçüde değişecektir. Son yinelemeye farklı bir sonuç verilirse, değişiklik miktarı çok küçük + -1 blok olacaktır. Ancak, tepe hala orada olacak ve vadi vb, ancak köşe ve çatlaklar değişmiş olacak. Eğer yeterince ileri gitmedikçe, tepe yok olacak.

Yani her yinelemede 100x100 değer yığını depolarsak. Sonra 100x100'de oyuncudan hiçbir şey değişemezdi. Ancak, 200x200'de işler 1 blok değişebilir. 400x400'de işler 2 blok değişebilir. 800x800 uzakta her şey 4 blokla değişebilir. Böylece işler değişecek ve gittikçe daha fazla değişecekler. Eğer geri giderseniz onlar farklı olacak, eğer çok ileri giderseniz tüm tohumlar terk edileceği için tamamen değiştirilecek ve tamamen kaybolacaklar.

Bu dengeleyici efekti sağlamak için farklı bir boyut eklemek kesinlikle işe yarar, y'yi uzaktan kaydırır, ancak gerekmediğinde çok fazla blok için çok fazla veri depolarsınız. Deterministik fraktal gürültü algoritmalarında, konum belirli bir noktanın ötesine geçerken değişen bir değer (farklı bir miktarda) ekleyerek aynı etkiyi elde edebilirsiniz.

https://jsfiddle.net/rkdzau7o/

var SCALE_FACTOR = 2;
//The scale factor is kind of arbitrary, but the code is only consistent for 2 currently. Gives noise for other scale but not location proper.
var BLUR_EDGE = 2; //extra pixels are needed for the blur (3 - 1).
var buildbuffer = BLUR_EDGE + SCALE_FACTOR;

canvas = document.getElementById('canvas');
ctx = canvas.getContext("2d");
var stride = canvas.width + buildbuffer;
var colorvalues = new Array(stride * (canvas.height + buildbuffer));
var iterations = 7;
var xpos = 0;
var ypos = 0;
var singlecolor = true;


/**
 * Function adds all the required ints into the ints array.
 * Note that the scanline should not actually equal the width.
 * It should be larger as per the getRequiredDim function.
 *
 * @param iterations Number of iterations to perform.
 * @param ints       pixel array to be used to insert values. (Pass by reference)
 * @param stride     distance in the array to the next requestedY value.
 * @param x          requested X location.
 * @param y          requested Y location.
 * @param width      width of the image.
 * @param height     height of the image.
 */

function fieldOlsenNoise(iterations, ints, stride, x, y, width, height) {
  olsennoise(ints, stride, x, y, width, height, iterations); //Calls the main routine.
  //applyMask(ints, stride, width, height, 0xFF000000);
}

function applyMask(pixels, stride, width, height, mask) {
  var index;
  index = 0;
  for (var k = 0, n = height - 1; k <= n; k++, index += stride) {
    for (var j = 0, m = width - 1; j <= m; j++) {
      pixels[index + j] |= mask;
    }
  }
}

/**
 * Converts a dimension into the dimension required by the algorithm.
 * Due to the blurring, to get valid data the array must be slightly larger.
 * Due to the interpixel location at lowest levels it needs to be bigger by
 * the max value that can be. (SCALE_FACTOR)
 *
 * @param dim
 * @return
 */

function getRequiredDim(dim) {
  return dim + BLUR_EDGE + SCALE_FACTOR;
}

//Function inserts the values into the given ints array (pass by reference)
//The results will be within 0-255 assuming the requested iterations are 7.
function olsennoise(ints, stride, x_within_field, y_within_field, width, height, iteration) {
  if (iteration == 0) {
    //Base case. If we are at the bottom. Do not run the rest of the function. Return random values.
    clearValues(ints, stride, width, height); //base case needs zero, apply Noise will not eat garbage.
    applyNoise(ints, stride, x_within_field, y_within_field, width, height, iteration);
    return;
  }

  var x_remainder = x_within_field & 1; //Adjust the x_remainder so we know how much more into the pixel are.
  var y_remainder = y_within_field & 1; //Math.abs(y_within_field % SCALE_FACTOR) - Would be assumed for larger scalefactors.

  /*
  Pass the ints, and the stride for that set of ints.
  Recurse the call to the function moving the x_within_field forward if we actaully want half a pixel at the start.
  Same for the requestedY.
  The width should expanded by the x_remainder, and then half the size, with enough extra to store the extra ints from the blur.
  If the width is too long, it'll just run more stuff than it needs to.
  */

  olsennoise(ints, stride,
    (Math.floor((x_within_field + x_remainder) / SCALE_FACTOR)) - x_remainder,
    (Math.floor((y_within_field + y_remainder) / SCALE_FACTOR)) - y_remainder,
    (Math.floor((width + x_remainder) / SCALE_FACTOR)) + BLUR_EDGE,
    (Math.floor((height + y_remainder) / SCALE_FACTOR)) + BLUR_EDGE, iteration - 1);

  //This will scale the image from half the width and half the height. bounds.
  //The scale function assumes you have at least width/2 and height/2 good ints.
  //We requested those from olsennoise above, so we should have that.

  applyScaleShift(ints, stride, width + BLUR_EDGE, height + BLUR_EDGE, SCALE_FACTOR, x_remainder, y_remainder);

  //This applies the blur and uses the given bounds.
  //Since the blur loses two at the edge, this will result
  //in us having width requestedX height of good ints and required
  // width + blurEdge of good ints. height + blurEdge of good ints.
  applyBlur(ints, stride, width + BLUR_EDGE, height + BLUR_EDGE);

  //Applies noise to all the given ints. Does not require more or less than ints. Just offsets them all randomly.
  applyNoise(ints, stride, x_within_field, y_within_field, width, height, iteration);
}



function applyNoise(pixels, stride, x_within_field, y_within_field, width, height, iteration) {
  var bitmask = 0b00000001000000010000000100000001 << (7 - iteration);
  var index = 0;
  for (var k = 0, n = height - 1; k <= n; k++, index += stride) { //iterate the requestedY positions. Offsetting the index by stride each time.
    for (var j = 0, m = width - 1; j <= m; j++) { //iterate the requestedX positions through width.
      var current = index + j; // The current position of the pixel is the index which will have added stride each, requestedY iteration
      pixels[current] += hashrandom(j + x_within_field, k + y_within_field, iteration) & bitmask;
      //add on to this pixel the hash function with the set reduction.
      //It simply must scale down with the larger number of iterations.
    }
  }
}

function applyScaleShift(pixels, stride, width, height, factor, shiftX, shiftY) {
  var index = (height - 1) * stride; //We must iteration backwards to scale so index starts at last Y position.
  for (var k = 0, n = height - 1; k <= n; n--, index -= stride) { // we iterate the requestedY, removing stride from index.
    for (var j = 0, m = width - 1; j <= m; m--) { // iterate the requestedX positions from width to 0.
      var pos = index + m; //current position is the index (position of that scanline of Y) plus our current iteration in scale.
      var lower = (Math.floor((n + shiftY) / factor) * stride) + Math.floor((m + shiftX) / factor); //We find the position that is half that size. From where we scale them out.
      pixels[pos] = pixels[lower]; // Set the outer position to the inner position. Applying the scale.
    }
  }
}

function clearValues(pixels, stride, width, height) {
  var index;
  index = 0;
  for (var k = 0, n = height - 1; k <= n; k++, index += stride) { //iterate the requestedY values.
    for (var j = 0, m = width - 1; j <= m; j++) { //iterate the requestedX values.
      pixels[index + j] = 0; //clears those values.
    }
  }
}

//Applies the blur.
//loopunrolled box blur 3x3 in each color.
function applyBlur(pixels, stride, width, height) {
  var index = 0;
  var v0;
  var v1;
  var v2;

  var r;
  var g;
  var b;

  for (var j = 0; j < height; j++, index += stride) {
    for (var k = 0; k < width; k++) {
      var pos = index + k;

      v0 = pixels[pos];
      v1 = pixels[pos + 1];
      v2 = pixels[pos + 2];

      r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
      g = ((v0 >> 8) & 0xFF) + ((v1 >> 8) & 0xFF) + ((v2 >> 8) & 0xFF);
      b = ((v0) & 0xFF) + ((v1) & 0xFF) + ((v2) & 0xFF);
      r = Math.floor(r / 3);
      g = Math.floor(g / 3);
      b = Math.floor(b / 3);
      pixels[pos] = r << 16 | g << 8 | b;
    }
  }
  index = 0;
  for (var j = 0; j < height; j++, index += stride) {
    for (var k = 0; k < width; k++) {
      var pos = index + k;
      v0 = pixels[pos];
      v1 = pixels[pos + stride];
      v2 = pixels[pos + (stride << 1)];

      r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
      g = ((v0 >> 8) & 0xFF) + ((v1 >> 8) & 0xFF) + ((v2 >> 8) & 0xFF);
      b = ((v0) & 0xFF) + ((v1) & 0xFF) + ((v2) & 0xFF);
      r = Math.floor(r / 3);
      g = Math.floor(g / 3);
      b = Math.floor(b / 3);
      pixels[pos] = r << 16 | g << 8 | b;
    }
  }
}


function hashrandom(v0, v1, v2) {
  var hash = 0;
  hash ^= v0;
  hash = hashsingle(hash);
  hash ^= v1;
  hash = hashsingle(hash);
  hash ^= v2;
  hash = hashsingle(hash);
  return hash;
}

function hashsingle(v) {
  var hash = v;
  var h = hash;

  switch (hash & 3) {
    case 3:
      hash += h;
      hash ^= hash << 32;
      hash ^= h << 36;
      hash += hash >> 22;
      break;
    case 2:
      hash += h;
      hash ^= hash << 22;
      hash += hash >> 34;
      break;
    case 1:
      hash += h;
      hash ^= hash << 20;
      hash += hash >> 2;
  }
  hash ^= hash << 6;
  hash += hash >> 10;
  hash ^= hash << 8;
  hash += hash >> 34;
  hash ^= hash << 50;
  hash += hash >> 12;
  return hash;
}


//END, OLSEN NOSE.



//Nuts and bolts code.

function MoveMap(dx, dy) {
  xpos -= dx;
  ypos -= dy;
  drawMap();
}

function drawMap() {
  //int iterations, int[] ints, int stride, int x, int y, int width, int height
  console.log("Here.");
  fieldOlsenNoise(iterations, colorvalues, stride, xpos, ypos, canvas.width, canvas.height);
  var img = ctx.createImageData(canvas.width, canvas.height);

  for (var y = 0, h = canvas.height; y < h; y++) {
    for (var x = 0, w = canvas.width; x < w; x++) {
      var standardShade = colorvalues[(y * stride) + x];
      var pData = ((y * w) + x) * 4;
      if (singlecolor) {
        img.data[pData] = standardShade & 0xFF;
        img.data[pData + 1] = standardShade & 0xFF;
        img.data[pData + 2] = standardShade & 0xFF;
      } else {
        img.data[pData] = standardShade & 0xFF;
        img.data[pData + 1] = (standardShade >> 8) & 0xFF;
        img.data[pData + 2] = (standardShade >> 16) & 0xFF;
      }
      img.data[pData + 3] = 255;
    }
  }
  ctx.putImageData(img, 0, 0);
}

$("#update").click(function(e) {
  iterations = parseInt($("iterations").val());
  drawMap();
})
$("#colors").click(function(e) {
  singlecolor = !singlecolor;
  drawMap();
})

var m = this;
m.map = document.getElementById("canvas");
m.width = canvas.width;
m.height = canvas.height;

m.hoverCursor = "auto";
m.dragCursor = "url(data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAcABQAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAA/AAAAfwAAAP+AAAH/gAAB/8AAAH/AAAB/wAAA/0AAANsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////gH///4B///8Af//+AD///AA///wAH//+AB///wAf//4AH//+AD///yT/////////////////////////////8=), default";
m.scrollTime = 300;

m.mousePosition = new Coordinate;
m.mouseLocations = [];
m.velocity = new Coordinate;
m.mouseDown = false;
m.timerId = -1;
m.timerCount = 0;

m.viewingBox = document.createElement("div");
m.viewingBox.style.cursor = m.hoverCursor;

m.map.parentNode.replaceChild(m.viewingBox, m.map);
m.viewingBox.appendChild(m.map);
m.viewingBox.style.overflow = "hidden";
m.viewingBox.style.width = m.width + "px";
m.viewingBox.style.height = m.height + "px";
m.viewingBox.style.position = "relative";
m.map.style.position = "absolute";

function AddListener(element, event, f) {
  if (element.attachEvent) {
    element["e" + event + f] = f;
    element[event + f] = function() {
      element["e" + event + f](window.event);
    };
    element.attachEvent("on" + event, element[event + f]);
  } else
    element.addEventListener(event, f, false);
}

function Coordinate(startX, startY) {
  this.x = startX;
  this.y = startY;
}

var MouseMove = function(b) {
  var e = b.clientX - m.mousePosition.x;
  var d = b.clientY - m.mousePosition.y;
  MoveMap(e, d);
  m.mousePosition.x = b.clientX;
  m.mousePosition.y = b.clientY;
};

/**
 * mousedown event handler
 */
AddListener(m.viewingBox, "mousedown", function(e) {
  m.viewingBox.style.cursor = m.dragCursor;

  // Save the current mouse position so we can later find how far the
  // mouse has moved in order to scroll that distance
  m.mousePosition.x = e.clientX;
  m.mousePosition.y = e.clientY;

  // Start paying attention to when the mouse moves
  AddListener(document, "mousemove", MouseMove);
  m.mouseDown = true;

  event.preventDefault ? event.preventDefault() : event.returnValue = false;
});

/**
 * mouseup event handler
 */
AddListener(document, "mouseup", function() {
  if (m.mouseDown) {
    var handler = MouseMove;
    if (document.detachEvent) {
      document.detachEvent("onmousemove", document["mousemove" + handler]);
      document["mousemove" + handler] = null;
    } else {
      document.removeEventListener("mousemove", handler, false);
    }

    m.mouseDown = false;

    if (m.mouseLocations.length > 0) {
      var clickCount = m.mouseLocations.length;
      m.velocity.x = (m.mouseLocations[clickCount - 1].x - m.mouseLocations[0].x) / clickCount;
      m.velocity.y = (m.mouseLocations[clickCount - 1].y - m.mouseLocations[0].y) / clickCount;
      m.mouseLocations.length = 0;
    }
  }

  m.viewingBox.style.cursor = m.hoverCursor;
});

drawMap();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="500" height="500">
</canvas>
<fieldset>
  <legend>Height Map Properties</legend>
  <input type="text" name="iterations" id="iterations">
  <label for="iterations">
    Iterations(7)
  </label>
  <label>
    <input type="checkbox" id="colors" />Rainbow</label>
</fieldset>

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.