Ghép một đoạn của thế giới được tạo theo thủ tục với một đoạn của thế giới khác


18

Bạn đã đọc The Chronicles of Amber của Roger Zelazny chưa?

Hãy tưởng tượng bạn đang chơi trong trò chơi MMO người thứ 3. Bạn sinh sản trên thế giới và bắt đầu lang thang khắp nơi. Sau một thời gian, khi bạn nghĩ rằng bạn đã học bản đồ, bạn nhận ra rằng bạn đang ở một nơi mà bạn chưa từng thấy trước đây. Bạn quay trở lại nơi cuối cùng bạn chắc chắn bạn biết và nó vẫn còn đó. Nhưng phần còn lại của thế giới đã thay đổi và bạn thậm chí không nhận thấy điều đó đã xảy ra như thế nào.

Tôi đã đọc về thế hệ thủ tục. Tôi đã đọc về tiếng ồn và quãng tám Perlin, tiếng ồn Simplex, thuật toán hình vuông kim cương, về mô phỏng các mảng kiến ​​tạo và xói mòn nước. Tôi tin rằng tôi có một số hiểu biết mơ hồ về cách tiếp cận chung trong thế hệ thủ tục.

Và với kiến ​​thức này tôi không biết làm thế nào bạn có thể làm một cái gì đó như được viết ở trên. Mỗi ý tưởng xuất hiện trong đầu tôi gặp phải một số vấn đề lý thuyết. Dưới đây là một số ý tưởng tôi có thể nghĩ ra:

1) Thế hệ "có thể đảo ngược" với số hạt giống làm đầu vào và một số mô tả đầy đủ-a-chunk-number

Tôi nghi ngờ rằng nó thậm chí có thể, nhưng tôi tưởng tượng một hàm, nó sẽ nhận được một hạt giống và tạo ra một ma trận các số, trên đó các khối được xây dựng. Và đối với mỗi số duy nhất có một đoạn duy nhất. Và một hàm thứ hai, lấy số chunk duy nhất này và tạo ra một hạt giống, chứa số này. Tôi đã cố gắng thực hiện một sơ đồ trong hình dưới đây:

nhập mô tả hình ảnh ở đây

2) Tạo khối hoàn toàn ngẫu nhiên và thực hiện chuyển đổi giữa chúng.

Như Aracthor đề nghị. Lợi ích của phương pháp này là có thể và nó không yêu cầu phép thuật :)

Theo tôi, nhược điểm của phương pháp này là có khả năng không có một thế giới đa dạng. Nếu bạn có thể nói cả quần đảo và lục địa được đại diện bởi chỉ một số và đó là các khối liền kề, thì kích thước của một khối sẽ không bằng lục địa. Và tôi nghi ngờ rằng có thể thực hiện chuyển đổi đẹp mắt giữa các khối. Tui bỏ lỡ điều gì vậy?

Vì vậy, nói cách khác, bạn đang phát triển MMO với thế giới được tạo theo thủ tục. Nhưng thay vì có một thế giới, bạn có nhiều . Cách tiếp cận nào bạn sẽ thực hiện để tạo ra các thế giới và cách bạn thực hiện chuyển đổi người chơi từ thế giới này sang thế giới khác mà không cần người chơi nhận thấy quá trình chuyển đổi.

Dù sao, tôi tin rằng bạn đã có ý tưởng chung. Làm thế nào bạn sẽ làm điều đó?


Vì vậy, tôi có một số vấn đề với các câu trả lời ở đây. @Aracthor Tôi đã nói với bạn về đa tạp trơn tru trước đây, loại áp dụng ở đây. Tuy nhiên, có 2 câu trả lời khá cao nên tôi tự hỏi liệu có một điểm ...
Alec Teal

@AlecTeal nếu bạn có bất cứ điều gì để thêm xin vui lòng làm. Tôi rất vui khi nghe bất kỳ ý tưởng và đề xuất.
netaholic

Câu trả lời:


23

Sử dụng một lát tiếng ồn bậc cao. Nếu bạn đã sử dụng nhiễu 2d cho bản đồ chiều cao trước đó, hãy sử dụng nhiễu 3D với tọa độ cuối cùng được cố định. Bây giờ bạn có thể từ từ thay đổi vị trí trong chiều cuối cùng để sửa đổi địa hình. Vì tiếng ồn Perlin liên tục ở tất cả các chiều, bạn sẽ có được các chuyển tiếp mượt mà miễn là bạn thay đổi trơn tru vị trí mà bạn lấy mẫu chức năng tiếng ồn.

Ví dụ, nếu bạn muốn thay đổi địa hình từ khoảng cách đến người chơi làm phần bù. Bạn cũng có thể lưu trữ phần bù cho từng tọa độ trên bản đồ và chỉ tăng chứ không bao giờ giảm. Bằng cách này, bản đồ chỉ mới hơn nhưng không bao giờ cũ hơn.

Ý tưởng này cũng hoạt động nếu bạn đã sử dụng nhiễu 3D, chỉ cần lấy mẫu từ 4D. Ngoài ra, hãy xem tiếng ồn Simplex. Đây là phiên bản cải tiến của tiếng ồn Perlin và hoạt động tốt hơn cho nhiều kích thước hơn.


2
Hay đấy. Tôi có hiểu chính xác không, rằng bạn đề xuất tạo ra tiếng ồn 3d, sử dụng xy-lát ở một số z nhất định của nó làm sơ đồ chiều cao và thực hiện chuyển đổi suôn sẻ sang một lát cắt khác bằng cách thay đổi tọa độ z khi tăng khoảng cách từ trình phát?
netaholic

@netaholic Chính xác. Mô tả nó như một lát cắt là một trực giác rất tốt. Ngoài ra, bạn có thể theo dõi giá trị cao nhất cho tọa độ cuối cùng ở mọi nơi trên bản đồ và chỉ tăng nó chứ không bao giờ giảm.
danijar

1
Đây là một ý tưởng tuyệt vời. Về cơ bản, bản đồ địa hình của bạn sẽ là một lát parabol (hoặc đường cong khác) thông qua một khối 3D.
Tên giả

Đây là một ý tưởng thực sự thông minh.
dùng253751

5

Ý tưởng của bạn để chia thế giới thành nhiều phần không phải là xấu. Nó chỉ là không đầy đủ.

Vấn đề duy nhất là mối nối giữa các khối. Chẳng hạn, nếu bạn sử dụng tiếng ồn perlin để tạo ra sự giảm bớt và một hạt giống khác nhau cho mỗi đoạn và có nguy cơ điều này xảy ra:

Chunk cứu trợ lỗi

Một giải pháp sẽ là tạo ra sự giảm bớt khối không chỉ từ hạt nhiễu Perlin của nó, mà còn từ các khối khác xung quanh nó.

Thuật toán Perlin sử dụng các giá trị của bản đồ ngẫu nhiên xung quanh chúng để "làm mịn" chính chúng. Nếu họ sử dụng một bản đồ chung, sẽ được làm mịn với nhau.

Vấn đề duy nhất là nếu bạn thay đổi một hạt giống để tạo sự khác biệt khi người chơi rút lui, bạn cũng sẽ phải tải lại các khối xung quanh, bởi vì đường viền của chúng cũng sẽ thay đổi.

Điều này sẽ không thay đổi kích thước của các khối, nhưng nó sẽ tăng khoảng cách tối thiểu từ người chơi đến khi được tải / không tải, bởi vì một khối phải được tải khi người chơi nhìn thấy nó, và với phương pháp này, vì các khối liền kề cũng phải .

CẬP NHẬT:

Nếu mỗi phần của thế giới của bạn thuộc một loại khác nhau, vấn đề sẽ lớn lên. Đây không chỉ là về cứu trợ. Một giải pháp tốn kém sẽ là như sau:

Cắt thịt

Chúng ta hãy giả sử các khối màu xanh lá cây là thế giới rừng, quần đảo màu xanh và quần đảo màu vàng sa mạc phẳng.
Giải pháp ở đây là tạo ra các vùng "chuyển tiếp", trong đó tính chất phù điêu và mặt đất của bạn (cũng như các vật thể được nối đất hoặc bất cứ thứ gì bạn muốn) sẽ dần dần chuyển từ loại này sang loại khác.

Và như bạn có thể thấy trên bức tranh này, phần địa ngục để mã hóa sẽ là những ô vuông nhỏ ở các góc nhỏ: chúng phải tạo một liên kết giữa 4 khối, có khả năng khác nhau.

Vì vậy, đối với mức độ phức tạp này, tôi nghĩ rằng các thế hệ 2D cổ điển như Perlin2D không thể được sử dụng. Tôi giới thiệu bạn đến câu trả lời @danijar cho điều đó.


Bạn có đề xuất để tạo ra "trung tâm" của một khối từ hạt giống và các cạnh của nó được "làm mịn" dựa trên các khối liền kề không? Nó có ý nghĩa, nhưng nó sẽ tăng kích thước của một đoạn, vì nó phải là kích thước của một khu vực, người chơi có thể quan sát cộng với gấp đôi chiều rộng của một khu vực chuyển tiếp sang các đoạn liền kề. Và diện tích chunk càng trở nên lớn hơn, thế giới càng đa dạng.
netaholic

@netaholic Nó sẽ không lớn hơn, nhưng loại. Tôi đã thêm một đoạn trên đó.
Aracthor

Tôi đã cập nhật câu hỏi của mình. Đã cố gắng để mô tả một số ý tưởng tôi có
netaholic

Vì vậy, câu trả lời khác ở đây sử dụng (sắp xếp, không hoàn toàn) một chiều thứ ba làm biểu đồ. Ngoài ra, bạn cũng xem máy bay như một đa tạp, và tôi thích ý tưởng của bạn. Để mở rộng thêm một chút bạn thực sự muốn một đa tạp trơn tru. Bạn cần chắc chắn rằng quá trình chuyển đổi của bạn diễn ra suôn sẻ. Sau đó, bạn có thể áp dụng một mờ hoặc tiếng ồn cho điều này và câu trả lời sẽ là hoàn hảo.
Alec Teal

0

Mặc dù ý tưởng của danijar khá vững chắc, nhưng cuối cùng bạn có thể lưu trữ rất nhiều dữ liệu, nếu bạn muốn có cùng một khu vực địa phương và sự thay đổi khoảng cách. Và yêu cầu ngày càng nhiều lát tiếng ồn ngày càng phức tạp. Bạn có thể nhận được tất cả những thứ này theo kiểu 2d chuẩn hơn.

Tôi đã phát triển một thuật toán để tạo ra tiếng ồn fractal ngẫu nhiên, một phần dựa trên thuật toán hình vuông kim cương mà tôi đã cố định là cả vô hạn và xác định. Vì vậy, hình vuông kim cương có thể tạo ra một cảnh quan vô hạn, cũng như thuật toán khá khối của riêng tôi.

Ý tưởng về cơ bản là giống nhau. Nhưng, thay vì lấy mẫu nhiễu chiều cao hơn, bạn có thể lặp lại các giá trị ở các mức lặp khác nhau.

Vì vậy, bạn vẫn lưu trữ các giá trị bạn yêu cầu trước đó và lưu trữ chúng (lược đồ này có thể được sử dụng độc lập để tăng tốc thuật toán đã cực nhanh). Và khi khu vực mới được yêu cầu, nó được tạo với giá trị y mới. và bất kỳ khu vực không được yêu cầu trong yêu cầu đó được loại bỏ.

Vì vậy, thay vì đi qua không gian khác nhau trong một kích thước bổ sung. Chúng tôi lưu trữ thêm một chút dữ liệu đơn điệu để trộn lẫn với nhau (với số lượng lớn hơn dần dần ở các cấp độ khác nhau).

Nếu người dùng di chuyển theo hướng, các giá trị được di chuyển tương ứng (và ở mỗi cấp) và các giá trị mới được tạo ở các cạnh mới. Nếu hạt giống lặp đầu được thay đổi, toàn bộ thế giới sẽ bị thay đổi mạnh mẽ. Nếu lần lặp cuối cùng được đưa ra một kết quả khác, thì số tiền thay đổi sẽ rất nhỏ + -1 khối hoặc hơn. Nhưng, ngọn đồi vẫn sẽ ở đó và thung lũng, v.v., nhưng các ngõ ngách sẽ thay đổi. Trừ khi bạn đi đủ xa, và rồi ngọn đồi sẽ biến mất.

Vì vậy, nếu chúng ta lưu trữ 100x100 khối giá trị mỗi lần lặp. Sau đó, không có gì có thể thay đổi ở 100x100 từ người chơi. Nhưng, ở 200x200, mọi thứ có thể thay đổi 1 khối. Với 400x400, mọi thứ có thể thay đổi bởi 2 khối. Ở 800x800, mọi thứ sẽ có thể thay đổi bởi 4 khối. Vì vậy, mọi thứ sẽ thay đổi và họ sẽ thay đổi nhiều hơn và nhiều hơn nữa bạn đi. Nếu bạn quay trở lại họ sẽ khác, nếu bạn đi quá xa họ sẽ hoàn toàn thay đổi và mất hoàn toàn vì tất cả các hạt giống sẽ bị bỏ rơi.

Thêm một kích thước khác để cung cấp hiệu ứng ổn định này, chắc chắn sẽ hoạt động, thay đổi y ở khoảng cách xa, nhưng bạn sẽ lưu trữ rất nhiều dữ liệu cho rất nhiều khối khi bạn không cần phải làm. Trong các thuật toán nhiễu fractal xác định, bạn có thể có được hiệu ứng tương tự bằng cách thêm một giá trị thay đổi (ở một mức khác) khi vị trí di chuyển vượt quá một điểm nhất định.

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>

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.