Este blog esta em reforma no momento.

Arvores da floresta de Aracne

Criando árvores gigantes para a cena da Aracne.
A produção da malha textura da árvore esta em andamento, o primeiro passo foi pegar uma textura real de árvore...
Lugar da foto no Google Maps
e transformar em textura ladrilhável com a ferramenta clone e o filtro de "Tornar encaixável".

Dei também um acabamento de desenho à árvore. O processo inclui saturação, posterização e sobreposição de camadas.

O segundo passo foi criar um plano, multiplica-lo com Alt+D (clone). Setar a UV para pegar todo o quadrado e depois sub-dividilo em centenas de vezes. Agora o relevo é feito com o Sculpt.

Já as pequenas trepadeiras foram acertadas no GIMP com auxílio do normalmap plugin.

A imagem gerada no GIMP foi usada no material dentro do Blender, na hora do bake ele é incorporado ao resultado final. Assim temos um mapeamento muito rico com poucos poligonos.

As folhas e galhos menores serão feitos com textura. Do mesmo modo que no Yo Frankie.
Minha árvore esta virando um Frankenstein, mas usar imagens de perto é tradição des-de as primeiras tentativas.

O tipo de mata
Bom, a ideia é criar um ambiente denso, sombrio e de árvores altas. Logicamente tenho dificuldade em achar um lugar como esse como referência.
Ao olhar árvores altas vê-se que os galhos ficam em cima mesmo. Tomei a liberdade de experimentar os modelos com galhos saindo do meio, assim posso utiliza-los para deixar o cenário mais complexo. Podendo Cibele caminhar sobre as árvores.
A árvore é composta de três malhas, a principal, os galhos intermediários e as folhas. Em proporções reais ela tem 40 metros.

Uma mata brasileira seria um problema mesmo para GPUs modernas.
Tentarei posteriormente ver se consigo usar um sistema de carregamento híbrido da gamekit com o carregamento de arquivo mesh normal da OGRE para usufruir dos recursos de LOD da OGRE.

A divisão do tronco da árvore em 2 malhas é para facilitar a física, os galhos menores não devem ser acessíveis (espero).

Páu Mulato - Deve ter uns 25~30 m.

Escrevendo roteiro e pensando em I.A.


Eu aqui pensando reversamente: o personagem, a ação e oque precisaria em forma de código.
Lembra um pouco aquela dos “ditos populares em php”. Mas em fim.

Sou Aracne, não lembro qual nome usei no passado antes de ser esta forma que me conheço agora. Não importa mais.

class CAracne (Char)
CAracne.passado = nil
CAracne.nome = “Aracne”
CAracne.idade = “?”

Aracne = CAracne.new()

Escuro, húmido e gostoso. O sol lá fora não me incomoda, estou feliz na sombra. Ouço os pássaros e os insetos da floresta. Apenas a harmonia escura a sombra. O cheiro do musgo, da casca da árvore e da flor, o cheiro do que me serviu de alimento.... a harmonia me afasta a solidão.

Aracne:attachSensor( Vista:new() )
Aracne:attachSensor( Audio:new() )
Aracne:attachSensor( Olfato:new() )
-- regula parãmetros para o personagem
Aracne.olfato.dist = 5
Aracne.audio.dist = 30 -- 30 metros

-- procura sombra, as áreas de sombra já estão demarcadas
-- no terreno. Escrita como função genérica para ser reaproveitada
-- por outros personagens.
function Char:procuraSombra( )
    local pos = self.phys.position
    local initialDist = 50
    sombra = nil
    for el in mapa:getElementByGroup(“sombra”)
        if el:dist( self ) < initialDist then
            initialDist = el:dist(self)
            sombra = el
        end
    end
    return sombra
end

Mas... que som é este? Se esgueira, é pesado, não é rato, nem coelho. Alguém talves? Comida para mais meses ou um sonho que me despertou?
É cheiro de homem... não tem algo a mais nele me incomodando. É cheiro de fêmea. Humpf, apenas comida.
Está chegando perto,  mais perto, quase perto, perto, AGORA!

function CAracne:main()
    if self.repouso then
        if self.audio:proximidade() or self.visao:proximidade() or self.olfato:proximidade() then
            self.epouso = false
        end
    else
        if self.primeiroAtaque then
            self.animation.set(“camuflagem”)
            local dist, char = minimo( {self.audio.alvo(), self.visao.alvo(), self.olfato.alvo() } )
            if dist <= self.distanciaDeBote then self.bote( char ) end
        else
            self.cacar()
    end
    self.continueAcao() --executa a ação escolhida ou continua a ação corrente que pode ser uma acao de parado, ou movimento
end

Estudando o Gamekit OGRE

http://code.google.com/p/gamekit/

Este projeto me chama atenção, pois já implementa OGRE e Lua numa tacada só.
 
Possui um grupo completo de 7 integrantes em seu trabalho e uma atividade realmente alta no desenvolvimento (até o fechamento deste post eles atualizavam o Google Code todos os dias).

Não tem muita documentação no site, mas ao baixar o source ele vem junto com toda a API de Lua.

Lua
Bastante completa, e com um modelo de programação lindíssimo.
Notei até que possui um objeto de maquina de estado já pronto ^_^.

Nos exemplos que vem no doc a coisa parece bem interessante.

Demo = BaseClass(OgreKit.Engine)
function Demo:constructor()
   self.scene = self:getActiveScene()
end
function Demo:OnUpdate(delta)
         OgreKit.DebugPrint("Demo main loop running ==> " .. self.scene:getName())
end
demo = Demo()
demo:connect(OgreKit.EVT_TICK, demo, Demo.OnUpdate)

C++
Na pasta Engine tem muitos arquivos que indicam as potencialidades da engine.
gkEngine.h mostra que ele usa uma instância de OGRESingleton.
 
E o gkSceneManager parece receber um nome como parâmetro. Apesar de eu ainda não achar na API Lua onde se pode influenciar isso, já é algo promissor.


Dá pra alterar facilmente conforme minhas necessidades?
Olhando com atenção o mecanismo de carga de arquivos, vê-se que ele é bem estruturado em várias classes.
A função loadFile na verdade é apenas um manipulador caso ocorra algum erro.
gkBlendFile *gkBlendLoader::loadFile(const gkString &fname, int options, const gkString &inResourceGroup)
{
       bool resetLoad = false;
       try {

               return loadAndCatch(fname, options, inResourceGroup);
       }

Depois o arquivo é pedido emloadAndCatch:
gkBlendFile *gkBlendLoader::loadAndCatch(const gkString &fname, int options, const gkString &inResourceGroup)
{
       m_activeFile = getFileByName(fname);
       if (m_activeFile != 0)
               return m_activeFile;


       m_activeFile = new gkBlendFile(fname, inResourceGroup);


Ela tem um mecanismo de segurança para evitar que o arquivo em execução atualmente seja recarregado. Não vejo um motivo muito sustentável para essa segurança, mas trata-se de uma engine predestinada a usuário intermediário talvez por isso.

Essa classe de carregamento é na verdade um manipulador de recursos, já que nada mais é que um gerente para uma lista de arquivos.

Indo reversamente ao código acho:
bool gkBlendFile::parse(int opts)
{

       utMemoryStream fs;
       fs.open(m_name.c_str(), utStream::SM_READ);
Esse objeto utMemoryStream tem um nome sinistro pra quem procura um stream de arquivo...
Mas o prefixo indica que esta na pasta Utils então espero que seja mesmo útil XD
class utMemoryStream : public utStream
{
public:
       utMemoryStream();
       ~utMemoryStream();

       void clear(void);

       void open(const char *path, utStream::StreamMode mode);
       void open(const utFileStream &fs, utStream::StreamMode mode);
       void open(const void *buffer, UTsize size, utStream::StreamMode mode);

       bool    isOpen(void)    const   {return m_buffer != 0;}
       bool    eof(void)       const   {return !m_buffer || m_pos >= m_size;}
       UTsize  position(void)  const   {return m_pos;}
       UTsize  size(void)      const   {return m_size;}

       UTsize read(void *dest, UTsize nr) const;
       UTsize write(const void *src, UTsize nr);


       void    seek(const UTsize pos, int dir) const;

       void            *ptr(void)          {return m_buffer;}
       const void      *ptr(void) const    {return m_buffer;}

protected:

       void reserve(UTsize nr);

       char            *m_buffer;
       mutable UTsize  m_pos;
       UTsize          m_size, m_capacity;
       int             m_mode;
};
Notem o método sobrecarregado open, a criança parece "bonbadinha". Mas aqui já parece que estou encontrando as referências a stream de arquivo clássicas, o lance é subistituilas por um manipulador de buffer (que a classe já tem).
void utFileStream::open(const char *p, utStream::StreamMode mode)
{
       if (m_handle != 0 && m_file != p)
               utFileWrapper::close(m_handle);


       m_file = p;
       m_handle= utFileWrapper::open(m_file.c_str(), mode);
       if (m_handle)
       {
               if (!(mode & SM_WRITE))
                       m_size= utFileWrapper::size(m_handle);
       }
}

Vamos ao utFileWrapper, uma função grandinha:
utFileHandle utFileWrapper::open(const char *filename, int mode)
{
#if UT_PLATFORM == UT_PLATFORM_WIN32 && defined(UT_WIN32_FILE)

       DWORD dwDesiredAccess= 0;
       if (mode & utStream::SM_READ)
               dwDesiredAccess |= GENERIC_READ;
       if (mode & utStream::SM_WRITE)
               dwDesiredAccess |= GENERIC_WRITE;

       DWORD dwShareMode= 0;
       if (mode & utStream::SM_READ)
               dwShareMode |= FILE_SHARE_READ;
       if (mode & utStream::SM_WRITE)
               dwShareMode |= FILE_SHARE_WRITE;


       DWORD dwCreationDisposition= OPEN_EXISTING;
       if (mode == utStream::SM_WRITE)
               dwCreationDisposition= CREATE_ALWAYS;

       HANDLE h= ::CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, 0,
                              dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, 0);

       return h == INVALID_HANDLE_VALUE ? 0 : h;

#else
       char fm[3] = {0,0,0};

       char *mp = &fm[0];

       if (mode & utStream::SM_READ)
               *mp++ = 'r';
       else if (mode & utStream::SM_WRITE)
               *mp++ = 'w';
       *mp++ = 'b';
       fm[2] = 0;
       return fopen(filename, fm);
#endif
}
80% dela é para o manipulador no ambiente Windows (Windows não se divide em módulos bonitinhos e fáceis de organizar) oque não deve fazer muita diferênça já que a ideia é desviar para um manipulador SQLite.

Ok, deste ponto em diante é um stream de arquivo simples. Em teoria basta trocar as chamadas de arquivo por um manipulador de campo BLOB do SQLite (sqlite3_blob_open(), sqlite3_blob_read()...)

Até aqui parece humanamente fácil, pesquizei um pouco mais para ver se não havia como diminuir a possibilidade de criar buffers de dados em excesso. E aparentemente não tem sem ter um arduo trabalho que poderia se justificar em portes para o iPhone (por exemplo a segunda geração conta com 256MB de memória apenas por tanto não se pode esperar muito!)