วิธีการสุ่มวางเอนทิตีที่ไม่ทับซ้อนกัน?


11

ฉันกำลังสร้างสภาพแวดล้อมที่สร้างแบบสุ่มสำหรับเกมที่ฉันกำลังพัฒนา ฉันใช้และการเข้ารหัสในOpenGLJava

ฉันกำลังพยายามวางต้นไม้ในโลกของฉัน (เพื่อสร้างป่า) แต่ฉันไม่ต้องการให้แบบจำลองทับซ้อนกัน (ซึ่งเกิดขึ้นเมื่อวางต้นไม้สองต้นใกล้กันเกินไป) นี่คือภาพสิ่งที่ฉันพูดถึง:

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

ฉันสามารถให้รหัสเพิ่มเติมหากจำเป็น แต่นี่คือตัวอย่างสำคัญ ฉันกำลังจัดเก็บวัตถุของฉันในด้วยArrayList List<Entity> entities = new ArrayList<Entity>();ฉันจะเพิ่มรายการนั้นโดยใช้:

Random random = new Random();
for (int i = 0; i < 500; i++) {
    entities.add(new Entity(tree, new Vector3f(random.nextFloat() * 800 - 400, 
    0, random.nextFloat() * -600), 0, random.nextFloat() * 360, 0, 3, 3, 3);
}

โดยที่แต่ละEntityรายการมีไวยากรณ์ต่อไปนี้:

new Entity(modelName, positionVector(x, y, z), rotX, rotY, rotZ, scaleX, scaleY, scaleZ);

rotXคือการหมุนตามแกน x และscaleXเป็นสเกลในทิศทาง x ฯลฯ

คุณสามารถเห็นได้ว่าฉันกำลังวางต้นไม้เหล่านี้แบบสุ่มด้วยrandom.nextFloat()สำหรับxและzตำแหน่งและการ จำกัด ขอบเขตเพื่อให้ต้นไม้ปรากฏในตำแหน่งที่ต้องการ อย่างไรก็ตามฉันต้องการควบคุมตำแหน่งเหล่านี้เพื่อที่ว่าถ้ามันพยายามวางต้นไม้ใกล้กับต้นไม้ที่วางไว้ก่อนหน้านี้มันจะคำนวณตำแหน่งสุ่มใหม่ ฉันคิดว่าต้นไม้แต่ละต้นEntityสามารถมีเขตข้อมูลอื่นเช่นtreeTrunkGirthและถ้าต้นไม้วางอยู่ในระยะห่างระหว่างที่ตั้งของต้นไม้อื่นและมันtreeTrunkGirthก็จะคำนวณตำแหน่งใหม่ มีวิธีการทำสิ่งนี้หรือไม่?

ฉันยินดีที่จะเพิ่มข้อมูลโค้ดและรายละเอียดเพิ่มเติมตามความจำเป็น


3
การสุ่มตัวอย่างดิสก์ปัวซองควรทำงาน ไม่รู้ว่ามันดีที่สุดสำหรับสิ่งนี้และไม่เคยนำไปใช้จริงหรือใช้ แต่ดูเหมือนว่าเป็นการเริ่มต้นที่ดี ตรวจสอบบทความนี้: devmag.org.za/2009/05/03/poisson-disk-sampling
ดาวอังคาร

@Mars Wow ลิงค์นั้นมีประโยชน์มากขอบคุณ ฉันจะเห็นสิ่งที่ฉันสามารถทำได้และอาจกลับมาพร้อมคำตอบของฉันเอง
wcarhart

@Pikalek ใช่ฉันคิดว่าคำถามที่คุณเชื่อมโยงนั้นซ้ำซ้อน ฉันจะใช้เครื่องบิน xzเป็นพื้นที่สำหรับ "แผนที่ดาว" เหมือนในคำถามอื่นหรือไม่?
wcarhart

ใช่ใช้ระนาบxzในกรณีของคุณ นอกจากนี้ให้ใช้treeTrunkGirthแทนค่าคงที่เพื่อกำหนดระยะห่างขั้นต่ำสำหรับการวางต้นไม้หากต้องการเปลี่ยนแปลง
Pikalek

@Pikalek หากคุณเพิ่มทุกอย่างในคำตอบฉันจะเลือกให้ดีที่สุด ขอบคุณสำหรับความช่วยเหลือ
wcarhart

คำตอบ:


15

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

อัลกอรึทึมของ Bridsonสามารถแก้ไขปัญหาใน O (n) ได้อย่างมีประสิทธิภาพ แต่สามารถบิดไปมาเพื่อปรับเปลี่ยนระยะทางได้เล็กน้อย หากพารามิเตอร์ของคุณค่อนข้างต่ำ & / หรือคุณกำลังคำนวณพื้นที่ของคุณไว้ล่วงหน้าวิธีการแก้ปัญหาแบบเดรัจฉานก็อาจให้บริการคุณเช่นกัน ต่อไปนี้เป็นตัวอย่างโค้ดที่เดรัจฉานทำให้เกิดปัญหาโดยการตรวจสอบการวางต้นไม้ใหม่ที่อาจเกิดขึ้นกับต้นไม้ที่วางไว้ก่อนหน้าทั้งหมด:

public static class SimpleTree{
    float x;
    float z;
    float r;

    public SimpleTree(Random rng, float xMax, float zMax, float rMin, float rMax){
        x = rng.nextFloat() * xMax;
        z = rng.nextFloat() * zMax;
        r = rng.nextFloat() * (rMax-rMin) + rMin;
    }
}

private static ArrayList<SimpleTree> buildTreeList(float xMax, float zMax, 
        float rMin, float rMax, int maxAttempts, Random rng) {
    ArrayList<SimpleTree> result = new ArrayList<>();

    SimpleTree currentTree = new SimpleTree(rng, xMax, zMax, rMin, rMax);
    result.add(currentTree);

    boolean done = false;
    while(!done){
        int attemptCount = 0;
        boolean placedTree = false;
        Point nextPoint = new Point();
        SimpleTree nextTree = null;
        while(attemptCount < maxAttempts && !placedTree){
            attemptCount++;
            nextTree = new SimpleTree(rng, xMax, zMax, rMin, rMax);
            if(!tooClose(nextTree, result)){
                placedTree = true;
            }
        }
        if(placedTree){
            result.add(nextTree);
        }
        else{
            done = true;
        }
    }

    return result;
}

private static boolean tooClose(SimpleTree tree, ArrayList<SimpleTree> treeList) {
    for(SimpleTree otherTree : treeList) {
        float xDiff = tree.x - otherTree.x;
        float zDiff = tree.z - otherTree.z;

        float dist = (float)Math.sqrt((xDiff * xDiff) + (zDiff * zDiff));
        if(dist < tree.r + otherTree.r){
            return true;
        }
    }        
    return false;
}

ด้วยพารามิเตอร์ต่อไปนี้:

 maxAttempts = 500;
 width = 300;
 height = 200;
 minSize = 2;
 maxSize = 15;

ฉันสามารถวาง & แสดงผลระหว่างต้น 400-450 ต้นในเวลาไม่กี่วินาที นี่คือตัวอย่าง: ป้อนคำอธิบายรูปภาพที่นี่


สิ่งนี้ใช้การสุ่มตัวอย่างดิสก์ของปัวซองหรือไม่
wcarhart

ใช่ฉันแก้ไขให้ชัดเจนแล้ว
Pikalek

ลองใช้ math.pow บนtree.r + other tree.r, 2, แทน math.sqrt, sqrt มักจะช้ากว่า pow
Ferrybig

1
@Ferrybig การเปรียบเทียบระยะกำลังสองได้เร็วขึ้น แต่นั่นไม่ได้เปลี่ยนความจริงที่ว่ามันเป็นอัลกอริทึมกำลังดุร้ายและจะยังคงเป็น O (n ^ 2) หากต้องการวิธีแก้ปัญหาที่เร็วกว่านี้ให้ใช้อัลกอริทึมของ Bridson นอกจากนี้การใช้Math.pow(x,2)ไม่จำเป็นใด ๆ ที่ดี / ที่แตกต่างกันกว่าการใช้x*xเป็นกล่าวถึงที่นี่
Pikalek

1
ฉันมีปัญหาคล้ายกันกับภูมิประเทศและทำให้แน่ใจว่ามีการแพร่กระจายที่เหมาะสมและไม่ทับซ้อนกัน จริง ๆ แล้วฉันได้ทำการเปลี่ยนแปลงในเวอร์ชั่นของฉันม้วนผม / แปรงจะกระจายไปทั่วพื้นที่ จากนั้นฉันเรียกใช้ฟังก์ชั่นโพสต์นี้เพื่อตรวจสอบระยะทางของทุกรายการซึ่งกันและกันซึ่งพวกเขาอยู่ใกล้เกินไปฉันผลักมันออกจากกัน แม้ว่าสิ่งนี้จะกระทบกับต้นไม้อื่น ๆ ในพื้นที่ ฉันทำสิ่งนี้ซ้ำจนกระทั่งไม่มีการชนกัน มันช้าลง แต่สิ่งที่ฉันมีในฐานะโบนัสคือสิ่งต่าง ๆ เช่นการล้าง (ไม่ครอบคลุมทุกที่!) และความหนาแน่นของต้นไม้ดูเหมือน "น่าสนใจ" มากขึ้น
ErnieDingo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.