CLinearColorGradient

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
gerdb
Posts: 194
Joined: Wed Dec 02, 2009 8:21 pm
Location: Dresden, Germany

CLinearColorGradient

Post by gerdb »

new class CLinearColorGradient

can contain any number of stop-colors creating the gradient with values from 0 to 1.

every call to addColor sorts the new color within the color-array by its float value t, so the interpolation should speed up.

it can create images of any size.

Example gradient with 5 colors

Image

Code: Select all

    video::CLinearColorGradient Gradient;
    Gradient.addColor( video::SColor(255,255,0,0), 1.0f );
    Gradient.addColor( video::SColor(255,0,0,255), 0.0f);
    Gradient.addColor( video::SColor(255,0,200,0), 0.35f );
    Gradient.addColor( video::SColor(255,255,255,0), 0.75f );
    Gradient.addColor( video::SColor(255,255,255,255), 0.5f );
    video::IImage* tmp = Gradient.createImage( 512, 256, false);
    if (tmp)
    {
        driver->writeImageToFile( tmp, "test_linear_color_gradient_h.png");
        tmp->drop();
        tmp = 0;
    }
 
Code::Blocks Project

http://benjaminhampe.be.ohost.de/Irrlic ... nt.tar.bz2

CLinearColorGradient.h

Code: Select all

 
// Copyright (C) 2002-2013 BenjaminHampe@gmx.de
// This file intends to advance the caps of the "Irrlicht Engine"
// For conditions of distribution and use, see copyright notice in irrlicht.h
 
#ifndef __IRR_C_LINEAR_COLOR_GRADIENT_H__
#define __IRR_C_LINEAR_COLOR_GRADIENT_H__
 
// #include <irrlicht.h>
#include <irrTypes.h>
#include <irrString.h>
#include <irrArray.h>
#include <SColor.h>
#include <EMaterialTypes.h>
#include <IImage.h>
#include <IVideoDriver.h>
 
namespace irr
{
    namespace video
    {
        core::stringc toString( const SColor& color );
 
        core::stringc toString( const SColorf& color );
 
        ////////////////////////////////////////////////////////////////////////////
        ///     class CLinearColorGradient
        ////////////////////////////////////////////////////////////////////////////
 
        class CLinearColorGradient : public IReferenceCounted
        {
        public:
 
            CLinearColorGradient(); // const SColor& startColor = SColor(0,0,0,0), const SColor& endColor = SColor(255,255,255,255) );
 
            virtual ~CLinearColorGradient();
 
            virtual core::stringc getName( ) const;
 
            virtual CLinearColorGradient& setName( const core::stringc& name );
 
            virtual SColor getColor( f32 t ) const;
 
            virtual SColorf getColorf( f32 t ) const;
 
            virtual u32 getColorCount() const;
 
            virtual CLinearColorGradient& clear();
 
            virtual CLinearColorGradient& addColor( const SColor& stopColor, f32 t = 0.5f );
 
            virtual CLinearColorGradient& addColor( const SColorf& stopColor, f32 t = 0.5f );
 
            virtual CLinearColorGradient& setColor( u32 index, const SColor& stopColor, f32 t = 0.5f );
 
            virtual CLinearColorGradient& setColor( u32 index, const SColorf& stopColor, f32 t = 0.5f );
 
            virtual bool isTransparent( ) const;
 
            virtual E_MATERIAL_TYPE getMaterialType( ) const;
 
            virtual ECOLOR_FORMAT getColorFormat( ) const;
 
            virtual core::stringc toString( ) const;
 
            virtual IImage* createImage( u32 w, u32 h, bool bVertical = false ) const;
 
        private:
 
            struct MyColorStruct
            {
                SColorf Color;
                f32 Position;           // in range 0..1
            };
 
            core::stringc Name;
            core::array<MyColorStruct> Colors;
        };
 
    } // end namespace video
 
} // end namespace irr
 
#endif // __IRR_C_LINEAR_COLOR_GRADIENT_H__
 
CLinearColorGradient.cpp

Code: Select all

 
// Copyright (C) 2002-2013 BenjaminHampe@gmx.de
// This file intends to advance the caps of the "Irrlicht Engine"
// For conditions of distribution and use, see copyright notice in irrlicht.h
 
#include "CLinearColorGradient.h"
 
//#include <irrlicht.h>
#include <irrTypes.h>
#include <irrString.h>
#include <irrArray.h>
#include <SColor.h>
#include <EMaterialTypes.h>
#include <IImage.h>
#include <IVideoDriver.h>
#include <../source/Irrlicht/os.h>
#include <../source/Irrlicht/CImage.h>
 
namespace irr
{
namespace video
{
 
core::stringc toString( const SColor& color )
{
    core::stringc s("SColor(");
    s += color.getAlpha();
    s += ",";
    s += color.getRed();
    s += ",";
    s += color.getGreen();
    s += ",";
    s += color.getBlue();
    s += ")";
    return s;
}
 
core::stringc toString( const SColorf& color )
{
    core::stringc s("SColorf(");
    s += color.getAlpha();
    s += ",";
    s += color.getRed();
    s += ",";
    s += color.getGreen();
    s += ",";
    s += color.getBlue();
    s += ")";
    return s;
}
 
CLinearColorGradient::CLinearColorGradient() // const SColor& startColor, const SColor& endColor)
: Name("")
{
    #if _DEBUG
        os::Printer::log( "CLinearColorGradient::ctr()", ELL_INFORMATION );
    #endif // _DEBUG
//  addColor( startColor, 0.0f );
//  addColor( endColor, 1.0f );
}
 
CLinearColorGradient::~CLinearColorGradient()
{
    #if _DEBUG
        os::Printer::log( "CLinearColorGradient::dtr()", ELL_INFORMATION );
    #endif // _DEBUG
    clear();
}
 
CLinearColorGradient& CLinearColorGradient::clear()
{
    Colors.clear();
}
 
 
CLinearColorGradient& CLinearColorGradient::addColor( const SColor& stopColor, f32 t )
{
    return addColor( video::SColorf(stopColor), t);
}
 
CLinearColorGradient& CLinearColorGradient::addColor( const SColorf& stopColor, f32 t )
{
    #if _DEBUG
        os::Printer::log( "CLinearColorGradient::addColor()", ELL_INFORMATION );
    #endif // _DEBUG
 
    MyColorStruct entry;
    entry.Color = stopColor;
    entry.Position = core::clamp<f32>( t, 0.0f, 1.0f );
 
    const u32 c = getColorCount();
 
    if (c==0)
    {
        Colors.push_back( entry );
    }
    else // if (c>0)
    {
        bool found_greater_t = false;
        u32 greater_t_index = 0;
 
        for (u32 i=0; i<c; i++)
        {
            if ( core::equals(t, Colors[i].Position) )
            {
                return *this;   // dont insert if any t does equal one of array-elements
            }
            else if ( Colors[i].Position > t )
            {
                found_greater_t = true;
                greater_t_index = i;
                break;
            }
        }
 
        if (found_greater_t)
            Colors.insert( entry, greater_t_index);
        else
            Colors.push_back( entry );
    }
    return *this;
}
 
CLinearColorGradient& CLinearColorGradient::setColor( u32 index, const SColor& stopColor, f32 t )
{
    const u32 size0 = getColorCount();
    if (index >= size0)
        return *this;
 
    Colors[index].Color = video::SColorf(stopColor);
    Colors[index].Position = core::clamp<f32>( t, 0.0f, 1.0f );
    return *this;
}
 
CLinearColorGradient& CLinearColorGradient::setColor( u32 index, const SColorf& stopColor, f32 t )
{
    const u32 size0 = getColorCount();
    if (index >= size0)
        return *this;
 
    Colors[index].Color = stopColor;
    Colors[index].Position = core::clamp<f32>( t, 0.0f, 1.0f );
    return *this;
}
 
 
SColor CLinearColorGradient::getColor( f32 t ) const
{
    return getColorf(t).toSColor();
}
 
SColorf CLinearColorGradient::getColorf( f32 t ) const
{
 
//  #if _DEBUG
//      os::Printer::log( "CLinearColorGradient::getColor()", ELL_INFORMATION );
//  #endif // _DEBUG
 
    const u32 colorCount = getColorCount();
 
    // find maximum and minimum neighbors
 
    bool found_greater_t = false;
    u32 greater_t_index = 0;
 
    for (u32 i=0; i<colorCount; i++)
    {
        if ( core::equals(t, Colors[i].Position) )
        {
            return Colors[i].Color;
        }
        else if (Colors[i].Position > t)
        {
            found_greater_t = true;
            greater_t_index = i;
            break;
        }
    }
 
    if (!found_greater_t)
    {
        return Colors[colorCount-1].Color;
    }
 
    // interpolate between prev and next neighbor color
    const SColorf& A = Colors[greater_t_index-1].Color;
    const SColorf& B = Colors[greater_t_index].Color;
    const f32 min_t = Colors[greater_t_index-1].Position;
    const f32 max_t = Colors[greater_t_index].Position;
    const f32 dx = core::abs_<f32>( (t - min_t) / (max_t - min_t) );
    const f32 fa = A.getAlpha() + dx*( B.getAlpha() - A.getAlpha() );
    const f32 fr = A.getRed() + dx*( B.getRed() - A.getRed() );
    const f32 fg = A.getGreen() + dx*( B.getGreen() - A.getGreen() );
    const f32 fb = A.getBlue() + dx*( B.getBlue() - A.getBlue() );
 
    return video::SColorf( fr,fg,fb,fa );
}
 
u32 CLinearColorGradient::getColorCount() const
{
    return Colors.size();
}
 
core::stringc CLinearColorGradient::getName( ) const
{
    return Name;
}
 
CLinearColorGradient& CLinearColorGradient::setName( const core::stringc& name )
{
    Name = name;
    return *this;
}
 
bool CLinearColorGradient::isTransparent( ) const
{
    bool bTransparent = false;
    u32 i=0;
    u32 c=getColorCount();
 
    while (i<c)
    {
        if (Colors[i].Color.getAlpha() < 1.0f) // not 255
        {
            bTransparent = true;
            break;
        }
        i++; // dont ever forget again foo
    }
 
    return bTransparent;
}
 
E_MATERIAL_TYPE CLinearColorGradient::getMaterialType( ) const
{
    if (isTransparent())
        return EMT_TRANSPARENT_ALPHA_CHANNEL;
    else
        return EMT_SOLID;
}
 
ECOLOR_FORMAT CLinearColorGradient::getColorFormat( ) const
{
    if (isTransparent())
        return ECF_A8R8G8B8;
    else
        return ECF_R8G8B8;
}
 
// old
//IImage*   CLinearColorGradient::createImage( IVideoDriver* driver, u32 w, u32 h, bool bVertical ) const
 
// new
IImage* CLinearColorGradient::createImage( u32 w, u32 h, bool bVertical ) const
{
    #if _DEBUG
    os::Printer::log( "CLinearColorGradient::createImage()", ELL_INFORMATION );
    #endif // _DEBUG
 
    if (w==0 || h==0)
    {
        os::Printer::log( "Can't create Image of size zero.", ELL_ERROR );
        return 0;
    }
 
    // old
    //IImage* tmp = driver->createImage( this->getColorFormat(), core::dimension2du(w,h) );
 
    // new
    IImage* tmp = (IImage*)new CImage( this->getColorFormat(), core::dimension2du(w,h) );
    if (!tmp)
    {
        os::Printer::log( "Could not create CImage", ELL_ERROR );
        return 0;
    }
 
    const core::dimension2du& ImageSize = tmp->getDimension();
 
//  #if _DEBUG
//      // os::Printer::log( core::sprintf("Created new CImage(%d,%d,%d)", ImageSize.Width, ImageSize.Height, tmp->getBitsPerPixel()).c_str(), ELL_INFORMATION );
//      // os::Printer::log( "start filling", ELL_INFORMATION );
//  #endif // _DEBUG
 
    // vertical filling
    if ( bVertical )
    {
        const f32 fy = 1.0f / (f32)h;
 
        for (u32 y=0; y<ImageSize.Height; y++)
        {
            video::SColor color = getColor( fy*y );
 
            for (u32 x=0; x<ImageSize.Width; x++)
            {
                tmp->setPixel( x,y,color );
            }
        }
 
    }
    // horizontal filling
    else
    {
        const f32 fx = 1.0f / (f32)w ;
 
        for (u32 x=0; x<ImageSize.Width; x++)
        {
            video::SColor color = getColor( fx*x );
 
            for (u32 y=0; y<ImageSize.Height; y++)
            {
                tmp->setPixel( x,y,color );
            }
        }
    }
 
//  #if _DEBUG
//      os::Printer::log( "finish image filling with CLinearColorGradient", ELL_INFORMATION );
//  #endif // _DEBUG
 
    return tmp;
}
 
core::stringc CLinearColorGradient::toString( ) const
{
    core::stringc s("CLinearColorGradient[");
 
    // print number of colors used
    u32 c = getColorCount();
    s += (s32)c;
 
    // print name
    if (Name.size()>0)
    {
        s += Name;
        s += ", ";
    }
 
    // print
    s += "] = {\n";
 
    // print colors
    for (u32 i=0; i<c; i++)
    {
        s += "\t{ ";
        s += video::toString( Colors[i].Color.toSColor() );
        s += ", ";
        s += Colors[i].Position;
        s += " }";
        if (i<c)
        {
            s += ",";
        }
        s += "\n";
    }
 
    // print
    s += "};\n";
    return s;
}
 
//IImage* createImageFromLinearColorGradient( u32 w, u32 h, bool bVertical, CLinearColorGradient* gradient, bool deleteGradient)
//{
//  if (!gradient)
//      return 0;
//
//  IImage* tmp = gradient->createImage( w,h,bVertical );
//
//  if (deleteGradient)
//  {
//      delete gradient;
//  }
//
//  return tmp;
//}
 
} // end namespace video
 
} // end namespace irr
 
main.cpp

Code: Select all

/**
 
    Example 099: tests new class CLinearColorGradient
 
    Copyright (C) 2002-2013 BenjaminHampe@gmx.de
    This file intends to advance the caps of the "Irrlicht Engine"
    For conditions of distribution and use, see copyright notice in irrlicht.h
 
*/
 
#ifdef _MSC_VER
    #pragma comment(lib, "Irrlicht.lib")
#endif
 
#include <irrlicht.h>
 
#include "CLinearColorGradient.h"
 
using namespace irr;
 
//! this draw a gradient with lines, very slow, better use the image creation, convert to texture, and draw textured quad or similar
 
inline void drawGradient(
    video::IVideoDriver* driver, video::CLinearColorGradient* gradient, const core::recti& rectangle)
{
    if (!driver)
        return;
 
    if (!gradient)
        return;
 
    const s32 x1 = rectangle.UpperLeftCorner.X;
    const s32 y1 = rectangle.UpperLeftCorner.Y;
    const s32 w = rectangle.getWidth();
    const s32 h = rectangle.getHeight();
    const f32 dt = 1.0f / (f32)h;
 
    for (u32 y = 0; y<h; y++)
    {
        f32 t = 1.0f - dt*(f32)y;
        driver->draw2DLine( core::position2di(x1,y1+y), core::position2di(x1+w,y1+y), gradient->getColor(t) );
    }
 
}
 
//! main entry
 
int main()
{
    SIrrlichtCreationParameters deviceParameter;
    deviceParameter.LoggingLevel = ELL_INFORMATION;
    deviceParameter.DriverType = video::EDT_OPENGL;
    deviceParameter.Fullscreen = false;
    deviceParameter.Doublebuffer = true;
    deviceParameter.WindowSize = core::dimension2du(1024,768);
    deviceParameter.Bits = 24;
    deviceParameter.AntiAlias = video::EAAM_QUALITY;
    deviceParameter.Vsync = false;
    deviceParameter.HighPrecisionFPU = true;
//  deviceParameter.Stencilbuffer = false;
//  deviceParameter.WithAlphaChannel = false;
//  deviceParameter.AntiAlias = video::EAAM_OFF;
//  deviceParameter.ZBufferBits = 16;
 
    IrrlichtDevice *device = createDeviceEx( deviceParameter );
    if (device == 0)
        return 1;
 
    device->setWindowCaption(L"Irrlicht Example 99 - CLinearColorGradient");
 
    ILogger* logger = device->getLogger();
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* smgr = device->getSceneManager();
    gui::IGUIEnvironment* env = device->getGUIEnvironment();
    const core::dimension2du screen = driver->getScreenSize();
 
    //driver->setTextureCreationFlag( video::ETCF_CREATE_MIP_MAPS, false);
    //driver->setTextureCreationFlag( video::ETCF_ALWAYS_32_BIT, true);
    //driver->setTextureCreationFlag( video::ETCF_ALLOW_NON_POWER_2, true);
 
    smgr->getParameters()->setAttribute( scene::DEBUG_NORMAL_LENGTH, 0.3f );
    smgr->getParameters()->setAttribute( scene::DEBUG_NORMAL_COLOR, video::SColor(255,255,0,0) );
 
    // device->run(); // does not help
    // TestColorGradient test0( device->getLogger(), driver );
 
    logger->log( "Test CLinearColorGradient", ELL_INFORMATION );
    logger->log( "" );
 
    video::CLinearColorGradient Gradient;
 
    logger->log( "add some colors to gradient", ELL_INFORMATION );
    logger->log( "" );
 
    Gradient.addColor( video::SColor(255,255,0,0), 1.0f );
    Gradient.addColor( video::SColor(255,0,0,255), 0.0f);
    Gradient.addColor( video::SColor(255,0,200,0), 0.35f );
    Gradient.addColor( video::SColor(255,255,255,0), 0.75f );
    Gradient.addColor( video::SColor(255,255,255,255), 0.5f );
 
    logger->log( "Now let the gradient print its internal data", ELL_INFORMATION );
    logger->log( "" );
 
    logger->log( Gradient.toString().c_str(), ELL_INFORMATION);
 
    logger->log( "Now test the getColor() function in the range from 0 to 1 and step-value 0.05", ELL_INFORMATION );
    logger->log( "" );
 
    const u32 count = 20;
    const f32 step = core::reciprocal( (f32)count ); // 1:20 == 0.05
 
    for (u32 i=0; i<=count; i++)
    {
        const f32 t = step*i;
        core::stringc s( "CLinearColorGradient.getColor(" ); s+=t; s+=") = ";
        s += video::toString( Gradient.getColor(t) );
        logger->log( s.c_str(), ELL_INFORMATION);
    }
 
    logger->log( "" );
    logger->log( "Now create two images, one with horizontal gradient, one with vertical gradient.", ELL_INFORMATION );
    logger->log( "" );
 
    video::IImage* tmp = 0;
 
    // create vertical gradient image
    tmp = Gradient.createImage( 512, 1024, true);
    if (tmp)
    {
        driver->writeImageToFile( tmp, "test_linear_color_gradient_v.png");
        tmp->drop();
        tmp = 0;
    }
 
    // create horizontal gradient image
    tmp = Gradient.createImage( 1024, 512, false);
    if (tmp)
    {
        driver->writeImageToFile( tmp, "test_linear_color_gradient_h.png");
        tmp->drop();
        tmp = 0;
    }
 
    /*
        create camera
    */
//  SKeyMap KeyMapArray[6];
//  KeyMapArray[0].Action = EKA_MOVE_FORWARD;
//  KeyMapArray[1].Action = EKA_MOVE_BACKWARD;
//  KeyMapArray[2].Action = EKA_STRAFE_LEFT;
//  KeyMapArray[3].Action = EKA_STRAFE_RIGHT;
//  KeyMapArray[4].Action = EKA_CROUCH;
//  KeyMapArray[5].Action = EKA_JUMP_UP;
//  KeyMapArray[0].KeyCode = KEY_KEY_W;
//  KeyMapArray[1].KeyCode = KEY_KEY_S;
//  KeyMapArray[2].KeyCode = KEY_KEY_A;
//  KeyMapArray[3].KeyCode = KEY_KEY_D;
//  KeyMapArray[4].KeyCode = KEY_KEY_C;
//  KeyMapArray[5].KeyCode = KEY_SPACE;
//
//  smgr->addCameraSceneNodeFPS(
//      smgr->getRootSceneNode(), 100.0f, .1f, -1, KeyMapArray, 6, false, 1, false, true );
//
//  scene::ICameraSceneNode* cam = smgr->getActiveCamera();
//  if (cam)
//  {
//      cam->setNearValue( 1.0f );
//      cam->setFarValue( 100000.0f );
//      cam->setPosition( core::vector3df(35,12,-180) );
//      cam->setTarget( core::vector3df(32,-9,2) );
//  }
 
    /*
        main loop
    */
 
//  scene::CAudioEQSceneNode eq( "../../media/audio/getout.ogg", smgr, smgr->getRootSceneNode(), -1,
//      core::vector3df(0,0,0),core::vector3df(0,0,0),core::vector3df(1,1,1));
//
//  eq.setScale( core::vector3df( 40,10,40) );
 
    u32 frames=0;
    while(device->run())
    {
        driver->beginScene(true, true, video::SColor(255,100,100,100));
 
        smgr->drawAll();
        env->drawAll();
 
        // this draws the gradient
        drawGradient( driver, &Gradient, core::recti(screen.Width/2, 0, screen.Width-1, screen.Height-1) );
 
        driver->endScene();
 
        if (++frames==100)
        {
            core::stringw str = L"GeometryCreator Example using Irrlicht Engine [";
            str += driver->getName();
            str += L"] FPS(";
            str += (s32)driver->getFPS();
            str += L"), Tris(";
            str += (s32)driver->getPrimitiveCountDrawn();
            str += L")";
            irr::scene::ICameraSceneNode* camera = smgr->getActiveCamera();
 
            if (camera)
            {
                str += L", Cam pos(";
                str += irr::core::round32(camera->getPosition().X);
                str += L", ";
                str += irr::core::round32(camera->getPosition().Y);
                str += L", ";
                str += irr::core::round32(camera->getPosition().Z);
                str += L"), eye(";
                str += irr::core::round32(camera->getTarget().X);
                str += L", ";
                str += irr::core::round32(camera->getTarget().Y);
                str += L", ";
                str += irr::core::round32(camera->getTarget().Z);
                str += L")";
            }
 
            device->setWindowCaption(str.c_str());
            frames=0;
        }
    }
 
    device->drop();
 
    return 0;
}
CuteAlien
Admin
Posts: 9646
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: CLinearColorGradient

Post by CuteAlien »

Nice looking. But some idea for improvement: Don't sort on addColor. Instead have an own function for sort. People might want to prefer using colors in another order.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
gerdb
Posts: 194
Joined: Wed Dec 02, 2009 8:21 pm
Location: Dresden, Germany

Re: CLinearColorGradient

Post by gerdb »

Hi,

thx for interest, i think the class is not complete yet, like setColor which does not sort anything,

so a separate sort function would be needed ofcourse, but i didnt got it working yesterday ( i have problems with sort-algos in general :-) )

BUT: What do you mean with sorting in another order? I sort ascending from 0 to 1.

For now i can assure that the current state is working well for my current FFT Spectrum Analyzer

Image

Here the gradient goes from blue (0) over green (0.5) to red (1)
CuteAlien
Admin
Posts: 9646
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: CLinearColorGradient

Post by CuteAlien »

What I mean is - you might want to see the result going from blue to red to green instead or start with red, etc, so why only allow one fixed order? Make sorting an independent function and it's automatically more flexible (and then you can just call that function whenever you want sorting). And yeah - means you have to sort the complete array each time - that's a little easier to do in STL than in Irrlicht (in Irrlicht arrays only sort by operator <, while in stl the sorting can use any operator that returns a bool so you can set custom sort functions).
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
gerdb
Posts: 194
Joined: Wed Dec 02, 2009 8:21 pm
Location: Dresden, Germany

Re: CLinearColorGradient

Post by gerdb »

Hi,

you can reverse the coloring order simply by using

getColor( 1.0f - value ) instead of getColor( value ).

Nontheless i will make the sort-function extra so others can overload or similar.
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: CLinearColorGradient

Post by chronologicaldot »

Nice work!

Funny, I wrote something similar awhile back, but my code simply creates a constant texture (so the result can be rendered faster, or at least that was the idea).
Post Reply