[NeoEngine Logo]
Platform independent Open Source 3D game engine
Source - latest release

Source - weekly snapshot

Download tarball




Projects and extensions

Plan files

Useful Links

Mailing Lists

News archive

SourceForge.net Logo
Tutorial 3 - Textures
Last update: 22/Jan/2004

This tutorial covers texture loading and basic material management. New code not in the previous tutorial is in bold.

#include <neoengine/core.h>
#include <neoengine/render.h>
#include <neoengine/logstream.h>
#include <neoengine/input.h>
#include <neoengine/filemanager.h>
#include <neoengine/renderprimitive.h>
#include <neoengine/vertex.h>
#include <neoengine/polygon.h>
#include <neoengine/material.h>
#include <neoengine/texture.h>

#ifdef WIN32
#include <neodevd3d9/link.h>
#include <neodevopengl/link.h>
#include <neoicpng/link.h>

using namespace std;
using namespace NeoEngine;
filemanager.h defines the file manager class. This is the hub of the file subsystem of the engine, and allows objects to add and remove directories and packages into the virtual filesystem, and to locate files for loading and saving.

material.h defines the material class, blend modes, z buffer modes and texture layers used for specifying material capabilities of a set of geometry.

texture.h defines the texture classes, and we also include the PNG codec link header for static builds (we are loading a PNG image).

class InputListener : public InputEntity

                     InputListener( InputGroup *pkGroup ) : InputEntity( pkGroup ) {}

    virtual void     Input( const InputEvent *pkEvent );

RenderDevice      *g_pkRenderDevice    = 0;
RenderCaps         g_kRenderCaps;
LogFileSink        g_kLogFile( "neoengine.log" );
InputGroup        *g_pkInputGroup      = 0;
InputListener     *g_pkInputListener   = 0;
bool               g_bRun              = true;
VertexBufferPtr    g_pkVertexBuffer;
PolygonBufferPtr   g_pkPolygonBuffer;
MaterialPtr        g_pkMaterial;
g_pkMaterial is our material object. Materials are, just like the vertex/polygon buffers and textures, reference counted and accessed through a smart pointer.

int main( int argc, char **argv )
  neolog.SetLogThreshold( DEBUG );
  neolog.AttachSink( Core::Get()->GetStdoutSink() );
  neolog.AttachSink( &g_kLogFile );

  Core::Get()->Initialize( argc, argv );

  Core::Get()->GetFileManager()->AddPackage( "../common/data" );
To be able to locate and load our texture file, we need to tell the file manager where to look for files in the real filesystem. We add a path to the data directory for the tutorials. This will cause the file manager to index that directory and any subdirectories in the virtual filesystem the manager keeps. We can now access any file in that directory through the manager.

  if( !( g_pkRenderDevice = Core::Get()->CreateRenderDevice( "opengl" ) ) )
    goto SHUTDOWN;

  if( !g_pkRenderDevice->Open( RenderWindow( "Render Polygon & Basic Input", g_kRenderCaps, RenderResolution( 640, 480, 16 ) ) ) )
    goto SHUTDOWN;

  g_kRenderCaps     = g_pkRenderDevice->GetCaps();

  g_pkInputGroup    = new InputGroup;
  g_pkInputListener = new InputListener( g_pkInputGroup );

  g_pkVertexBuffer = g_pkRenderDevice->CreateVertexBuffer( Buffer::NORMAL, 4, &DiffuseVertex::s_kDecl );

  g_pkVertexBuffer->Lock( Buffer::WRITE );
    DiffuseVertex *pkVertex = (DiffuseVertex*)g_pkVertexBuffer->GetVertex();

    pkVertex->m_kPosition.Set( -10.0f,  5.0f, -20.0f ); 
    pkVertex->m_afTexCoord[0] = 0.0f;
    pkVertex->m_afTexCoord[1] = 0.0f;
    pkVertex->m_kPosition.Set( -10.0f, -5.0f, -20.0f ); 
    pkVertex->m_afTexCoord[0] = 0.0f;
    pkVertex->m_afTexCoord[1] = 1.0f;

    pkVertex->m_kPosition.Set(  10.0f, -5.0f, -20.0f ); 
    pkVertex->m_afTexCoord[0] = 1.0f;
    pkVertex->m_afTexCoord[1] = 1.0f;

    pkVertex->m_kPosition.Set(  10.0f,  5.0f, -20.0f ); 
    pkVertex->m_afTexCoord[0] = 1.0f;
    pkVertex->m_afTexCoord[1] = 0.0f;

  g_pkPolygonBuffer = g_pkRenderDevice->CreatePolygonBuffer( Buffer::NORMAL, 2 );

  g_pkPolygonBuffer->Lock( Buffer::WRITE );
    Polygon *pkPolygon = g_pkPolygonBuffer->GetPolygon();
    pkPolygon->v[0] = 0;
    pkPolygon->v[1] = 1;
    pkPolygon->v[2] = 2;

    pkPolygon->v[0] = 0;
    pkPolygon->v[1] = 2;
    pkPolygon->v[2] = 3;
Allocate 4 vertices and 2 polygons to build a quad. The vertices have position and texcoord data, and we set the texture coordinates to map the whole texture to the quad. Then build two triangles forming the quad.

  g_pkRenderDevice->LoadCodec( "png" );

  g_pkMaterial = new Material( "logo", 0 );

  g_pkMaterial->m_pkTexture  = g_pkRenderDevice->LoadTexture( "logo" );
First we request the texturing subsystem to load the codec to parse PNG files. We then create a new named material (we pass 0 as material manager pointer, more about this in later tutorials) and set the base texture by loading the logo.png texture. The LoadTexture method will automatically locate already loaded textures matching the request to avoid multiple copies of a texture (remember, textures are also reference counted objects in NeoEngine).

  while( g_bRun )

    g_pkRenderDevice->Clear( RenderDevice::COLORBUFFER | RenderDevice::ZBUFFER | RenderDevice::STENCILBUFFER, Color::BLACK, 1.0f, 0 );

    g_pkRenderDevice->Begin( Matrix::IDENTITY );
      RenderPrimitive kPrim;

      kPrim.m_ePrimitive      = RenderPrimitive::TRIANGLES;
      kPrim.m_pkMaterial      = g_pkMaterial;
      kPrim.m_pkVertexBuffer  = g_pkVertexBuffer;
      kPrim.m_pkPolygonBuffer = g_pkPolygonBuffer;
      kPrim.m_uiNumPrimitives = 2;

      g_pkRenderDevice->Render( kPrim );


Set the desired material in the render primitive and render 2 triangles.


  g_pkMaterial      = 0;
  g_pkVertexBuffer  = 0;
  g_pkPolygonBuffer = 0;

  Core::Get()->DeleteRenderDevice( g_pkRenderDevice );	
  delete g_pkInputListener;
  delete g_pkInputGroup;


  return 0;
Reset the material pointer to allow reference counter base class to cleanup the object when it is no longer used. Remember, this is only needed since we must make sure all resources are correctly deallocated before deleting the render device, otherwise the smart pointers will try to clean up when they run out of scope and cause problems since the engine has already been shutdown by that time.

void InputListener::Input( const InputEvent *pkEvent )
  if( ( ( pkEvent->m_iType == IE_KEYDOWN ) && ( pkEvent->m_aArgs[0].m_iData == KC_ESCAPE ) ) ||
      ( ( pkEvent->m_iType == IE_SYSEVENT ) && ( pkEvent->m_aArgs[0].m_iData == IE_SYSEVENT_KILL ) ) )
    g_bRun = false;