Graphics.DspRat - lordmundi/wikidoctest GitHub Wiki
Author: Todd Heino
On this page… (hide)
- 1. Description
- 2. Installation
- 2.1 Plugin Code
- 2.2 Example Setup
This plugin demonstrates the ability to create a Runtime Adjustable Texture (RAT) within EDGE. Changing a texture at runtime is nothing new in computer graphics. However, graphics developers have a desire for optimization and often have no need for changing textures. As a result, static, pre-loaded textures have become the norm. In fact, EDGE has no method (to my knowledge) of modifying a texture for your model at runtime.
There are several motivations for a RAT within EDGE. Using RATs, one could create a monitor-like screen within your world. If your world included a manned-vehicle and you wanted to show an astronaut perspective of a mission you may want to include the same displays the astronauts would see. These displays are constantly changing in the world and would be a good application of RATs. These could be created using pre-recorded video files of what the display would look like or created within EDGE by drawing directly to the RAT. In my own exploration, I was even able to create a second EDGE world within a RAT. Complete with lighting and any models you wish to put in it.
For the purposes of ALHAT, our motivation was to load a new hazard map into the world whenever output by our sim. The basic functionality of this is demonstrated in this plugin. It will accept a path to either a TGA or PGM file and use that as a texture for a square drawn by the plugin. The plugin looks at a model named "RAT" and uses the XYZ, PYR and visible parameters to display the RAT square appropriately. Whenever it detects a change in the files modified attribute, it automatically reloads its texture to match the changes in the file. Therefore, when our sim outputs a new hazard map, it will be reflected in the world.
As this plugin is more for demonstration purposes than a ready-to-use tool, I am posting the source code files with the hope of inspiring community development. It has lots of comments attempting to explain every detail of what I was doing. If you've read through the EDGE Newbie Tutorial on here, you should be ready to understand everything that is going on in this plugin.
The plugin source code: Attach:dsp_rat.c
Last Updated: August 18, 2010, at 02:19 PM
[Show dsp_rat.c complete source code][9]
/*
File name: dsp_rat.c
Author: Todd Heino
Written: August 14, 2010
For lots of information all about how textures work in OpenGL:
http://glprogramming.com/red/chapter09.html
*/
#include "doug.h"
#include "dsp_double.h"
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
//This pdouble contains all our scene data. We use this to get information about what is going on in EDGE.
static DSS_DOUBLE_DATA *pdouble = NULL;
/*
I just made a static variable that contains the name of the texture file.
You could have put this as a parameter in your load texture functions.
*/
static const char* texFilename = "/home/theino/Desktop/diceTexture2.pgm";
//This is a openGL integer and is a unique identifier for a texture
static GLuint texName;
/*
Contains data about our EDGE node RAT. This is just a node created in a configuration file
that this plugin reads to adjust orientation of our drawn texture
*/
static DSS_NODE *RAT;
//The id of the node RAT
static int rat_id;
/*
A variable to keep track of the current version of the texture we have loaded.
Since we're reading from a file, this tells us what the modified time on that file was when we
last loaded the file into the texture.
*/
static time_t currModifiedTime;
/*
A function which draws the square that our texture is mapped to. The y and z parameters can be used
to increase the size of the object drawn.
*/
void DrawObject(double y, double z)
{
//Scale the size of our square to the parameters specified
glScaled(1, y, z);
/*
This allows our square to be seen on both sides. Normally when working with polygons in
openGL it is ok to only draw one side of the polygon. Often the other side of the polygon is
inside a 3d object and you wouldn't see the inside anyway. So when drawing a cube, openGL would
only render the outside of the cube.
*/
glDisable(GL_CULL_FACE);
//We're going to start doing textures
glEnable(GL_TEXTURE_2D);
//Active blend mode so partially transparent objects work properly.
glEnable(GL_BLEND);
//Defines what kind of blending we're doing
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
//We want the texture to replace whatever surface the polygon may have without it
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
//Use our texture defined by the id texName on whatever we're drawing next
glBindTexture(GL_TEXTURE_2D, texName);
//Draw a square
glBegin(GL_QUADS);
glTexCoord2f(1,0); glVertex3f(0,1,1);
glTexCoord2f(0,0); glVertex3f(0,-1,1);
glTexCoord2f(0,1); glVertex3f(0,-1,-1);
glTexCoord2f(1,1); glVertex3f(0,1,-1);
glEnd();
glDisable(GL_TEXTURE_2D);
}
/*
A function which reads a PGM file specified by texFilename and loads it into our texture.
PGM is a Portable Gray Map format and only specifies a single gray value for each pixel.
For our application of hazard maps, I made this gray value map to red. The texture is also set
at a constant 50% transparency.
*/
void LoadNewTexturePGM()
{
//This will contain the initial data we get from the file
char *data;
//Header information (we probably won't need a full 128)
char header[128];
//Just an intermediate variable to tokenize some of our data
char *token;
/*
This is the data that will get passed into the texture.
It undergoes some very minor processing from the data variable
*/
char *texData;
//Pointer to our file
FILE *file;
//Width height and depth for data size purposes
int fileWidth, fileHeight, fileDepth;
//Maximum gray scale number and an indexing variable
int grayScale, pixel;
file = fopen(texFilename, "rb");
//The do-while structure allows us to keep reading until we get something that isn't a comment
do
{
//Get a line
fgets(header, 128, file);
//Ignore it if it is a comment
if(header[0] != '#')
{
//P5 is the unique header of a pgm file. If this doesn't appear, we aren't looking at a pgm
if(!(header[0] == 'P' && header[1] == '5'))
{
//Not a pgm so exit
fclose(file);
printf("PGM format invalid");
exit(0);
}
}
} while(header[0] == '#');
//Another do-while looking for comments
do
{
//Get a line
fgets(header, 128, file);
//Ignore it if it is a comment
if(header[0] != '#')
{
//At this point we should get a line for the width of our file and the height
token = strtok(header," ");
fileWidth = atoi(token);
token = strtok(NULL," ");
fileHeight = atoi(token);
}
} while(header[0] == '#');
//Yet another do-while for comments
do
{
//Get a line
fgets(header, 128, file);
//Ignore it if it is a comment
if(header[0] != '#')
{
//A line containing only the maximum gray scale value
grayScale = atoi(header);
//If our gray scale value is greater than 255, each of our pixels is specified with 2 bytes
fileDepth = grayScale > 255 ? 2 : 1;
}
} while(header[0] == '#');
//Based on our height width and size of each pixel we can know how much data we need to allocate
data = malloc(fileHeight*fileWidth*fileDepth);
//Set index to 0
pixel = 0;
//Read in the rest of our data
fread(data,fileWidth*fileHeight*fileDepth,1,file);
//If our data was 2 bytes in length, lets just shrink it back into somehting that will fit into 1 byte
if(fileDepth == 2)
{
while(pixel < fileWidth*fileHeight)
{
data[pixel] = (char)(((int)data[pixel*2] + (int)data[pixel*2 + 1])/256);
pixel++;
}
}
//Our texture data is going to have 4 bytes per pixel, corresponding to rgba
texData = malloc(fileHeight*fileWidth*4);
//Set index back to 0
pixel = 0;
/*
Take our current data and put it into our texData variable.
Remember we want the gray value to map to red and our transparency to be 50% (or 127)
*/
while(pixel < fileHeight*fileWidth)
{
texData[pixel*4] = data[pixel];
texData[pixel*4 + 1] = 0;
texData[pixel*4 + 2] = 0;
texData[pixel*4 + 3] = 127;
pixel++;
}
//Bind our texture "texName". This allows us to change around certain settings of our texture.
glBindTexture(GL_TEXTURE_2D, texName);
/*
These are just parameters that define how we deal with our texture.
You can find out more about these online
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//This is the final line that actually says "make our texture"
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,fileWidth,fileHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,texData);
}
/*
A function which reads a TGA file specified by texFilename and loads it into our texture.
TGA is a format that allows specification of rgba values for each pixel. Gimp allows you to make
TGA files. It is a larger specification than PGM and this loader doesn't support everything TGA does.
Files read into this must not use compression.
*/
void LoadNewTextureTGA()
{
/*
The data variable in this case is used to take in data from the file and then modified
in the same variable to work for the texture. The header is for the header data.
*/
char *data, *header;
//Point to our file
FILE *file;
//Width height and depth for data size purposes. Pixel is an indexing variable
int fileWidth, fileHeight, fileDepth, pixel;
//This is used to set the color mode which can either be GL_RGB or GL_RGBA
unsigned int colorMode;
//This is a temporary variable used for a byte swap
char temp;
//Open our file
file = fopen(texFilename, "rb");
//Set header size ot use (may not use all of this)
header = malloc(20);
//Read the first 18 bytes of the file
fread(header, 18, 1, file);
//Just checking some bytes to make sure this is a TGA file that we can read
if (header[2] != 2 && header[2] != 10)
{
fclose(file);
printf("TGA Type Error\n");
exit(0);
}
//Skips ahead of any comments
if (header[0])
{
fseek(file, header[0], SEEK_CUR);
}
//Getting our width, height and depth. In this case depth is either 3 or 4 depending on if we use an alpha channel
fileWidth = header[13] * 256 + header[12];
fileHeight = header[15] * 256 + header[14];
fileDepth = header[16] / 8;
//If we don't have a depth of 3 or 4, we have a problem
if(fileDepth != 3 && fileDepth != 4)
{
fclose(file);
printf("File BPP Error\n");
exit(0);
}
//Set the size of the data needed to be read in
data = malloc(fileWidth*fileHeight*fileDepth);
//Read in the image data from our file
fread(data,fileWidth*fileHeight*fileDepth,1,file);
//Set index to 0
pixel = 0;
/*
TGA format specifies its colors in BGR format. The texture we are using needs it to be in RGB format.
This while-loop goes through the data and flips the r and the b for each pixel
*/
while(pixel < fileWidth*fileHeight*fileDepth)
{
temp = data[pixel];
data[pixel] = data[pixel+2];
data[pixel+2] = temp;
pixel += fileDepth;
}
//We're done reading the file, lets close it
fclose(file);
/*
I'm a fan of the ternary operator. This is just saying if fileDepth is equal to 3, then set colorMode to GL_RGB...
otherwise set it to GL_RGBA. An equivalent form would be:
if(fileDepth == 3)
{
colorMode = GL_RGB; //Alpha isn't specified
}
else
{
colorMode = GL_RGBA; //Alpha is specified
}
*/
colorMode = fileDepth==3 ? GL_RGB : GL_RGBA;
//Bind our texture "texName". This allows us to change around certain settings of our texture.
glBindTexture(GL_TEXTURE_2D, texName);
/*
These are just parameters that define how we deal with our texture.
You can find out more about these online
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//This is the final line that actually says "make our texture"
glTexImage2D(GL_TEXTURE_2D,0,colorMode,fileWidth,fileHeight,0,colorMode,GL_UNSIGNED_BYTE,data);
}
/*
This function gets called repeatedly to update the plugin. Anything you want to happen on each refresh should be put in here
*/
void DSP_UpdateCube()
{
//This contains information about the file we want to open. In this case, all we care about is the last time it was modified
struct stat fileInfo;
//Check to make sure the stat was successful
if (!stat(texFilename,&fileInfo))
{
//Is the modified time greater than the last time we updated the texture
if(currModifiedTime < fileInfo.st_mtime)
{
/*
This is just making sure that the modified time isn't equal to the current time. We want to wait a second in case
we are in the middle of writing our new texture to file.
*/
if(time(NULL) - 1 > fileInfo.st_mtime)
{
/*
We're going to call the texture functions to modify our texture, so we need to update currModifiedTime to tell
it we're making this update.
*/
currModifiedTime = fileInfo.st_mtime;
//If the last character in our texFilename is an a, we know it refers to a tga file... otherwise its pgm
if(texFilename[strlen(texFilename)-1] == 'a')
{
LoadNewTextureTGA();
}
else
{
LoadNewTexturePGM();
}
}
}
}
/*
This tells EDGE to continue doing everything else it is doing. If a plugin function does not call
ExecuteCore, execution of that stack terminates in that function. Check the wiki for more details:
Graphics.DougPluginFunctionReference#toc9
*/
DSF_ExecuteCore();
/*
I asked Frankie about BgnSceneAccess and this is what he had to say:
the BgnSceneAccess call is really a semaphore protection for when
DOUG is running with shared memory and multiple processes. It doesn't
hurt but probably wouldn't matter if you didn't have it since people
rarely do this anymore.
*/
DSF_BgnSceneAccess( DSV_scene, DSD_READ_ACCESS );
//I've also found this put be mostly unnecessary. Not entirely sure what it does.
DSF_PushDrawingState();
/*
Lets ignore lighting for now. In the DrawObject method we're telling OpenGL to draw the texture
directly to the object. Therefore the lighting would have no affect on what we're doing, so why
waste processor cycles trying
*/
glDisable( GL_LIGHTING );
/*
The next two lines are saying switch to the modelview matrix and then load in EDGE's current matrix.
We do this so our object exists in "EDGE space" rather than following the camera around.
*/
glMatrixMode( GL_MODELVIEW );
glLoadMatrixd(pdouble->DSV_modelview_matrix[0]);
/*
We translate and rotate our object to match what is specified in our RAT object. This
is something that is specified in the EDGE configuration files. Rather than assigning it
to some pre-made model, we leave it empty and just use the values that can be specified.
*/
glTranslatef( RAT->x, RAT->y, RAT->z);
glRotatef(RAT->P, 0, 1, 0 );
glRotatef(RAT->R, 1, 0, 0 );
glRotatef(RAT->Y, 0, 0, 1 );
/*
RAT->attrib contains information about the object. One of them you see in the GUI is
whether or not you want it to be visible. This is contained in the first bit of attrib,
and that is the only attribute this is checking right now. If it isn't visible, don't DrawObject.
You could extend it to implement the other things as well.
*/
if(!(RAT->attrib&1))
DrawObject( 3, 3);
//PopDrawingState matches with PushDrawingState
DSF_PopDrawingState();
//EndSceneAccess matches with BgnSceneAccess
DSF_EndSceneAccess();
}
/*
This function gets called once when first creating our plugin within the EDGE environment.
*/
DSP_InitializePlugin( DSS_PLUGIN *plugin )
{
/*
Get data about our scene and make pdouble a pointer to that data. The pointer allows us to see
any changes that may occur, without calling DSF_GetDataFromScene again.
*/
pdouble = DSF_GetDataFromScene( DSV_scene, "DSS_DOUBLE_DATA" );
/*
Checks to see if pdouble was set properly. If it wasn't write out the error message.
The main reason for this is you aren't in double precision mode, although this is default now for
EDGE, so you're unlikely to encounter this. I just kept this in here from the EDGE Newbie tutorial.
*/
if (!pdouble) {
char msg[300];
sprintf( msg, "\nWorking in single precision mode. Restart with double argument in command line: ./run_graphics -double\n");
DSF_LogWarning( msg );
return 0;
}
//Get our rat_id, so we can find specific EDGE object
rat_id = DSF_GetNodeID("RAT");
/*
Set RAT to that EDGE object. node_list is a pointer to all the EDGE objects. To get a
pointer to our specific one, just add rat_id
*/
RAT = DSV_scene->node_list + rat_id;
/*
Make our previously defined DSP_UpdateCube method occur on DSD_PLUGIN_DRAW_SCENE.
There are other options for this, and I have found it is easiest to just experiment to
determine which one you want. This one seems to occur everytime the scene is re-drawn.
Which for my simple test case is at about 500Hz.
*/
DSF_AppendPluginFunction( plugin->handle, DSP_UpdateCube, DSD_PLUGIN_DRAW_SCENE);
return 1;
}
Take a look through the source code, it should explain all the programming details through comments. If something isn't clear, feel free to post on here or send me an email.
Under Construction
[9]: javascript:toggleObj('togglecode_id0','hide','Show dsp_rat.c complete source code','Show dsp_rat.c complete source code','')