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
