• Advertisement
  • entries
  • comments
  • views

About this blog

Developing ShaderMap 2

Entries in this blog

ShaderMap 2.0.7 Update

The latest update of ShaderMap brings improved normal to displacment conversion with additional controls for contrast and height. There are other new features as well including linear magnification on preview image controls, additional zoom controls, and a map control button to refresh source images from file.


Next up... ShaderMap 2 R1

I'll be rebuilding the normal editor which means implementing a layer system, adding a stencil brush for painting displacement/normals, adding shapes using a vector library, an eraser tool for rasterized layers, and line/box/oval/curve gizmos. Wish me luck.


Blending Normal Maps

ShaderMap 2 currently implements overlay and detail oriented methods (called pure rotation in SM2) for painting normals. I may do a write up of the SM2 blending modes and maths behind them in the future.

In the meantime here is a really great article I found on blending a detail normal map with a base normal map. http://blog.selfshadow.com/publications/blending-in-detail/


In other news, I'm on Google+ here: Neil Kemp

and I've started a G+ page for ShaderMap as well: ShaderMap+

Woody3D Discontinued

I'll just post what I wrote on the woody3d.com website, then I'll explain...

[color=#0066cc]"Woody3D was developed by Neil Kemp at Rendering Systems Inc. as an inexpensive solution for developers to bring fully animated trees and foliage to their applications and engines. Woody3D was discontinued in July 2012. [/color]

[color=#0066CC]Real-time tree generation and animation remain a core focus for Neil and Rendering Systems. In the future they'll place a higher emphasis on modeling tools and less on source code integration."[/color]

Woody3D was a long term comitment for me. Three years of development. I wanted to create affordable real-time trees for developers. Why did it fail? Simple, the technology was complex and not easily integrated into today's popular engines. It focused more on source code integration and had a very nerdy user inteface. It should have focused on a simple intuitive interface (draw the tree - grab and bend the branches, etc.) with easy export to popular file formats.

So that's what I plan to do. Start again. I believe I can take what learned from Woody3D (and ShaderMap 2) to build a better (single) tool to generate fully animated 3D trees and plants for video games, simulations, and software rendering systems. I can't fail if I don't quit.

The Woody3D Field of Trees video will remain online:

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:

// 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

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

// Tell ShaderMap this map plugin is 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

// Set default save format for the map

// Is a normal map

// 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

// Success
return TRUE;

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
case 0:
mp_set_map_tile_type(map_id, MAP_TILE_NONE);
case 1:
mp_set_map_tile_type(map_id, MAP_TILE_X);
case 2:
mp_set_map_tile_type(map_id, MAP_TILE_Y);
case 3:
mp_set_map_tile_type(map_id, MAP_TILE_XY);

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

// 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];
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
// Allocate 16 bit map pixels
map_pixel_d_array_16 = new (std::nothrow) unsigned short[width * height * 4];
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
{ delete [] map_pixel_d_array_8;
return FALSE;
// 16 bit
// 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
{ 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
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;

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.
A new version of ShaderMap 2 is now available for download. This version brings bug fixes, new features, and options.
Some of the new features include:

  • Support for OBJ quads
  • Lockable and savable source maps
  • Optimized GPU usage
  • Option to override tile types for source maps
  • Option to set default geometry for projects
  • A V Brush vector reset button
  • Keyboard shortcuts
  • And last but not least, a built in support page

    For a complete list of updates visit http://shadermap.com/forums/showthread.php?tid=35
    To download the latest version visit: http://shadermap.com/downloads

    Here is an image of the support page. With it you can get news and updates right in the app.


    I'll be posting another SDK tutorial later this week. This time of Map Plugins
ShaderMap 2 allows users to create materials using the ShaderMap 2 SDK. A material can use any map in the ShaderMap project or load external image files such as cube maps. The material also provides a user interface wtih associated HLSL constants. To get started let's look at the basic structure of the ShaderMap 2 material file.

Each material file is an XML file. It is made of two parts: Setup and FX.

Material File Structure:




The Setup Section

The first section of the XML is the SETUP section. Information in this section defines the maps and properties that the material will use.

The "setup" element requires 2 attributes: "name" and "description".

There are 4 types of elements that can be added to the Setup Section: lighting, world, input, and texture.

Lighting Element

The lighting element takes the form:
If lighting is set to 1 then the following constants will be sent to the shader:

// Light data
int g_light_count;
float3 g_light_vector_array[4];
float3 g_light_color_array[4];
float g_light_specular_array[4];
float3 g_light_ambient_color;

World Element

The "world" element can be of type "matrix", "vector", or "position". This element tells ShaderMap to send world information about the object it is rendering.

  • Matrix types are sent to the fx_name as float4x4.
  • Vector types are sent to the fx_name as float3.
  • Position types are sent to the fx_name as float3.

    1. The "matrix" type has the additional attribute "define" which defines the matrix to be sent to the fx_name constant. It can be any combination of the following characters:

    • 'w' - World matrix
    • 'v' - View matrix
    • 'p' - Projection matrix
    • 'r' - Rotation matrix
    • 'i' - Inverse of matrix
    • 't' - Transpose of matrix


      Each matrix is multiplied by the next. The inverse and transpose are applied to whatever matrix has previously been created.

      A string of "wit" sends the world_inverse_transpose matrix
      A string of "wvp" sends the world_view_projection matrix
      A string of "ri" sends the inversed rotation matrix

      There is no scaling in ShaderMap or rather all matrices have a scale of 1.0.

      2. The "vector" type has the additional attribute "define" which can be any one of the following values:

      • "view" - The view vector in world space


        3. The "position" type has the additional attribute "define" which can be on of the following values:

        • "eye" - The eye position in world space
        • "target" - The eye target position in world space
        • "object" - The object position in world space


          Input Element

          The "input" element defines a property control that will appear in the map properties section when that map is selected. This element can be a "list", "slider", "checkbox" or "color" control. Each type has additional attributes specific to the type.

          Values of the current input are sent to the FX constant defined in "fx_name".

          List types are sent to the fx_name as int
          Slider types are sent to the fx_name as int
          Checkbox types are sent to the fx_name as bool
          Color types are sent to the fx_name as float3

          1. The "list" type has the additional attributes: "prefix", "items", and "default".

          • "prefix" - A prefix to the list. A list of colors might use "color: "
          • "items" - A list of strings divided by the ; character. "A;B;C"
          • "default" - The default item in the list. A zero based index (integer).


            2. The "slider" type has the additional attributes: "name", "min", "max", and "default".

            • "name" - The name shown next to the slider control
            • "min" - The minimum value of the slider (integer)
            • "max" - The maximum value of the slider (integer)
            • "default" - The default value of the slider (integer)

              3. The "checkbox" type has the additional attributes: "name" and "default"

              • "name" - The name shown next to the checkbox control
              • "default" - The default state 1 or 0.


                4. The "color" type has the additional attributes: "name" and "default"

                • "name" - The name shown next to the color control
                • "default" - The default color of the control in HEX format.


                  Texture Element

                  The "texture" element tells ShaderMap to send a texture to ShaderMap. The texture can be from a Map or an image file. Two types of image files are allowed and they are standard images (jpeg, bmp, etc.) and cube maps (always dds).

                  If any texture is sent to the shader then the tile constant is sent as well. It is a float2 and is always named g_uv_tile. Multiply this by all texture coords to get proper tiling.

                  1. The "map" type texture sends a user defined map to the shader. It has the additional attributes: "name", "desc", "is_normal", and "is_checkbox".

                  • "name" - The name of the map required by the shader.
                  • "desc" - A description of the map required by the shader.
                  • "is_normal" - Can be either 1 or 0. If set to 1 then the map sent should be a normal map. An additional constant is sent the the FX shader which contains the coordinate system of the normal map. It will take the form _coordsys. It is a float3 vector. It represents the coordinate system relative to the default tangent space used by ShaderMap. A vector of (1,1,1) means it is in X = +Right, Y = +Down, Z = +Near. A value of (1,1,-1) means that Y = +Up
                  • "is_checkbox" - Can be 1 or 0. If 1 then a checkbox control will be added to the properties that allows the user to disable sendingthis texture.


                    2. The "image" type texture sends an image from file to the shader. It has the additional attributes: "name", "file", and "is_checkbox".

                    • "name" - The name of the image shown only if is_checkbox is 1.
                    • "file" - The filename of the image to load as a texture. It is relative to the location of the material file.
                    • "is_checkbox" - Can be 1 or 0. If 1 then a checkbox control will be added to the properties that allows the user to disable sending this texture.

                      3. The "cube" type texture sends a DDS cube map from file to the shader. It has the additional attributes: "file" and "is_checkbox".

                      • "name" - The name of the cube map shown only if is_checkbox is 1.
                      • "file" - The filename of the cube map to load as a texture. It is relative to the location of the material file.
                      • "is_checkbox" - Can be 1 or 0. If 1 then a checkbox control will be added to the properties that allows the user to disable sending this texture.


                        Ex. A Diffuse Lighting Material Setup:

                        The FX Section

                        The second section of the XML is the FX section. This section can include an entire Direct3D effect script enclosed in the CDATA section or can just be a link to a file in the same directory as the XML.

                        Attributes for the "fx" element are "vsm", "psm", and "file". vsm is the maximum vertex shader model required by the material. psm is the maximum pixel shader model required. The file attribute is optional, if set the fx is loaded from an external file rather than from the CDATA section. Ex. file="external_shader.fx"

                        Because ShaderMap uses DirectX 9c the maximum shader model supported is SHADER MODEL 3.

                        To learn more about DirectX Effect files see the Direct3D9c documentation:


                        or just google it: http://www.google.co...rect3D9 Effects

                        Diffuse Lighting Material

                        Here is a complete material example. It serves as a good starting point for creating your own materials. It takes one map as diffuse and uses per-pixel n dot l lighting for each of the project directional lights.

                        // ==============================================================

                        // Light data
                        int g_light_count;
                        float3 g_light_vector_array[4];
                        float3 g_light_color_array[4];
                        float g_light_specular_array[4];
                        float3 g_light_ambient_color;

                        // World data
                        float4x4 g_world_matrix;
                        float4x4 g_world_view_projection;
                        float4x4 g_world_inverse_transpose;

                        // Input data
                        int g_multiplier;

                        // Map data
                        float2 g_uv_tile;
                        texture g_diffuse_texture;

                        // Samplers
                        sampler g_diffuse_sampler =
                        Texture = ;

                        // I/O structs
                        struct VS_INPUT
                        float3 position : POSITION;
                        float3 normal : NORMAL;
                        float2 uv : TEXCOORD0;
                        float4 tangent : TANGENT;

                        struct VS_OUTPUT
                        float4 position : POSITION;
                        float2 uv : TEXCOORD0;
                        float3 normal : TEXCOORD1;

                        // ==============================================================

                        // Vertex shader
                        VS_OUTPUT vs_main(VS_INPUT IN)
                        // Local data
                        VS_OUTPUT OUT;

                        // Store output position, UV, and normal
                        OUT.position = mul(float4(IN.position, 1.0f), g_world_view_projection);
                        OUT.uv = IN.uv * g_uv_tile;
                        OUT.normal = mul(IN.normal, (float3x3)g_world_inverse_transpose);

                        return OUT;

                        // Pixel (fragment) shader
                        float4 ps_main(VS_OUTPUT IN) : COLOR
                        // Local data
                        int i;
                        float n_dot_l;
                        float3 n, d, l;
                        float4 c;

                        // Get texture color
                        c = tex2D(g_diffuse_sampler, IN.uv);

                        // Get normal
                        n = normalize(IN.normal);

                        // Base light color
                        d = float3(0.0f, 0.0f, 0.0f);

                        // Calculate diffuse light color
                        for(i=0; i {
                        // Light vector
                        l = normalize(-g_light_vector_array);

                        // N dot L
                        n_dot_l = saturate(dot(n, l));

                        // Add in light
                        d += n_dot_l * g_light_color_array;

                        // Calculate final color and return;
                        c.rgb = saturate(saturate(c.rgb * (g_multiplier / 100.0f)) * d);

                        return c;

                        // ==============================================================

                        // ShaderMap Technique
                        technique shadermap_material
                        VertexShader = compile vs_2_0 vs_main();
                        PixelShader = compile ps_2_a ps_main();

                        // ==============================================================

                        And here is it running in ShaderMap 2:


                        You can download the full material file here: Diffuse Lighting Material

                        To install the XML material file - place it in the following directory: C:\Program Files\ShaderMap 2\plugins\materials\

                        For more information and examples download the free ShaderMap SDK: http://shadermap.com/downloads
Hi everyone, it's been a while since I last published anything. That's because I've been working my heart out for a full year on ShaderMap 2. I am really proud of what I've been able to accomplish on this project.

Here's a rundown of the new features available and if you want to learn more just follow the links at the bottom. I made some videos of it in action if you want to see it in action. I'll be posting weekly entries on using the new ShaderMap SDK to build materials and plugins for ShaderMap.

- Redesigned User Interface
- Normal Map Painting
- Internal / External visualizer window
- 32 / 64 bit binaries
- Muli-core support
- Added more image formats including PSD and DDS.
- External editor integration
- ShaderMap material editor
- Advanced maps configuration
- Expandable using map plugins
- Expandable using HLSL shaders for materials
- Free ShaderMap SDK for developers

To dowload the demo, visit http://shadermap.com/

You can watch video of ShaderMap here http://shadermap.com/video/

[size="3"] woody3d.pause()

As it stands Woody still requires a couple more months of development to be ready for the next release. I'm really excited about all of the new features in the upcoming release but I've been developing Woody3D for about 3 years. That's a long time for a single developer to work on a project with no end in sight and little reward... I need a break.

Or rather...

I need a finite project to work on.


Earlier this week, I began implementing the interface I'd previously drawn in PhotoShop.


It's built using C++and Direct3D 9 in a Win32 framework. All controls are derived from a ui_base class which maintains a list of children controls and provides an interface for event callbacks, updates, and drawing.

Here's what I've done on the interface so far:
  • [size="3"]Tool tip support
  • [size="3"]Drag and drop support
  • [size="3"]UI framework
  • [size="3"]Button control
  • [size="3"]Checkbox control
  • [size="3"]ShaderMap 2 icon artwork
  • [size="3"]Menu interface
  • [size="3"]Start page[size="3"]
    I'll continue developing the UI then do an open test here and on the ShaderMap forums to get all the bugs worked out. Then I'll get to work on the plugin system.

    Woody fans have no fear, development will .play() soon after ShaderMap2 is released. Switching projects has already brought my energy levels back near immortal levels.

Woody3D can create tree roots as shown above. There is a problem however, as it currently uses a very simple hierarchy for branch levels. It does not allow for more than one child per branch. This means you can add roots but not branches. That's a pretty boring tree.

Next version will support a more flexible hierarchy. To manage the various parts of the tree, I'll be using a CtreeCtrl in the Tree Forge. Below is a colored [size="3"]CtreeCtrl [size="3"] (with fancy gradient selection bar).


And here is the source for the control if you're interested in how it works. Feel free to modify it and use it in your own MFC projects.

#ifndef ___COLOR_TREE_CONTROL_H___
#define ___COLOR_TREE_CONTROL_H___

class Color_Tree : public CTreeCtrl

COLORREF ___select_background_color;
COLORREF ___select_font_color;
COLORREF ___lost_focus_select_background_color;
COLORREF ___lost_focus_select_font_color;

BOOL ___is_select_full_row;
BOOL ___is_select_full_row_gradient;



afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);


// Constructor / Destructor

void enable_select_full_row(BOOL enable, BOOL is_gradient);
void set_select_color(COLORREF select_background_color, COLORREF select_font_color,
COLORREF lost_focus_select_background_color, COLORREF lost_focus_select_font_color);


#include "stdafx.h"
#include "Color_Tree.h"

// Local Gradient rectangle function
BOOL draw_gradient_rect(CDC *pDC,
const CRect &draw_rect,
const COLORREF start_color,
const COLORREF end_color,
const bool is_horizontal)
// Local data
BYTE red_start, green_start, blue_start,
red_end, green_end, blue_end;
TRIVERTEX vertex_array[2] = { 0 };
GRADIENT_RECT gradient_rect[1] = { 0 };

// Validate DC
if(!pDC || !pDC->GetSafeHdc())
{ return FALSE;

// Get RGB bytes from start color
red_start = GetRValue(start_color);
green_start = GetGValue(start_color);
blue_start = GetBValue(start_color);

// Set first vertex
vertex_array[0].x = draw_rect.left;
vertex_array[0].y = draw_rect.top;
vertex_array[0].Red = (static_cast(red_start) << 8);
vertex_array[0].Green = (static_cast(green_start) << 8);
vertex_array[0].Blue = (static_cast(blue_start) << 8);
vertex_array[0].Alpha = 0x0000;

// Get RGB bytes from end color
red_end = GetRValue(end_color);
green_end = GetGValue(end_color);
blue_end = GetBValue(end_color);

// Set second vertex
vertex_array[1].x = draw_rect.right;
vertex_array[1].y = draw_rect.bottom;
vertex_array[1].Red = (static_cast(red_end) << 8 );
vertex_array[1].Green = (static_cast(green_end) << 8 );
vertex_array[1].Blue = (static_cast(blue_end) << 8 );
vertex_array[1].Alpha = 0x0000;

// Gradient vertex index
gradient_rect[0].UpperLeft = 0;
gradient_rect[0].LowerRight = 1;

// Draw gradient rect
return GradientFill( pDC->GetSafeHdc(),

// c()
___is_select_full_row = FALSE;
___is_select_full_row_gradient = FALSE;
// d()

// Message map
BEGIN_MESSAGE_MAP(Color_Tree, CTreeCtrl)

// Handle color of select
void Color_Tree::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
// Local data
RECT rect;
CBrush brush;

// Get custom draw struct

// Handle by draw stage


// Pre paint of tree item

// To Get item
HTREEITEM hItem = (HTREEITEM)pcd->nmcd.dwItemSpec;

// Handle control has focus
if(this->GetFocus() == this)
// If item is selected
if(pcd->nmcd.uItemState & CDIS_SELECTED)
pcd->clrText = ___select_font_color;
pcd->clrTextBk = ___select_background_color;

// Determine if item is in view
// Get text rect
if(___is_select_full_row && this->GetItemRect(hItem, &rect, TRUE))
// Adjust rect
pcd->nmcd.rc.left = rect.right;

// Gradient fill
// Get DC and fill
pDC = this->GetDC();
draw_gradient_rect(pDC, pcd->nmcd.rc, ___select_background_color, this->GetBkColor(), true);

// Cleanup
// Solid fill
// Get DC and Create brush
pDC = this->GetDC();

// Solid fill
pDC->FillRect(&pcd->nmcd.rc, &brush);

// Cleanup
// If item is hot (mouse over)
else if(pcd->nmcd.uItemState & CDIS_HOT)
pcd->clrText = this->GetTextColor();
// If item is selected
if(pcd->nmcd.uItemState & CDIS_SELECTED)
pcd->clrText = ___lost_focus_select_font_color;
pcd->clrTextBk = ___lost_focus_select_background_color;

// Determine if item is in view
// Get text rect
if(___is_select_full_row && this->GetItemRect(hItem, &rect, TRUE))
// Adjust rect
pcd->nmcd.rc.left = rect.right;

// Gradient fill
// Get DC and fill
pDC = this->GetDC();
draw_gradient_rect(pDC, pcd->nmcd.rc, ___lost_focus_select_background_color, this->GetBkColor(), true);

// Cleanup
// Solid fill
// Get DC and Create brush
pDC = this->GetDC();

// Solid fill
pDC->FillRect(&pcd->nmcd.rc, &brush);

// Cleanup
// If item is hot (mouse over)
else if(pcd->nmcd.uItemState & CDIS_HOT)
pcd->clrText = this->GetTextColor();



// Enable select full row
void Color_Tree::enable_select_full_row(BOOL enable, BOOL is_gradient)
{ ___is_select_full_row = enable;
___is_select_full_row_gradient = is_gradient;

// Set the colors for selecting items
void Color_Tree::set_select_color(COLORREF select_background_color, COLORREF select_font_color,
COLORREF lost_focus_select_background_color, COLORREF lost_focus_select_font_color)
___select_background_color = select_background_color;
___select_font_color = select_font_color;
___lost_focus_select_background_color = lost_focus_select_background_color;
___lost_focus_select_font_color = lost_focus_select_font_color;


CImageList ___tree_image_list;
Color_Tree ___tree_control;

// Local data
CBitmap bitmap;
HTREEITEM o_1, o_2, c_1, c_2;

// Setup tree colors
___tree_control.SetBkColor(RGB(116, 127, 138));
___tree_control.SetTextColor(RGB(255, 255, 255));
___tree_control.SetLineColor(RGB(255, 255, 255));
___tree_control.set_select_color(RGB(196, 210, 224), RGB(0, 0, 0), RGB(102, 114, 124), RGB(255, 255, 255));

// Setup tree images
___tree_image_list.Create(16, 16, ILC_COLOR32, 1, 4);
// Bitmap 1
___tree_image_list.Add(&bitmap, RGB(0, 0, 0));
// Bitmap 2
___tree_image_list.Add(&bitmap, RGB(0, 0, 0));
// Set image list
___tree_control.SetImageList(&___tree_image_list, TVSIL_NORMAL);

// Set item height

// Custom enable select of whole row - do not use 'Full Row Select' property for Control - it disables lines
// Allows for horizontal gradient too.
___tree_control.enable_select_full_row(TRUE, TRUE);

// Add items
o_1 = ___tree_control.InsertItem(_T("Trunk"), 0, 0);
c_1 = ___tree_control.InsertItem(_T("Standard Branch 0"), 1, 1, o_1);
c_2 = ___tree_control.InsertItem(_T("Standard Branch 1"), 1, 1, o_1);
___tree_control.InsertItem(_T("Imposter Branch 0"), 1, 1, c_2);
o_2 = ___tree_control.InsertItem(_T("Leaf Types"), 0, 0);
___tree_control.InsertItem(_T("Mesh Leaf 0"), 1, 1, o_2);
___tree_control.InsertItem(_T("Billboard Leaf 0"), 1, 1, o_2);


Above is a tree which uses spoke meshes for leaves (used imposter branches for leaves [see wire-frame render in comments]). Below is a tree which uses billboarded quads for leaves.


I'm thinking that mesh type leaves are the way forward. I'll keep support for billboards, they could be very useful for vines, but the next version will not rely on them exclusively. With mesh type leaves the user can choose from a palette of leaf mesh types. Also export to static formats would be possible.

What do you think?

[size="3"]I've spent two days building SSAO with Direct3D. I used the method described in the GameDev.net article A Simple and Practical Approach to SSAO. After some tweeking I've got some decent results and speeds. It uses two buffers for normals and positions vs extracting position from depth and perspective. The result is blurred using a two-pass box blur.

Later today I'll start adding this AO implementation to the Leaf Painter. It should greatly improve the leaf textures.

In other news - the BrushUp Utlity GUI is finished. See below:


[size="3"]It has a re-sizable and intuitive interface with drag-and-drop list box and image preview. It uses an iterative thread scheduler to process cropped images based on CPU count. I'll release the scheduler source on my website when I get around to it.

[size="3"]One of the new utility apps (BrushUp) I'm developing for Woody3D will allow the artist to scan in a series of leaves in the field and with very little effort convert them to brush maps compatible with the Leaf Painter editor. This entry documents the process by which this is achieved.

For this example I've taken 8 leaves from one of my house plants, placed them vertically in the scanner, closed the lid, hit the button and the results are shown above.

What BrushUp does is take scans like the one above, detect and crop out each individual leaf, build alpha maps for each, expand the color edges, and save to a folder. It does this using the following steps.

Step 1. A little pre-processing. Load the scan into Photoshop and use the paint tool, with high tolerance and anti-alias disabled, to create a solid background color for the map.


Step 2. Load the image into BrushUp and tell it the background color.

Step 3. Enforce a 1 pixel border. To simplify edge detection we ensure there is a 1 pixel border of background color on the image. Canvas resize.

Step 4. Flood Fill Alpha to Zero where background color. This does two things. It defines the initial binary alpha map, and paints the edges of each brush with a 128 alpha. This will be used later when determining bounding rects for each brush. The flood fill uses 4 directions and is stack based. Pixels already added to the stack are marked with alpha 128. The resulting alpha map is shown below:


Step 5. Get a list of Bounding Rectangles for each Leaf / Brush. The alpha channel is scanned for every pixel. When a pixel with alpha 128 is found it uses an 8 direction stack based fill algorithm to follow the edge around, marking checked pixels white as it goes and maintaining min and max points to build the rectangle on completion.

Step 6: For every bounding rect Crop and Post Process. Iterate through the list of rects and do the following for each:
  • [size="3"]a) Crop the brush using rect.
  • [size="3"]b) Optionally give brush an n-pixel border.
  • [size="3"]c) Expand the color edges of the brush. This is a filter that pulls out edge colors making it easier to down-sample the brush image without destroying the edge colors.
  • [size="3"]d) Optionally apply smooth edges to alpha map. To do this it uses a Gaussian blur on the alpha channel where Alpha is 255.
  • [size="3"]e) Save the brush image to file.[size="3"]
    A few of the resulting brush files are shown below:


    And a couple of (I think 4) minutes later, I had created this texture in Leaf Painter using the new Palmate Compound node.


Just made a change. There are now just two compound leaf types: Palmate and Pinnate. Compound pinnates, such as the one shown above, can be generated using a pinnate type node with pinnate type leaf textures. The result is acceptable. Moving on...
[size="3"] e_18a.jpg

I've made some progress with the procedurally generated compound leaf types in the Leaf Painter editor. There are three basic compound types to paint with: Palmate, Pinnate, and Compound Pinnate. Pinnate types can be made to be odd or even with optionally alternating leaf patterns.

There are a number of parameters that allow for scaling and rotating leaves along the length of the stem as well as modifiers for bending and angling each stem. I've added the curve editor to the Leaf Painter for additional control.

[size="3"]The leaves on each compound leaf are animated automatically. No more time consuming leaf animation setup. Click, adjust parameters, set orientation, and done. You can also set any compound leaf as the paint template - meaning the parameters of the template node will be used when painting additional nodes.

[size="3"]So far I've completed the palmate and pinnate types. An example of each is shown on the image above. Only one leaf texture is used at the moment.


[size="3"]There will be (at least) two new tools added to version 1.2. The first new tool is a map packer for combining multiple leaf maps and imposter branch maps into a single texture. It will also generate the map markers which Tree Forge uses to calculate uv frame data. Drag a few textures in, hit a button, it spits out a map.

The second tool will vastly improve the Leaf Painter brush making process. In the field I want to scan several leaves at a time. The brush maker tool will load the scanned image, detect and crop out the individual leaves, and generate and embed alpha masks into the resulting brush images. Currently doing this in Photoshop is exceedingly tedious.

I'll begin working on these tools once the compound leaf types are complete (I need a break from the Leaf Painter). After that I'll return to the Leaf Painter to add the ambient occlusion, multi-sampled rendering, and improved alpha maps which should wrap up 1.2 features for Leaf Painter.
[size="3"] So I've spent a little while dimming the lights...


I think it's a big improvement.

Working hard to bring procedurally generated compound leaf types. After that... occlusion shading. If you're wondering, the interface is MFC +C+. All common controls are sub classed for custom colors and bitmaps. The property grid (right) is a custom control, made by me. The color picker uses the XColorSpectrum control by Hans Dietrich. Thanks Hans.

Will post again when I have some shots of the new compound leaf nodes. The images above still use the single leaf type geometry.
[size="3"]I've recently discovered "Custom Content Blocks" on my journal page. For examples, take a look at the blocks on the right of this page, the top three are custom content blocks. Yesterday I added a "Random Album Image" block to the top of my page. It started displaying random images from my Woody3D gallery. Today when I visited my journal it was displaying random images from every member gallery on gamedev.net. Strange, I thought. That's not going to work. So rather than bug the gamedev.net developers with my feedback, I solved the problem myself.

Requirements: Web server with PHP.

On my server I created a directory with the following items:


The "woody3d_screens" folder contains all images I want to display randomly. To make them fit They are all resized to have a width of 160 pixels. All images are jpeg so they work with my random image script.

The .htaccess file is important. It tells the server to treat any file with the extension .jpg as a php file. This is done because gamedev.net image links need to have of an image type file extension to display. So for instance writing will not work in the custom block.

The [size="3"]"woody3d_screens" [size="3"].jpg file is actually a PHP script. It finds all files in the folder of type jpeg and outputs one of them randomly using a custom header with Content-type: image/jpeg.

The PHP file is just a redirect to the gallery page. I use this because linking directly to my gamedev.net gallery in the custom block creates a popup block advertising the gallery (for some weird reason). So I link my randomized image to the redirect script instead.

Here is what is in each file:


Options All -Indexes

#sets jpg to be processed by php
AddType application/x-httpd-php .jpg


$gallery = "woody3d_screens";



$path = $gallery . "/";

// Obtain list of images from directory
$image_filename_list = get_image_filename_array_from_directory($path);

// Get random image file name
$image_filename = get_random_entry_from_array($image_filename_list);

// Output header and data of image
if(file_exists($path . $image_filename))
$data = file_get_contents($path . $image_filename);
header('Content-type: image/jpeg');
echo $data;



// Get random entry from array
function get_random_entry_from_array($ar)
mt_srand((double)microtime() * 1000000);
$num = array_rand($ar);
return $ar[$num];

// Get array of image file names from a directory
function get_image_filename_array_from_directory($path)
$images = array();
if($img_dir = @opendir($path))
{ while(($img_file = readdir($img_dir)) !== false)
// Check for gif, jpg, png
// if(preg_match("/(\.gif|\.jpg|\.png)$/", $img_file))

// Just check for jpg
if(preg_match("/(\.jpg)$/", $img_file))
{ $images[] = $img_file;
return $images;



header("Location: https://www.gamedev.net/gallery/album/106-woody3d-screens/");

I really like that I can add my own side bar content to the journal pages. This is a feature I think everyone should take advantage of. Comments and thoughts are always welcome.

ShaderMap 2 Interface


For those of you who don't know, ShaderMap is an app for quick and easy map creation. I fully expect to begin development of version 2 later this spring, after the release of Woody 1.2.

From the start I had the thought that by "restricting" myself to a 3D rendered interface, rather that relying on Windows dialogs and controls, I would be forced to create a more streamlined user experience. Here's what I've drummed up. Mind you, these are just concept images and do not reflect the final product.


Each map will be created in a plugin. There will be two kinds of plugins (DLL and LUA script). Each plugin defines a set of controls to be displayed in the map properties panel. A hierarchy of maps is defined as each plugin dictates its parent map. Figure B shows a source image of type diffuse texture. The two maps below it are children of the source image, they are displacement and specular. The displacement map has two children, normal map and ambient occlusion.

Any selected map can be tweaked, locked, edited in an external editor (Photoshop), and rendered to file.


As well as the 3D preview now being inside the app, there will be two special editors (not yet drawn): The tangent normal editor and displacement (height map) editor. Both of these editors can be assigned to a map in that map's plugin script.

I'd be happy to hear anyone's thoughts on the new design.
[size="3"]Just a quick post to announce that Woody3D 1.2 development is underway. Below is a list of features being added to the SDK.

Leaf Painter - Will be maturing the Painter application nearer its full potential. Adding procedurally generated compound leaves (Compound Paint Tool) for fast drawing of leaf textures. That should also generate more realistic tangent space maps. Will be using the Woody procedural engine for stem mesh generation. Users will be able to select from a variety of leaf types when painting. Ambient shadows / shading will be added as well as non-binary alpha transparency for generated maps.

Tree Forge - Adding a tree wizard. Users will select from a tree shape, size, and polygonal resolution. The resulting tree can then be edited as normal. The wizard will be scripted so users can add their own plant types.

Tree Compiler - Allow trees and plants to be randomized at compile time. Ability to automatically generate multiple randomized mesh files. Useful if integrating into a game editor and want to randomize meshes during development using the command line version.

API - Adding an example demonstrating the rendering multiple trees in a scene. Creating a C# version of the API with examples.
[size="3"]I wish I could go back to the 80's and undo all of my tight rolled pants. That time I tripped and spilled punch all over my date - deleted! The ability to undo human history is a luxury not "yet" programmed into life (brain damage doesn't count). Tools we develop, on the other hand, should suffer no such limitations.

In this entry I'll describe the history control I've just added to the Woody3D tools.

A Cumulative Approach

When first thinking of how to create history in my tools I started from the top. For every change (action) that is made I could save the entire state of the application before and after the action. With this saved data I could undo and redo all user actions. This idea formed the basis of my next design.

By grouping saved data into history events the system retains simplicity, adds scalability, and uses memory more efficiently. An example of one such event is scene lighting. Each time a light variable is altered, all lighting data is saved to a history node. Saved data includes values before (undo) and after (redo) the action. In order for this approach to work all data must be applied cumulatively when performing a history undo or redo.

A list of history nodes is maintained. Each history node contains an id and two virtual file objects for storing pre and post action data. The virtual file class uses functions similar to the C++ fwrite() and fread() for reading and writing to a byte array which is grown as needed.

Two callback functions are assigned to the history class for undo and redo events. When the undo function is called (with undo step count as a parameter) the undo callback is sent each history node in reverse chronological order until the desired history location is reached. Inside the undo callback function the pre-action data is reapplied to the corresponding data set. On the last call of the undo callback the post-action data is applied to ensure the state up to date (example: selection of leaves). Similarly when the redo function is called (with redo step count as parameter) the redo callback is sent each history node in chronological order until the desired history location is reached. Inside the redo callback the post-action data is reapplied to the corresponding data set.

The history class maintains a timer for history actions. It is used when history nodes are added to the class. If the same history action is added to the history class within a span of time (say 1 second) then the last history node is merged with the new one leaving the original pre-action data but replacing the post-action data with the latest. This is useful when multiple actions occur very quickly (slider bar, spinner control).

If you'd like to see this strategy in action, please download the latest Woody3D SDK evaluation which comes with Leaf Painter and Tree Forge. Both tools now allow history control.
[size="3"]Using SuperNotecard for Feature Organization

A couple of weeks ago I started using SuperNotecard to order and keep track of features for Woody. Notecard is a program designed for writers and plot development but I find it works just as well for keeping track of software features. Using it is simple, you create a card, double click it, then give it a title and description. Cards can be stacked to create a hierarchy and positioned anywhere on the board. It's a great replacement for anyone still sticking note cards to a cork board. It's just $29 and available for Win, Mac, and Linux. You can export projects to XML so I may create an XML to HTML script for that at some point to share features online.

Woody History and Lighting Tool Windows

I'm currently adding a Project History window, similar to Photoshop's History Panel, to both the Leaf Painter and Tree Forge. I've decided to use the history listbox scheme rather than the traditional undo / redo for the simple reason that there is a written history available to the user. The Lighting window is being developed at the same time. This will allow all lighting to be controlled from the main interface, add a few lighting gizmos, and add the ability to save lighting presets. Lighting is going to be greatly improved during 1.1.x development, especially in the Leaf Painter.

Resources at the Matthaei Botanical Gardens

Christa and I recently made a trip to the Matthaei Conservatory. It was a relief to spend a few hours in a warm and humid environment with all the snow we've been getting. During our visit we received permission to photograph texture resources in the conservatory for commercial applications. We have already used the fields surrounding the gardens for many of the current Woody trees but to be able use the trees inside will add many non native species to the library.

In which words are written and you read them.

I have started titling my journal entries now that they matter. Previous entries (now renamed for relevance) were titled Entry 1, Entry 2, ... Very Prussian.

So here we are and you've decided you liked this title and clicked on it. Great! Come in. Have a candy. Like something to drink? Good. Okay so here's my January thus far.

New Woody Website

For new readers, I'm developing Woody3D, a procedurally generated tree rendering system. I've spent a lot of time over the last 20 days redrawing and rewriting the Woody website. Behold the finished: http://woody3d.com/features/

It's greatly simplified from its previous incarnation and better displays the content. Also you don't have to go through 'like' 5 layers of security to get to the forums so... yeah it's better.

Speaking of Content

Want to see how Woody3D works? The Evaluation C++ source code and shaders are now available for download. I've really only left out the leaf animation functions. Also the full Tree Model Library with project files and brushes can be downloaded for free. So... that's better too.

The License is Easier Now

It's a no royalties license for most users. If a product that uses Woody grosses over 250K then an additional one time fee is paid. Read more at http://woody3d.com/license/

Using Gamedev.Net's New Features

Wow a photo of me. I'll have to update it when the sun comes back to Michigan. I've created a Woody gallery as well. My next integrational step is to get some friends...


Heavily Weighted Tree

Deth Tree
This tree was recently used to work out geometry kinks. The first level of branches (trunks) are weighted to grow towards a point near the bottom left of the screen. The second level is highly weighted using a downwards vector. The third level also follows a downward vector but with less intensity. See how the geometry follows splines using the method discussed in entry 7.
New Videos
I've posted two new videos that demonstrate the Woody3D Leaf Painter and Tree Forge editors. Watch them on my YouTube channel: http://youtube.com/renderingsystems
The videos should give you a general feel of what working with the editors is like. The Leaf Painter requires direct interaction with each leaf to create animated textures. The Tree Forge requires knowledge of a set of parameters to modify for each part of the tree. If you'd like to try the editors they are available with the Woody3D evaluation kit: http://woody3d.com/downloads/

Editors and My Plan of Abstraction
Abstraction of the Leaf Painter controls will include procedurally generating leaf types (Fig.1) and allowing the user to paint with them. This small abstraction will speed up the process of painting and build more realistic textures.
Abstraction of the Tree Forge controls begins with tree type selection, moves on to parameters, and ends with the new tools. Tree type selection involves giving the user an option to select a tree type (Fig.2) from a library of tree types to begin with. This removes the need to recreate similar types at different resolutions. Some of the parameters can be combined and transformed into slider controls to simplify the parameters. Other parameters can be controlled using tree interactive controls. This will make working with parameters more intuitive in the tree editor. Finally more tools will be added for directly interacting with each part of the tree. Tools will allow the user to cut branches and leaves, modify (bend, move, rotate) branches, paint leaves onto branches and more.
Leaf Painter is Key
It's all about what you wear. To get great looking trees I'm going to have to create great looking textures. The current leaf painter version is a good start. As mentioned in the section above, I'm going to add leaf types which will be generated from leaf textures. Attention to detail in these leaf types is key. Lighting, shadowing, ambient occlusion, and multisampling are all priority goals.

Evaluation Station
A C++ evaluation of the Woody3D model loader and basic versions of the animation shaders is coming in January. I'll be releasing 1.1.1 with the evaluation and I'll begin adding some abstraction improvements over version 1.1.X. The primary goal of 1.1 will be to make improvements to the Leaf Painter. With improvements to the Tree Forge in 1.2.

End of Year Sneaking Up
Tomorrow is the 15th already! Another Gregorian year is coming to a close. I'm happy to have released Woody3D finally after three years of hard work. It's seems odd sometimes when I think that it's only the beginning.

Woody3D 1.0.3 Release

Woody3D 1.0.3
Woody3D version 1.0.3 is now available for download. Full C++ source code for the API is now included with the SDK. Also licensing has been updated to simplify purchasing and it now allows for development on PC, Console, and Mobile platforms with any product license. The Evaluation Kit has been updated to include demo versions of Tree Forge and Leaf Painter. The Evaluation also includes full HTML documentation for the API and Tools. http://woody3d.com/downloads/

What's Next
First I'd like to get the C++ API working on Linux. I'll probably use Ubuntu since I'm most familiar with installing and operating that flavor. I'm also thinking of creating an evaluation API to be released with the Evaluation Kit. This way people can do more than look at sample source code and documentation, they'll be able to bring Woody3D tree models into their application before purchasing a license. I'll need to give it more thought and determine how I can implement the evaluation API.
On top of all that I'm still working on new trees, to be released soon, and the blog page for the website. I've just finished a significant remake of the features and licensing pages. Simplification is my mantra.

Woody3D 1.0.2 Release

Woody3D 1.0.2
I've fixed the mesh distortion bug. The problem was caused when rotating branch stacks (frames) to follow the branch spline. I was using a Fixed Up method that caused occasional twists and kinks in the branch mesh. I solved it by using the Parallel Transport Frame method described in Game Programming Gems 2.

The Way Forward
As the comments and questions continue to roll in on Woody3D I've been spending a lot of time thinking of ways to meet the demands of potential customers. Two requests rise above the rest. They are tool demos and source code.
I've created demo versions of the tools so everyone can play with the editors and get a feel for what is possible while experiencing the user interface first hand. The tool demos will be released with 1.0.3 in the Evaluation Kit hopefully later next week.
As for API source code... I guess it's a no-brainer. I'm currently working to get a source code version of the API to my customers which will also be released with 1.0.3. This will allow users to build the API into their applications on multiple platforms without being bothered with the binary libraries. I'm also deciding to release un-obfuscated shaders so users can modify them and maybe even make them better. Additionally I plan to create a C# version of the API and release it during the first quarter of next year.
  • Advertisement