<< Voltar Índice Próxima >>

RAYSCENEQUERY E SELEÇÃO COM O MOUSE

RaySceneQuery

Este tutorial exige que se tenha conhecimento da Navi e que ela esteja funcionando. Por isso caso ainda não tenha lido os tutorias aqui, deve-se lê-los para que possa acompanhar o restante do texto.

Um RaySceneQuery são raios(ray) lançados a partir de uma posição em uma direção no ambiente 3D. Eles retornam em uma lista, todos os elementos da cena em que o raio alcançou. São muito utilizados para detecção bem simples, além de verificação de qual elemento foi selecionado com o clique do mouse. Essa última será abordada no texto.

    Entity *entRobo = scene_mgr->createEntity("robo","robot.mesh");
    SceneNode *nodeRobo = scene_mgr->getRootSceneNode()->createChildSceneNode("roboNode");
    nodeRobo->attachObject(entRobo);
    nodeRobo->setPosition(Vector3(-25,0,-200));
    nodeRobo->setVisible(false);

    Entity *entKnot = scene_mgr->createEntity("Knot","knot.mesh");
    SceneNode *nodeKnot = scene_mgr->getRootSceneNode()->createChildSceneNode("knotNode");
    nodeKnot->attachObject(entKnot);
    nodeKnot->setVisible(false);
    nodeKnot->setScale(0.2f, 0.2f, 0.2f);

    scene_mgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
    scene_mgr->setWorldGeometry("terrain.cfg");

    camera->setPosition(Vector3(750,150,750));
    camera->pitch(Degree(-30));

 

Primeiro montamos uma cena para o exemplo, com o código acima no método "CarregaMalhas()". Cria-se o robô e uma malha com formato de nó(knot) que marcará o local do clique do mouse no terreno, ambos estão inicialmente invisíveis. Um SkyDome e um terreno também são adicionados para compor a cena, além de ajustar a posição e o ângulo de visão da câmera.

    RaySceneQuery *raySceneQuery;	// O ponteiro do RaySceneQuery

 

No arquivo "AplicationListener.h" adicione a variável acima. O RaySceneQuery executará as ações dos raios(rays) criados.

    raySceneQuery = scene_mgr->createRayQuery(Ray());

 

Inicializa-se o RaySceneQuery através do gerenciador da cena(SceneManager), no construtor da classe.

Colisão Simples

    Vector3 camPos = camNode->getPosition();
    Ray cameraRay(Vector3(camPos.x, 1000.0f, camPos.z), Vector3::NEGATIVE_UNIT_Y);
    raySceneQuery->setRay(cameraRay);

 

Indo até o médoto "frameStarted()" adicionaremos um pequeno código para exemplificar uma detecção de colisão bem simples usando ray. Vamos forçar a câmera a ficar sempre a 60(sessenta) unidades acima do terreno, para isso verificaremos a posição utilizando um ray e depois atualizaremos caso seja necessário.

O trecho acima cria um vetor "camPos" que guardará a posição atual da câmera. Em seguida criamos um ray, através da função "cameraRay()", onde o primeiro parâmetro é a origem do ray(que de certa forma também é o seu tamanho) e o segundo é a sua direção, no exemplo é o eixo negativo de Y. Depois passamos esse ray para o RaySceneQuery que é quem irá utilizar-lo.

    RaySceneQueryResult &result = raySceneQuery->execute();
    RaySceneQueryResult::iterator itr;

 

Cria-se uma variável "result" que guardará as informações da execução do ray, ou seja, após o ray percorrer a cena, através do método "execute()", a váriavel "result" terá uma lista com os elementos da cena em que o ray tocou. Um iterator é também criado para que a lista seja percorrida.

    //testa o resultado da iteração do charRay com a cena
    for (itr = result.begin(); itr != result.end(); itr++)
    {

 

Para percorrer toda a lista usamos um for, que vai incrementado a posição na lista até encontrar o final dela(itr != result.end()).

    if (itr->worldFragment)
    {
    	Real terrainHeight = itr->worldFragment->singleIntersection.y;
    	if ((terrainHeight + 60.0f) > camPos.y)
    		camera->setPosition( camPos.x, terrainHeight + 60.0f, camPos.z );
    	break;
    }

 

Se o elemento atual da lista dos que o ray tocou durante a execução for o terreno. Guarda-se o valor da posição, em que o ray tocou no terrno, no eixo Y na variável "terrainHeight".

E depois verifica se a posição do toque mais 60(sessenta) unidades é maior que a altura atual da câmera. Se for, então significa que a câmera está a uma altura maior que a desejada, dessa forma colocamos a câmera novamente 60 unidades acima do terreno(com "setPosition()") e sai do loop(break).

Selecionar e arrastar um objeto com o mouse

O próximo exemplo mostrará como marcar um determinado ponto clicado com o mouse no terreno e mover um objeto selecionado. Vá até o método "onLeftPressed()" para inserir o os códigos do exemplo.

    Ray mouseRay = camera->getCameraToViewportRay(e.state.X.abs/float(e.state.width),
            e.state.Y.abs/float(e.state.height));
    raySceneQuery->setRay(mouseRay);

 

De inicio criamos um ray de forma especial utilizando a função "getCameraToViewportRay()". Ela cria um ray a partir da localização do ponteiro do mouse no Viewport, com direção perpendicular a do Viewport. Os dois parâmetros dessa função são exatamente a posição X e Y da origem do ray. E com "setRay()" passamos esse ray para o "RaySceneQuery()".

    RaySceneQueryResult &result = raySceneQuery->execute();
    RaySceneQueryResult::iterator itr;

 

O trecho acima é semelhante ao exemplo anterior neste texto.

for ( itr = result.begin(); itr != result.end(); itr++ )
{
  //verifica se o mouseRay colidiu com o terreno
  if (itr->worldFragment)
  {
      //coloca o knot na posição para onde o char vai
      scene_mgr->getSceneNode("knotNode")->setPosition(itr->worldFragment->singleIntersection);
      scene_mgr->getSceneNode("knotNode")->setVisible(true);
  }
}

 

Percorrendo a lista com os elementos tocados pelo ray, com o for, e verificando se o elemento atual da lista é o terreno. Em caso verdadeiro, colocamos a malha knot na posição do clique do mouse no terreno e tornamos ela visivel. Execute o exemplo e teste.

Incluir imagem com o knot no terreno.

bool AplicationListener::mouseMoved( const OIS::MouseEvent &e ){
  if(e.state.Z.rel != 0)
    naviMgr->injectMouseWheel(e.state.Z.rel);


  if(mouseDirDown)
  {
    Ray mouseRay = camera->getCameraToViewportRay(e.state.X.abs/float(e.state.width),
        e.state.Y.abs/float(e.state.height));
    raySceneQuery->setRay(mouseRay);

    RaySceneQueryResult &result = raySceneQuery->execute();
    RaySceneQueryResult::iterator itr;

    for ( itr = result.begin(); itr != result.end(); itr++ )
    {
    	//verifica se o mouseRay colidiu com o terreno
    	if (itr->worldFragment)
    	{
    		//coloca o knot na posição para onde o char vai
    		scene_mgr->getSceneNode("knotNode")->setPosition(itr->worldFragment
                    ->singleIntersection);
    		scene_mgr->getSceneNode("knotNode")->setVisible(true);
    	}
    }
  }

  return naviMgr->injectMouseMove(e.state.X.abs, e.state.Y.abs);
}

 

O trecho de código acima adicionado no método "mouseMoved()" será resposável pelo arrasto do objeto. Observe que ele é semelhante ao adicionado um pouco acima neste tutorial, mas no método "onLeftPressed()". Mas nesse caso ele será acionado quando o mouse se movimentar na tela e quando o botão direito do mouse for pressionado( if(mouseDirDown) ). Teste.

Essa mesma lógica pode ser utilizada para arrastar algum outro objeto, selecionando-o no com um clique do mouse(onLeftPressed) e depois arrastando-o com o botão direito do mouse(MouseMoved).

if (strstr(itr->movable->getName().c_str(),"cubo") != 0)

 

Caso o elemento que se queira selecionar seja um SceneNode, utilizamos a váriavel movable, e apartir dela conseguimos obter outras informações. O nome da Entity, por exemplo, é obtido apartir da função getName() e o nome do SceneNode com o método getParentNode()->getName(). A função c_str() serve para converter o formato de string da Ogre para o formato aceito pela strstr().

Apartir do nome obtido podemos comparar o nome do objeto selecionado com o desejado. No trecho acima verificamos se o nome da Entity selecionada é cubo, usando o método strstr(). Onde os parâmetros são as strings que desejamos comparar. Caso sejam iguais, irá retornar um valor diferente de zero, se forem diferentes retornará zero.

Italo Mendes
italo.ribeiro@gmail.com

<< Voltar Índice Próxima >>