Como generalizo o algoritmo de linha de Bresenham para pontos finais de ponto flutuante?

12

Estou tentando combinar duas coisas. Estou escrevendo um jogo e preciso determinar os quadrados da grade em uma linha com os pontos finais de ponto flutuante.

Grade de calha de linha

Além disso, preciso incluir todos os quadrados da grade em que toca (ou seja, não apenas a linha de Bresenham, mas a linha azul):

Bresenham vs varredura completa

Alguém pode me oferecer alguma idéia de como fazer isso? A solução óbvia é usar o algoritmo de linha ingênuo, mas há algo mais otimizado (mais rápido)?

SmartK8
fonte
No caso da ligação fica offline, apenas google para "Um mais rápido voxel travessia algoritmo para raytracing"
Gustavo Maciel

Respostas:

9

Você está procurando um algoritmo de deslocamento de grade. Este artigo fornece uma boa implementação;

Aqui está a implementação básica em 2D encontrada no artigo:

loop {
    if(tMaxX < tMaxY) {
        tMaxX= tMaxX + tDeltaX;
        X= X + stepX;
    } else {
        tMaxY= tMaxY + tDeltaY;
        Y= Y + stepY;
    }
    NextVoxel(X,Y);
}

Há também uma versão 3D de projeção de raios no papel.

Caso o link apodreça , você poderá encontrar muitos espelhos com seu nome: Um algoritmo transversal voxel mais rápido para rastreamento de raios .

Gustavo Maciel
fonte
Bem, estranho. Eu acho que vou mudar a resposta para você e votar no ltjax. Porque eu resolvi com base no seu link para esse artigo.
precisa saber é o seguinte
5

A ideia de Blue é boa, mas a implementação é um pouco desajeitada. De fato, você pode fazer isso facilmente sem o sqrt. Vamos supor que você exclua casos degenerados ( BeginX==EndX || BeginY==EndY) e se concentre apenas nas direções da linha no primeiro quadrante BeginX < EndX && BeginY < EndY. Você também precisará implementar uma versão para pelo menos outro quadrante, mas isso é muito semelhante à versão do primeiro quadrante - você só verifica outras arestas. No pseudo-código C'ish:

int cx = floor(BeginX); // Begin/current cell coords
int cy = floor(BeginY);
int ex = floor(EndX); // End cell coords
int ey = floor(EndY);

// Delta or direction
double dx = EndX-BeginX;
double dy = EndY-BeginY;

while (cx < ex && cy < ey)
{
  // find intersection "time" in x dir
  float t0 = (ceil(BeginX)-BeginX)/dx;
  float t1 = (ceil(BeginY)-BeginY)/dy;

  visit_cell(cx, cy);

  if (t0 < t1) // cross x boundary first=?
  {
    ++cx;
    BeginX += t0*dx;
    BeginY += t0*dy;
  }
  else
  {
    ++cy;
    BeginX += t1*dx;
    BeginY += t1*dy;
  }
}

Agora, para outros quadrantes, basta alterar a condição ++cxou ++cye do loop. Se você usar isso para colisão, provavelmente precisará implementar todas as 4 versões; caso contrário, poderá se safar de duas trocando adequadamente os pontos inicial e final.

ltjax
fonte
O algoritmo que Gustavo Maciel forneceu é um pouco mais eficiente. Ele determina apenas primeiro Ts e depois adiciona 1 à vertical ou horizontal e muda Ts por um tamanho de célula. Mas como ele não a converteu em resposta, aceitarei esta como a resposta mais próxima.
precisa saber é o seguinte
3

Sua suposição não é necessariamente encontrar as células, mas as linhas que ela cruza nesta grade.

Por exemplo, tirando sua imagem, podemos destacar não as células, mas as linhas da grade que ela cruza:

Linhas vermelhas

Isso mostra que, se cruzar uma linha de grade, as células de ambos os lados dessa linha são aquelas preenchidas.

Você pode usar um algoritmo de interseção para descobrir se sua linha de ponto flutuante os cruzará escalando seus pontos em pixels. Se você tiver uma proporção de 1,0: 1 de coordenadas flutuantes: pixels, será classificada e poderá traduzi-la diretamente. Usando o algoritmo de interseção do segmento de linha, você pode verificar se a linha inferior esquerda (1,7) (2,7) cruza com a linha (1.3,6.2) (6.51,2.9).http://alienryderflex.com/intersect/

Alguma tradução de c para C # será necessária, mas você pode obter a ideia nesse documento. Colocarei o código abaixo, caso o link seja quebrado.

//  public domain function by Darel Rex Finley, 2006

//  Determines the intersection point of the line defined by points A and B with the
//  line defined by points C and D.
//
//  Returns YES if the intersection point was found, and stores that point in X,Y.
//  Returns NO if there is no determinable intersection point, in which case X,Y will
//  be unmodified.

bool lineIntersection(
double Ax, double Ay,
double Bx, double By,
double Cx, double Cy,
double Dx, double Dy,
double *X, double *Y) {

  double  distAB, theCos, theSin, newX, ABpos ;

  //  Fail if either line is undefined.
  if (Ax==Bx && Ay==By || Cx==Dx && Cy==Dy) return NO;

  //  (1) Translate the system so that point A is on the origin.
  Bx-=Ax; By-=Ay;
  Cx-=Ax; Cy-=Ay;
  Dx-=Ax; Dy-=Ay;

  //  Discover the length of segment A-B.
  distAB=sqrt(Bx*Bx+By*By);

  //  (2) Rotate the system so that point B is on the positive X axis.
  theCos=Bx/distAB;
  theSin=By/distAB;
  newX=Cx*theCos+Cy*theSin;
  Cy  =Cy*theCos-Cx*theSin; Cx=newX;
  newX=Dx*theCos+Dy*theSin;
  Dy  =Dy*theCos-Dx*theSin; Dx=newX;

  //  Fail if the lines are parallel.
  if (Cy==Dy) return NO;

  //  (3) Discover the position of the intersection point along line A-B.
  ABpos=Dx+(Cx-Dx)*Dy/(Dy-Cy);

  //  (4) Apply the discovered position to line A-B in the original coordinate system.
  *X=Ax+ABpos*theCos;
  *Y=Ay+ABpos*theSin;

  //  Success.
  return YES; }

Se você precisar descobrir apenas quando (e onde) os segmentos de linha se cruzam, você pode modificar a função da seguinte maneira:

//  public domain function by Darel Rex Finley, 2006  

//  Determines the intersection point of the line segment defined by points A and B
//  with the line segment defined by points C and D.
//
//  Returns YES if the intersection point was found, and stores that point in X,Y.
//  Returns NO if there is no determinable intersection point, in which case X,Y will
//  be unmodified.

bool lineSegmentIntersection(
double Ax, double Ay,
double Bx, double By,
double Cx, double Cy,
double Dx, double Dy,
double *X, double *Y) {

  double  distAB, theCos, theSin, newX, ABpos ;

  //  Fail if either line segment is zero-length.
  if (Ax==Bx && Ay==By || Cx==Dx && Cy==Dy) return NO;

  //  Fail if the segments share an end-point.
  if (Ax==Cx && Ay==Cy || Bx==Cx && By==Cy
  ||  Ax==Dx && Ay==Dy || Bx==Dx && By==Dy) {
    return NO; }

  //  (1) Translate the system so that point A is on the origin.
  Bx-=Ax; By-=Ay;
  Cx-=Ax; Cy-=Ay;
  Dx-=Ax; Dy-=Ay;

  //  Discover the length of segment A-B.
  distAB=sqrt(Bx*Bx+By*By);

  //  (2) Rotate the system so that point B is on the positive X axis.
  theCos=Bx/distAB;
  theSin=By/distAB;
  newX=Cx*theCos+Cy*theSin;
  Cy  =Cy*theCos-Cx*theSin; Cx=newX;
  newX=Dx*theCos+Dy*theSin;
  Dy  =Dy*theCos-Dx*theSin; Dx=newX;

  //  Fail if segment C-D doesn't cross line A-B.
  if (Cy<0. && Dy<0. || Cy>=0. && Dy>=0.) return NO;

  //  (3) Discover the position of the intersection point along line A-B.
  ABpos=Dx+(Cx-Dx)*Dy/(Dy-Cy);

  //  Fail if segment C-D crosses line A-B outside of segment A-B.
  if (ABpos<0. || ABpos>distAB) return NO;

  //  (4) Apply the discovered position to line A-B in the original coordinate system.
  *X=Ax+ABpos*theCos;
  *Y=Ay+ABpos*theSin;

  //  Success.
  return YES; }
Tom 'Blue' Piddock
fonte
Olá, o percurso da grade é exatamente com o objetivo de otimizar milhares de interseções de linhas em toda a grade. Isso não pode ser resolvido por milhares de interseções de linhas. Eu tenho um mapa em um jogo com linhas que o jogador não pode cruzar. Pode haver milhares deles. Preciso determinar qual calcular a interseção cara. Para determinar isso, eu quero apenas calcular as interseções daqueles na linha de movimento do jogador (ou a luz da fonte de luz). No seu caso, eu precisaria determinar interseções com ~ 256x256x2 segmentos de linha a cada rodada. Isso não seria otimizado.
precisa saber é o seguinte
Mas ainda assim obrigado por responder. Tecnicamente, ele funciona e está correto. Mas simplesmente não é viável para mim.
precisa saber é o seguinte
3
float difX = end.x - start.x;
float difY = end.y - start.y;
float dist = abs(difX) + abs(difY);

float dx = difX / dist;
float dy = difY / dist;

for (int i = 0, int x, int y; i <= ceil(dist); i++) {
    x = floor(start.x + dx * i);
    y = floor(start.y + dy * i);
    draw(x,y);
}
return true;

Demonstração JS:

Imgur

A-312
fonte
1
Isso falhou para mim devido a erros numéricos de ponto flutuante (o loop fará uma iteração extra para a fração mais pequena do próximo número inteiro que empurrará o ponto final da linha além do local 'final'). A correção simples é calcular dist como um teto em primeiro lugar; portanto, dx, dy são divididos pelo número inteiro de iterações do loop (isso significa que você pode perder o teto (dist) no loop for).
PeteB
0

Encontrei o mesmo problema hoje e fiz uma grande montanha de espaguete de uma colina, mas acabei com algo que funciona: https://github.com/SnpM/Pan-Line-Algorithm .

No Leiame:

O conceito central desse algoritmo é semelhante ao de Bresenham, na medida em que ele aumenta em 1 unidade em um eixo e testa o aumento no outro eixo. As frações tornam o incremento consideravelmente mais difícil, no entanto, e muitas pizzas precisam ser adicionadas. Por exemplo, incrementar de X = 0,21 a X = 1,21 com uma inclinação de 5 cria um problema complexo (padrões de coordenadas entre esses números desagradáveis é difícil de prever), mas aumentar de 1 para 2 com uma inclinação de 5 facilita o problema. O padrão de coordenadas entre números inteiros é muito fácil de resolver (apenas uma linha perpendicular ao eixo de incremento). Para obter o problema mais fácil, o incremento é deslocado para um número inteiro com todos os cálculos feitos separadamente para a peça fracionária. Então, em vez de iniciar o incremento em 0,21,

O Leia-me explica a solução muito melhor que o código. Estou pensando em revisá-lo para ser menos indutor de dor de cabeça.

Sei que estou com um ano de atraso para essa pergunta, mas espero que isso chegue a outras pessoas que estão procurando uma solução para esse problema.

JPtheK9
fonte