ShaderMap 2 SDK Tutorial - Creating Maps

Published July 16, 2012
Advertisement
Each map in ShaderMap 2 is a plugin. The plugin is a Windows DLL with the file extension .smp. To create a map plugin you will need to download the ShaderMap 2 SDK. http://shadermap.com/downloads/ As of this tutorial I am using version 2.0.5. You will also need Visual Studio 2008 - 2010.

Once you have the SDK downloaded and unzipped navigate to the folder 'map'. Inside you will see the VS solution file called map.sln. This solution contains 3 project files. Each project builds an example map. The 3 maps demonstrate how to build:

- source maps
- maps with no inputs
- maps with inputs

For the purpose of this tutorial I will be focusing on the third project. Right click on the project 'example_3' and set it as the default project. Open the source file 'example_3.cpp' so you can follow along.

Each map plugin uses the file 'map_plugin_core.cpp' which contains all API functions for interacting with ShaderMap 2. It is included at the top of each plugin file.

Each map plugin must implement 4 functions that are prototyped in the 'map_plugin_core.cpp' file. They are:

[source]
// Initialize plugin - Called when the plugin is loaded.
// This tells ShaderMap about the plugin and sets up the plugin property controls
BOOL on_initialize(void);

// Process plugin - called each time the map needs to be regenerated - the map_id is used to gain access to the map property values.
BOOL on_process(unsigned int map_id);

// Called just before the plugin is unloaded when ShaderMap is shutdown.
// If global resources are used this is the place to release them.
BOOL on_shutdown(void);

// Called when a project is loaded - It sends the version of the plugin with which the project was saved.
// The values are arranged in the order 0,1,2,...property index.
// If the order of the property controls have changed since the version parameter then modify the index array accordingly.
void on_arrange_load_data(unsigned int version, unsigned int index_count, unsigned int* index_array);[/source]

Here is the on_initialize function:
[source]// Initialize plugin - This tells ShaderMap about the plugin and sets up the plugin property controls
// Is called when the plugin is loaded by ShaderMap
BOOL on_initialize(void)
{
// Tell ShaderMap we are starting initialize
mp_begin_initialize();

// Set version - useful for tracking property changes
// from version to version. See on_arrange_load_data()
mp_set_version(101);

// Tell ShaderMap this map plugin is type MAP
mp_set_map_type(MAP_PLUGIN_TYPE_MAP);

// Set name and description
mp_set_name(_T("Example 3 Map"));
mp_set_description(_T("An example of normal map plugin with 1 input."));

// Set the thumbnail filename. This image must be located in "APP_DIRECTORY\plugins\(x86|x64)\maps\thumbs"
// See examples of thumbnails in that folder.
// Thumbnail must be 128 x 128 pixels but thumb is much smaller at 71 x 71 and offset from top-left by 12 pixels
mp_set_thumbnail(_T("example_3.png"));

// Set default save format for the map
mp_set_default_format(MAP_FORMAT_TGA_RGB_8);

// Is a normal map
mp_enable_normal_map(TRUE);

// Add 1 input
mp_add_input(_T("Normal Map"), _T("An image with normal vectors stored per pixel."));

// Add properties controls Property Index
// Add a coodinate system property with a default coordinate system set
mp_add_property_coordsys(_T("Coord System"), MAP_COORDSYS_X_POS_RIGHT | MAP_COORDSYS_Y_POS_DOWN | MAP_COORDSYS_Z_POS_NEAR, 0); // 0

// Tell ShaderMap initialize was success - map is added
mp_end_initialize();

// Success
return TRUE;
}[/source]

The first and last API calls are mp_begin_initialize(); and mp_end_initialize(); Everything else is between those two functions.

Here we setup the version integer, the map type (source or map), the name and description, the thumbnail filename, default file save format, if the map is a normal map or not (if so then will require a coordinate system to be set), map inputs with a description, and property controls associated with the map.

Here we have set up a map that is a normal map and takes a single normal map as input. It has one property control which is a coordinate system control. In essence this map allows the user to transform the normals of the input map to a new coordinate system. But you could make it do anything (randomize normals, etc).

The on_process function is shown below:

[source]// Process plugin - called each time the map needs to be regenerated
BOOL on_process(unsigned int map_id)
{
// Local data
unsigned int thread_limit, width, height, origin, tile_type, image_type, input_coord_system, user_coord_system, i, count_i;
float x, y, z;

const unsigned char* input_pixel_array_8;
const unsigned short* input_pixel_array_16;

unsigned char* map_pixel_d_array_8;
unsigned short* map_pixel_d_array_16;


// Get suggested thread limit for plugin process - not used in this example
thread_limit = mp_get_map_thread_limit();

// -----------------

// Get size of input
width = (int)mp_get_input_width(map_id, 0);
height = (int)mp_get_input_height(map_id, 0);
if(!width || !height)
{
LOG_ERROR_MSG(map_id, _T("Invalid input size. Width or height is zero."));
return FALSE;
}
// Get origin of input
origin = mp_get_input_origin(map_id, 0);

// Get tile type of input
tile_type = mp_get_input_tile_type(map_id, 0);

// Get image type of input
image_type = mp_get_input_image_type(map_id, 0);

// As the input is a normal map - we must get its coordinate system
input_coord_system = mp_get_input_coordsys(map_id, 0);

// -----------------

// Tell ShaderMap the tile type of this map so children of this map inherit the tile type
switch(tile_type)
{
case 0:
mp_set_map_tile_type(map_id, MAP_TILE_NONE);
break;
case 1:
mp_set_map_tile_type(map_id, MAP_TILE_X);
break;
case 2:
mp_set_map_tile_type(map_id, MAP_TILE_Y);
break;
case 3:
mp_set_map_tile_type(map_id, MAP_TILE_XY);
break;
}

// -----------------

// Allocate map pixels and get pointer to input pixel array
// 8 bit
if(image_type == MAP_IMAGE_TYPE_8)
{
// Allocate 8 bit map pixels
map_pixel_d_array_8 = new (std::nothrow) unsigned char[width * height * 4];
if(!map_pixel_d_array_8)
{
LOG_ERROR_MSG(map_id, _T("Failed to allocate map pixels."));
return FALSE;
}

// Get pixel array of input 0
input_pixel_array_8 = (const unsigned char*)mp_get_input_pixel_array(map_id, 0);
}
// 16 bit
else
{
// Allocate 16 bit map pixels
map_pixel_d_array_16 = new (std::nothrow) unsigned short[width * height * 4];
if(!map_pixel_d_array_16)
{
LOG_ERROR_MSG(map_id, _T("Failed to allocate map pixels."));
return FALSE;
}

// Get pixel array of input 0
input_pixel_array_16 = (const unsigned short*)mp_get_input_pixel_array(map_id, 0);
}

// -----------------

// Get property values
user_coord_system = mp_get_property_coordsys(map_id, 0);

// -----------------

// Set the map coordsys as defined by the user
mp_set_map_coordsys(map_id, user_coord_system);

// Get number of items in the pixel arrays
count_i = width * height * 4;

// 8 bit
if(image_type == MAP_IMAGE_TYPE_8)
{
// For every pixel
for(i=0; i {
// Convert input pixel to normal
x = 2.0f * ((float)input_pixel_array_8 / 255.0f) - 1.0f;
y = 2.0f * ((float)input_pixel_array_8[i+1] / 255.0f) - 1.0f;
z = 2.0f * ((float)input_pixel_array_8[i+2] / 255.0f) - 1.0f;

// Normalize vector
normalize_vector(x, y, z);

// Compare input coordsys with user coordsys and flip values as needed
// Adjust X
if((input_coord_system & MAP_COORDSYS_X_POS_RIGHT) != (user_coord_system & MAP_COORDSYS_X_POS_RIGHT))
{ x = -x;
}
// Adjust Y
if((input_coord_system & MAP_COORDSYS_Y_POS_DOWN) != (user_coord_system & MAP_COORDSYS_Y_POS_DOWN))
{ y = -y;
}
// Adjust Z
if((input_coord_system & MAP_COORDSYS_Z_POS_NEAR) != (user_coord_system & MAP_COORDSYS_Z_POS_NEAR))
{ z = -z;
}

// Store normal as color in the map pixel array
map_pixel_d_array_8 = (unsigned char)((x + 1.0f) / 2.0f * 255.0f);
map_pixel_d_array_8[i+1] = (unsigned char)((y + 1.0f) / 2.0f * 255.0f);
map_pixel_d_array_8[i+2] = (unsigned char)((z + 1.0f) / 2.0f * 255.0f);
map_pixel_d_array_8[i+3] = input_pixel_array_8[i+3];

// Check for cancel
if(mp_is_cancel_process())
{ delete [] map_pixel_d_array_8;
return FALSE;
}
}
}
// 16 bit
else
{
// For every pixel
for(i=0; i {
// Convert input pixel to normal
x = 2.0f * ((float)input_pixel_array_16 / 65535.0f) - 1.0f;
y = 2.0f * ((float)input_pixel_array_16[i+1] / 65535.0f) - 1.0f;
z = 2.0f * ((float)input_pixel_array_16[i+2] / 65535.0f) - 1.0f;

// Normalize vector
normalize_vector(x, y, z);

// Compare user coordsys and flip values as needed
// Adjust X
if((input_coord_system & MAP_COORDSYS_X_POS_RIGHT) != (user_coord_system & MAP_COORDSYS_X_POS_RIGHT))
{ x = -x;
}
// Adjust Y
if((input_coord_system & MAP_COORDSYS_Y_POS_DOWN) != (user_coord_system & MAP_COORDSYS_Y_POS_DOWN))
{ y = -y;
}
// Adjust Z
if((input_coord_system & MAP_COORDSYS_Z_POS_NEAR) != (user_coord_system & MAP_COORDSYS_Z_POS_NEAR))
{ z = -z;
}

// Store normal as color in the map pixel array
map_pixel_d_array_16 = (unsigned short)((x + 1.0f) / 2.0f * 65535.0f);
map_pixel_d_array_16[i+1] = (unsigned short)((y + 1.0f) / 2.0f * 65535.0f);
map_pixel_d_array_16[i+2] = (unsigned short)((z + 1.0f) / 2.0f * 65535.0f);
map_pixel_d_array_16[i+3] = input_pixel_array_16[i+3];

// Check for cancel
if(mp_is_cancel_process())
{ delete [] map_pixel_d_array_8;
return FALSE;
}
}
}

// -----------------

// Create the final map using the map pixels
// 8 bit
if(image_type == MAP_IMAGE_TYPE_8)
{
if(!mp_create_map(map_id, width, height, origin, image_type, map_pixel_d_array_8))
{
LOG_ERROR_MSG(map_id, _T("Failed to create map with mp_create_map()."));
delete [] map_pixel_d_array_8;
return FALSE;
}

// Cleanup
delete [] map_pixel_d_array_8;
map_pixel_d_array_8 = 0;
}
// 16 bit
else
{
if(!mp_create_map(map_id, width, height, origin, image_type, map_pixel_d_array_16))
{
LOG_ERROR_MSG(map_id, _T("Failed to create map with mp_create_map()."));
delete [] map_pixel_d_array_16;
return FALSE;
}

// Cleanup
delete [] map_pixel_d_array_16;
map_pixel_d_array_16 = 0;
}

// Update map progress 100%
mp_set_map_progress(map_id, 100);

// Success
return TRUE;
}[/source]

The first part of the function uses API calls to get info about the input map and the property values, The next registers the tile type of the map. A pixel array is allocated based on 8 bit or 16 bit channels at the same size as the input map. The coordinate system is registered as the one the user has defined in the coordinate system property control. Depending on the image type the pixels (normals) are copied into the new pixel array and set to the new coordinate system.

At the end of the function the pixels are sent to ShaderMap using the mp_create_map() function. Which takes the map_id, size, and array of pixels in RGBA format.

To install this plugin, add the binary *.SMP file to the ShaderMap plugins directory: "SM2_DIRECTORY\plugins\(x86 or x64)\maps\"

and install the plugin thumbnail to the directory: "SM2_DIRECTORY\plugins\(x86 or x64)\maps\thumbs\"

Now the map will be available from the Advanced Map Setup page inside ShaderMap 2.
Previous Entry ShaderMap 2.0.5 Update
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement