จับคู่ชิ้นส่วนของโลกที่สร้างขึ้นแบบขั้นตอนกับส่วนอื่นของโลก


18

คุณได้อ่าน The Chronicles of Amber โดย Roger Zelazny แล้วหรือยัง?

ลองนึกภาพตัวเองเล่นในเกม MMO คนที่ 3 คุณวางไข่ในโลกและเริ่มเดินไปรอบ ๆ เมื่อเวลาผ่านไปเมื่อคุณคิดว่าคุณได้เรียนรู้แผนที่คุณรู้ว่าคุณอยู่ในสถานที่ที่คุณไม่เคยเห็นมาก่อน คุณกลับไปยังที่สุดท้ายที่คุณแน่ใจและรู้ว่ายังอยู่ที่นั่น แต่ส่วนที่เหลือของโลกมีการเปลี่ยนแปลงและคุณไม่ได้สังเกตว่ามันเกิดขึ้นได้อย่างไร

ฉันได้อ่านเกี่ยวกับการสร้างโลกขั้นตอน ฉันได้อ่านเกี่ยวกับเสียงเพอร์ลินและอ็อกเทฟเสียงรบกวน, อัลกอริธึมไดมอนด์สแควร์เกี่ยวกับการจำลองแผ่นเปลือกโลกและการกัดเซาะของน้ำ ฉันเชื่อว่าฉันมีความเข้าใจที่คลุมเครือของวิธีการทั่วไปในการสร้างโลกแบบขั้นตอน

และด้วยความรู้นี้ฉันไม่มีความคิดว่าคุณจะทำสิ่งที่เขียนไว้ด้านบนได้อย่างไร ทุกความคิดที่อยู่ในใจของฉันได้พบกับปัญหาทางทฤษฎีบางอย่าง นี่คือความคิดที่ฉันคิดได้:

1) "ย้อนกลับ" รุ่นโลกที่มีหมายเลขเมล็ดเป็นอินพุตและบางอย่างที่อธิบาย -a-chunk-number

ฉันสงสัยว่ามันเป็นไปได้ แต่ฉันจินตนาการถึงฟังก์ชั่นที่จะได้รับเมล็ดพันธุ์และสร้างเมทริกซ์ของตัวเลขซึ่งจะถูกสร้างเป็นชิ้น ๆ และสำหรับแต่ละหมายเลขที่ไม่ซ้ำกันจะมีอันที่ไม่ซ้ำกัน และฟังก์ชั่นที่สองที่ได้รับหมายเลขอันไม่ซ้ำกันนี้และสร้างเมล็ดพันธุ์ที่มีหมายเลขนี้ ฉันพยายามที่จะทำให้รูปแบบในภาพด้านล่าง:

ป้อนคำอธิบายรูปภาพที่นี่

2) การทำชิ้นสุ่มอย่างสมบูรณ์และทำการเปลี่ยนแปลงระหว่างพวกเขา

ตามที่แนะนำในAracthor ประโยชน์ของวิธีนี้คือมันเป็นไปได้และไม่ต้องใช้เวทมนตร์ fuction :)

ข้อเสียของวิธีการนี้มีอยู่ในความคิดของฉันคือว่าเป็นไปไม่ได้ที่จะมีโลกที่หลากหลาย ถ้าคุณสมมุติว่าทั้งหมู่เกาะและทวีปแสดงเพียงหนึ่งหมายเลขและมันเป็นส่วนที่อยู่ติดกันดังนั้นขนาดของก้อนจะไม่เท่ากับทวีป และฉันสงสัยว่ามันเป็นไปได้ที่จะทำให้การเปลี่ยนหน้าตาดีระหว่างชิ้น ฉันพลาดอะไรไปรึเปล่า?

คุณกำลังพัฒนา MMO กับโลกที่สร้างขึ้นแบบขั้นตอน แต่แทนที่จะมีโลกเดียวคุณมีมากมายจำนวนมากคุณจะใช้วิธีการใดในการสร้างโลกและคุณจะใช้การเปลี่ยนแปลงของผู้เล่นจากโลกหนึ่งไปยังอีกโลกหนึ่งได้อย่างไรโดยที่ผู้เล่นไม่สังเกตเห็นการเปลี่ยนแปลง

อย่างไรก็ตามฉันเชื่อว่าคุณมีความคิดทั่วไป คุณจะทำมันได้อย่างไร


ดังนั้นฉันจึงมีปัญหากับคำตอบที่นี่ @Arhor ฉันได้พูดคุยกับคุณเกี่ยวกับแมนิโฟลด์ที่ราบรื่นมาก่อนการใช้งานชนิดนี้ที่นี่ อย่างไรก็ตามมีคำตอบที่ค่อนข้างสูง 2 ข้อดังนั้นฉันจึงสงสัยว่ามีประเด็นหรือไม่ ...
Alec Teal

@AlecTeal หากคุณมีอะไรที่จะเพิ่มโปรดทำ ฉันยินดีที่จะรับฟังความคิดและข้อเสนอแนะ
netaholic

คำตอบ:


23

ใช้เสียงที่มีลำดับสูงกว่า หากคุณใช้สัญญาณรบกวน 2d สำหรับแผนที่ความสูงก่อนหน้านี้ให้ใช้เสียง 3 มิติโดยมีการแก้ไขพิกัดล่าสุดแทน ตอนนี้คุณสามารถเปลี่ยนตำแหน่งในมิติสุดท้ายเพื่อปรับเปลี่ยนภูมิประเทศได้อย่างช้าๆ เนื่องจากเสียงเพอร์ลินนั้นมีความต่อเนื่องในทุกมิติคุณจะได้การเปลี่ยนที่ราบรื่นตราบใดที่คุณเปลี่ยนตำแหน่งที่คุณฟังก์ชั่นเสียงรบกวนได้อย่างราบรื่น

หากคุณต้องการเปลี่ยนภูมิประเทศที่ห่างไกลจากระยะทางไปยังผู้เล่นเป็นออฟเซ็ตเช่น คุณสามารถจัดเก็บออฟเซตสำหรับแต่ละพิกัดบนแผนที่และเพิ่มขึ้น แต่ไม่ลดลง วิธีนี้แผนที่จะใหม่กว่า แต่ไม่เก่ากว่า

ความคิดนี้ยังใช้งานได้ถ้าคุณใช้สัญญาณรบกวนแบบ 3 มิติเพียงแค่ตัวอย่างจาก 4D ในตอนนั้น นอกจากนี้ลองดูที่ Simplex noise เป็นรุ่นปรับปรุงของสัญญาณรบกวน Perlin และทำงานได้ดีขึ้นสำหรับขนาดที่มากขึ้น


2
สิ่งนี้น่าสนใจ ฉันเข้าใจถูกต้องหรือไม่ว่าคุณแนะนำให้สร้างเสียง 3 มิติใช้ xy-slice ที่ z ของมันเป็นแผนที่ความสูงและทำการเปลี่ยนไปยังชิ้นอื่นอย่างราบรื่นโดยเปลี่ยนพิกัด z - เป็นระยะทางจากผู้เล่นเพิ่มขึ้น?
netaholic

@netaholic แน่นอน การอธิบายว่ามันเป็นชิ้นเป็นสัญชาตญาณที่ดีมาก นอกจากนี้คุณสามารถติดตามค่าสูงสุดสำหรับพิกัดล่าสุดทุกที่บนแผนที่และเพิ่มขึ้น แต่ไม่ลดลง
danijar

1
นี่เป็นความคิดที่ยอดเยี่ยม โดยทั่วไปแผนที่ภูมิประเทศของคุณจะเป็นชิ้นส่วนโค้ง (หรือส่วนโค้งอื่น ๆ ) ผ่านระดับเสียง 3 มิติ
ชื่อปลอม

นี่เป็นความคิดที่ฉลาดจริงๆ
253751

5

ความคิดของคุณที่จะแยกโลกออกเป็นหลาย ๆ ชิ้นก็ไม่เลว มันไม่สมบูรณ์

ปัญหาเดียวคือทางแยกระหว่างชิ้น ตัวอย่างเช่นหากคุณใช้เสียงเพอร์ลินเพื่อสร้างความโล่งใจและเมล็ดที่แตกต่างกันสำหรับแต่ละอันและเสี่ยงต่อสิ่งนี้ที่จะเกิดขึ้น:

ข้อบกพร่องบรรเทาก้อน

การแก้ปัญหาคือการสร้างการบรรเทาแบบก้อนไม่เพียง แต่จากเมล็ดพันธุ์ Perlin เท่านั้น แต่ยังรวมถึงชิ้นส่วนอื่น ๆ ที่อยู่รอบ ๆ ด้วย

อัลกอริทึม Perlin ใช้ค่าของแผนที่แบบสุ่มรอบตัวพวกเขาเพื่อ "ราบรื่น" ด้วยตัวเอง หากพวกเขาใช้แผนที่ทั่วไปจะมีการปรับให้เข้ากัน

ปัญหาเดียวก็คือถ้าคุณเปลี่ยนเมล็ดให้แตกต่างกันเมื่อผู้เล่นถอยห่างคุณจะต้องโหลดก้อนอีกรอบเพราะขอบควรเปลี่ยนเช่นกัน

สิ่งนี้จะไม่เปลี่ยนขนาดของชิ้นส่วน แต่มันจะเพิ่มระยะทางที่น้อยที่สุดจากผู้เล่นที่จะถูกโหลด / ไม่โหลดเนื่องจากชิ้นส่วนจะต้องโหลดเมื่อผู้เล่นเห็นและด้วยวิธีนี้เนื่องจากชิ้นส่วนที่อยู่ติดกันจะต้องมากเกินไป .

UPDATE:

หากแต่ละอันในโลกของคุณแตกต่างกันปัญหาจะเติบโตขึ้น นี่ไม่ใช่แค่การบรรเทาทุกข์ โซลูชันที่มีราคาแพงจะเป็นดังนี้:

ตัดชิ้นเนื้อ

สมมติว่าชิ้นสีเขียวคือโลกของป่าหมู่เกาะสีฟ้าและทะเลทรายสีเหลือง
วิธีแก้ปัญหาที่นี่คือการสร้างโซน "การเปลี่ยนภาพ" ที่ซึ่งลักษณะโล่งอกและพื้นดินของคุณ (รวมถึงวัตถุที่มีการต่อสายดินหรือสิ่งอื่นใดที่คุณต้องการ) จะเปลี่ยนจากประเภทหนึ่งเป็นอีกประเภทหนึ่ง

และอย่างที่คุณเห็นในภาพนี้ส่วนของรหัสนรกจะเป็นสี่เหลี่ยมจัตุรัสเล็ก ๆ ในมุมอันเล็ก: พวกมันต้องสร้างการเชื่อมโยงระหว่าง 4 ชิ้นซึ่งอาจมีลักษณะที่แตกต่างกัน

ดังนั้นสำหรับระดับความซับซ้อนนี้ฉันคิดว่าคนรุ่น 2D โลกคลาสสิกอย่าง Perlin2D ไม่สามารถใช้งานได้ ฉันแนะนำคุณถึง @danijar คำตอบสำหรับสิ่งนั้น


คุณแนะนำให้สร้าง "จุดศูนย์กลาง" ของก้อนจากเมล็ดและขอบ "ทำให้เรียบ" ตามชิ้นส่วนที่อยู่ติดกันหรือไม่? มันสมเหตุสมผล แต่มันจะเพิ่มขนาดของก้อนเนื่องจากมันควรมีขนาดของพื้นที่ผู้เล่นสามารถสังเกตบวกสองเท่าของความกว้างของพื้นที่เปลี่ยนผ่านไปยังชิ้นส่วนที่อยู่ติดกัน และพื้นที่ก้อนใหญ่ยิ่งมีความหลากหลายมากขึ้นในโลก
netaholic

@netaholic มันจะไม่ใหญ่ขึ้น แต่เป็นชนิด ฉันเพิ่มย่อหน้าลงไป
Aracthor

ฉันได้อัปเดตคำถามของฉันแล้ว พยายามที่จะอธิบายความคิดบางอย่างที่ฉันมี
netaholic

ดังนั้นคำตอบอื่น ๆ ที่นี่ใช้ (เรียงลำดับไม่มาก) มิติที่สามเป็นแผนภูมิ นอกจากนี้คุณยังมองเครื่องบินด้วยความหลากหลายและฉันชอบความคิดของคุณ หากต้องการยืดออกไปอีกเล็กน้อยคุณต้องมีท่อร่วมที่เรียบเนียน คุณต้องแน่ใจว่าช่วงการเปลี่ยนภาพของคุณราบรื่น จากนั้นคุณสามารถใช้ความเบลอหรือเสียงรบกวนกับสิ่งนี้และคำตอบจะสมบูรณ์แบบ
Alec Teal

0

ในขณะที่ความคิดของ Danijar นั้นค่อนข้างแข็งแกร่งคุณสามารถเก็บข้อมูลจำนวนมากได้หากคุณต้องการให้พื้นที่ในพื้นที่นั้นเหมือนกันและการเปลี่ยนระยะทาง และขอเสียงรบกวนที่ซับซ้อนมากขึ้นเรื่อย ๆ คุณสามารถรับสิ่งเหล่านี้ทั้งหมดในแบบ 2d ที่เป็นมาตรฐานมากกว่า

ฉันได้พัฒนาอัลกอริทึมสำหรับการสร้างเสียงเศษส่วนแบบสุ่มโดยใช้ส่วนของอัลกอริธึมไดมอนด์สแควร์ที่ฉันกำหนดให้เป็นแบบไม่ จำกัด และแบบกำหนดขั้นตอน ดังนั้นไดมอนด์สแควร์สามารถสร้างภูมิทัศน์ที่ไม่มีที่สิ้นสุดเช่นเดียวกับอัลกอริทึมบล็อกของฉันค่อนข้างเป็นธรรม

ความคิดนั้นเหมือนกัน แต่แทนที่จะสุ่มตัวอย่างเสียงมิติที่สูงขึ้นคุณสามารถวนซ้ำค่าในระดับซ้ำ ๆ ที่แตกต่างกัน

ดังนั้นคุณยังคงเก็บค่าที่คุณร้องขอไว้ก่อนหน้านี้และทำการแคช (รูปแบบนี้สามารถใช้เพื่ออัลกอริธึมที่รวดเร็วเป็นพิเศษได้แล้ว) และเมื่อมีการร้องขอพื้นที่ใหม่มันจะถูกสร้างขึ้นด้วยค่า y ใหม่ และพื้นที่ใด ๆ ที่ไม่ได้ร้องขอในคำขอนั้นจะถูกลบออก

ดังนั้นแทนที่จะกำหนดขอบเขตผ่านพื้นที่ต่าง ๆ ในมิติข้อมูลเพิ่มเติม เราจัดเก็บข้อมูลแบบโมโนโทนิกเพิ่มเล็กน้อยเพื่อผสมกัน (ในปริมาณที่มากขึ้นในระดับที่ต่างกัน)

หากผู้ใช้เดินทางไปในทิศทางค่าจะถูกย้ายตาม (และในแต่ละระดับ) และค่าใหม่จะถูกสร้างขึ้นที่ขอบใหม่ หากมีการเปลี่ยนแปลงเมล็ดซ้ำย้ำอันดับต้น ๆ ทั้งโลกก็จะเปลี่ยนไปอย่างมาก หากการทำซ้ำครั้งสุดท้ายได้รับผลลัพธ์ที่แตกต่างกันจำนวนการเปลี่ยนแปลงจะน้อยมาก + -1 บล็อกหรือมากกว่านั้น แต่เนินเขาจะยังอยู่ที่นั่นและหุบเขา ฯลฯ แต่พวกซอกและ crannies จะเปลี่ยนไป เว้นแต่คุณจะไปไกลพอและจากนั้นเขาจะหายไป

ดังนั้นถ้าเราเก็บค่า 100x100 อันแต่ละค่าซ้ำ จากนั้นไม่มีอะไรสามารถเปลี่ยนแปลงที่ 100x100 จากผู้เล่น แต่สิ่งที่ 200x200 สามารถเปลี่ยนแปลงได้ 1 บล็อก ที่ 400x400 สิ่งต่าง ๆ สามารถเปลี่ยนแปลงได้ 2 ช่วงตึก เมื่ออยู่ห่างออกไป 800x800 จะสามารถเปลี่ยนได้ 4 ช่วงตึก ดังนั้นสิ่งต่าง ๆ จะเปลี่ยนไปและพวกเขาจะเปลี่ยนไปมากขึ้นเรื่อย ๆ ถ้าคุณกลับไปพวกเขาจะแตกต่างกันถ้าคุณไปไกลเกินไปพวกเขาจะเปลี่ยนไปอย่างสิ้นเชิงและสูญเสียไปโดยสิ้นเชิงเพราะเมล็ดทั้งหมดจะถูกทิ้งร้าง

การเพิ่มมิติที่แตกต่างกันเพื่อให้เอฟเฟกต์การทรงตัวนี้ใช้งานได้จริงขยับ y ที่ระยะทาง ในอัลกอริทึมสัญญาณรบกวนแฟร็กทัลเสียงคุณสามารถได้รับผลเช่นเดียวกันนี้โดยการเพิ่มค่าการเปลี่ยนแปลง (ในจำนวนที่แตกต่างกัน) ในขณะที่ตำแหน่งเคลื่อนที่เกินจุด

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>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.