작은 상자로 만들 수있는 가장 큰 상자를 추출하는 알고리즘이 너무 느립니다.


9

큐브 크기의 세계 (예 : Minecraft, Trove 또는 Cube World)가 모두 같은 크기의 큐브로 구성되고 모든 큐브가 같은 종류 인 경우를 상상해보십시오 .

목표는 큐브를 병합하지만 볼록한 모양 (직사각형 상자 모양)을 유지하여 사각형 상자 수가 가장 적은 세계를 나타내는 것입니다. 내 알고리즘은 이것에 성공하지만 의도 된 목적에 비해 성능이 너무 느립니다. 더 빠른 알고리즘이 있습니까?

내 알고리즘의 의사 C # 코드는 기본적으로 다음과 같습니다.

struct Coordinate { int x,y,z; }; //<-- integer based grid
HashSet<Coordinate> world; // <-- contains all the cubes

//width, height, and length represent how many cubes it spans
struct RectangularBox { Coordinate coord; int width,height,length; }

void Begin()
{
    List<RectangularBox> fewestBoxes = new List<RectangularBox>();
    while(world.Count > 0)
    {
         RectangularBox currentLargest = ExtractLargest();
         fewestBoxes.Add(currentLargest);
         world.RemoveRange(currentLargest.ContainedCubes());
    }
    //done; `fewestBoxes` contains the fewest rectangular boxes needed.
}

private RectangularBox ExtractLargest()
{
    RectangularBox largestBox = new RectangularBox();
    foreach (Coordinate origin in world)
    {
        RectangularBox box = FindMaximumSpan(origin);
        if (box.CalculateVolume() >= largestBox.CalculateVolume())
            largestBox = box;
    }
    return largestBox;
}

private RectangularBox FindMaximumSpan(Coordinate origin)
{
    int maxX, maxY,maxZ;
    while (world.Contains(origin.Offset(maxX, 0, 0))) maxX++;
    while (world.Contains(origin.Offset(0, maxY, 0))) maxY++;
    while (world.Contains(origin.Offset(0, 0, maxZ))) maxZ++;

    RectangularBox largestBox;
    for (int extentX = 0; extentX <= maxX; extentX++)
        for (int extentY = 0; extentY <= maxY; extentY++)
            for (int extentZ = 0; extentZ <= maxZ; extentZ++)
            {
                int lengthX = extentX + 1;
                int lengthY = extentY + 1;
                int lengthZ = extentZ + 1;
                if (BoxIsFilledWithCubes(origin, lengthX, lengthY, lengthZ))
                {
                    int totalVolume = lengthX * lengthY * lengthZ;
                    if (totalVolume >= largestBox.ComputeVolume())
                        largestBox = new RectangularBox(origin, lengthX, lengthY, lengthZ);
                }
                else
                    break;
            }
    return largestBox;
}

private bool BoxIsFilledWithCubes(Coordinate coord, 
    int lengthX, int lengthY, int lengthZ)
{
    for (int gX = 0; gX < lengthX; gX++)
        for (int gY = 0; gY < lengthY; gY++)
            for (int gZ = 0; gZ < lengthZ; gZ++)
                if (!world.Contains(coord.Offset(gX, gY, gZ)))
                    return false;
    return true;
}

기본적으로 세계의 모든 블록에 대해 먼저 3 개의 양의 차원 (+ X, + Y, + Z)에 몇 개의 연속 블록이 있는지 찾습니다. 그리고 그것은 그 양을 채우고 어떤 블록도 빠지지 않은 가장 큰 충전물인지 점검합니다.


업데이트 : 이것이 게임의 렌더링 엔진에 대한 것임을 암시하는 것처럼 보였으므로 분명히 말하고 싶습니다. 이것은 게임의 렌더링 엔진에 대한 것이 아닙니다. 파일 변환기를위한 것입니다. GUI가 없습니다.


2
codereview.stackexchange.com에 더 적합 할 것입니다
Rotem

4
@Rotem 아마도,하지만 실제로 내 코드를 검토하는 대신 대체 알고리즘을 찾고 있습니다. 나는 습관의 힘으로 내 코드를 제공했습니다.
Mr. Smith

물론 말이됩니다.
Rotem

알고리즘 질문은 컴퓨터 과학과 같은 SE 사이트에 더 적합합니다.
Bakuriu

또한 메소드 호출 빈도에 따라 다릅니다. 매 프레임마다 또는 블록이 변경 될 때만 호출합니다. 이러한 게임에는 일반적으로 청크 (특정 크기의 사각형 ex : 64x64x32 블록)가 있으며 값을 최대한 캐시하고 청크 당 계산합니다. 그리고 보이는 블록에서만 이러한 값을 계산하십시오.
the_lotus

답변:


6

당신은 사실을 활용할 수 있습니다

 BoxIsFilledWithCubes(c,x,y,z)

true를 반환 BoxIsFilledWithCubes(c,x+1,y,z)하면 좌표 범위 "(c, x, y, z)"에 있는 모든 큐브 를 다시 체크인 할 필요가 없습니다 . 새로운 x 좌표로 큐브를 확인하기 만하면됩니다 c.x + (x+1). y+1또는의 경우도 마찬가지입니다 z+1. 더 일반적으로, 상자를 두 개의 작은 상자로 나눠서 (둘 다 큐브로 채워 졌는지 또는 채워져 있지 않은지 이미 알고있을 수 있음) 분할 및 정복 기법을 적용 할 수 있습니다. 중간 결과를 캐시 할 때 원래 접근 방식.

이를 위해 다음과 같이 BoxIsFilledWithCubes(c,x,y,z)재귀 적으로 구현을 시작합니다 .

 bool BoxIsFilledWithCubes(coord,lx,ly,lz)
 {
     if(lx==0|| ly==0 || lz==0)
        return true;
     if(lx==1 && ly==1 && lz==1)
          return world.Contains(coord);
     if(lx>=ly && lx>=lz)  // if lx is the maximum of lx,ly,lz ....
         return BoxIsFilledWithCubes(coord,lx/2,ly,lz) 
             && BoxIsFilledWithCubes(coord.Offset(lx/2,0,0), lx-lx/2, ly, lz);
     else if(ly>=lz && ly>=lx)  
         // ... analogously when ly or lz is the maximum

 }

그런 다음 동일한 매개 변수로 반복되는 호출을 피하기 위해 메모 를 사용 하십시오 (여기에서 설명하는 것처럼 ) BoxIsFilledWithCubes. worldby에 의해 변경 사항을 컨테이너에 적용 할 때 메모 캐시를 비워야합니다 world.RemoveRange. 그럼에도 불구하고 이것이 프로그램을 더 빨리 만들 것이라고 생각합니다.


5

업 빌드 옥트리 당신의 상자의 크기의 잎 노드 AABB 크기를. octree를 순회하면서 노드를 저렴하게 병합 할 수 있습니다. 완전히 채워진 노드는 병합하기가 쉽지 않습니다 (새 상자 = 부모 aabb). 부분적으로 채워진 노드의 경우 현재 알고리즘의 변형을 사용하여 병합 가능성을 확인할 수 있습니다.


두 노드가 완전히 채워 졌다고해서 병합되어야 함을 의미하지는 않습니다. 가장 큰 상자를 찾는 단계가 아닙니다. 그것들을 합치면 가장 큰 상자가 발견되면 나중에 다시 분할해야 할 것입니다. 이 시나리오에서 octree가 어떻게 도움이되는지 모르겠습니다.
Mr. Smith

8 명의 자녀가 모두 하나의 큰 상자에 포함될 수 있다는 의미에서 전체 노드를 병합 할 수 있습니다. 이 큰 상자는 나중에 나눌 필요가 없지만 병합 될 수 있습니다. 계층 구조를 사용하면 많은 상자를 신속하게 병합 할 수 있으며 완전히 채워진 노드를 사용하면 추가 테스트없이 레벨을 올릴 수 있습니다.
Wilbert

1

“Begin ()”에서 세계의 모든 상자를 반복 할 때 최소한 O (n ^ 2) ( 큰 O 표기법 참조 ) 인 것처럼 보이고 각 상자에 대해 ExtractLargest ( ). 따라서 관련없는 상자가 10 개있는 세계는 5 개의 관련없는 상자가있는 세계보다 4 배 더 오래 걸립니다.

따라서 ExtractLargest ()가 살펴 봐야하는 상자의 수를 제한해야합니다 . 3D로 작업 할 때 3D 공간 검색이 필요할 수있는 공간 검색 유형을 사용해야 합니다. 그러나 먼저 2D 공간 검색을 이해하여 시작하십시오.

그런 다음 서로 위에 많은 상자가 있을지 고려하십시오. 그렇지 않으면 x, y 만 포함하는 2D 공간 검색으로 루핑을 줄일 수 있습니다.

Octree / quadtree 는 하나의 옵션이지만 공간 분할 을위한 다른 많은 옵션이 있습니다 .

그러나 점 (a, b)을 포함하는 모든 상자가 array [a, b] .list에 있는 2 차원 목록 배열 ( Grid Space Index ) 만 사용할 수 있습니다 . 그러나 가장 유쾌하게도 배열이 너무 커질 수 있으므로 array [mod (a, 100), mob (b, 100)]. list는 어떻습니까? 이것은 모두 데이터가 어떤지에 달려 있습니다.

(그리드 솔루션은 실제 생활에서 매우 잘 작동하는 것을 보았습니다.)

또는 상자 크기의 리프 노드 aabb 크기를 가진 octree로 Wilbert가 말한 것을 수행하지만 나중에 사용자의 마우스가 가리키는 상자를 공간 검색의 경우 다시 찾아야 할 수도 있습니다.

( 이 소프트웨어를 사용하려고하는지 또는 더 나은 프로그래머가되는 방법을 이해하려고하는지 여부를 결정해야하므로 학습에 대한 관심이 빠른 솔루션입니다. )

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.