Author's Guide to Transforms

This article should be considered required reading for transform authors. It contains important information that you need in order for your transform to function correctly under Microsoft® DirectX® Transform containers. It also includes conventions and hints that are useful for writing transforms.

This article contains the following sections.

Things You Need to Know About All Transforms

This section contains the following topics.

Name

Use a user-friendly name for the component. This is the name that visual authoring tools will expose to users. Good names such as Slide, Explode, and Fade describe what the transform does and are easy to remember.

ProgIDs

Programmatic identifiers (ProgIDs) are used for hosting transforms in Microsoft® DirectAnimation®. They are constructed in the following format.

For example: Microsoft.DX3DTransform.explode.1

You also need to make provisions for the version number to be optional when a ProgID is used. In that case, if the version is not specified in the ProgID, the latest available one will be used. It is important that you use the following .rgs file as a template in order for the version-independent ProgID to work correctly.

HKCR
{
    DX3DTransform.Microsoft.Explode.1 = s 'Explode'
    {
        CLSID = s '{141DBAF1-55FB-11D1-B83E-00A0C933BE86}'
    }
    DX3DTransform.Microsoft.Explode = s 'Explode'
    {
        CurVer = s 'DX3DTransform.Microsoft.Explode.1'
        CLSID = s '{141DBAF1-55FB-11D1-B83E-00A0C933BE86}'
    }
    NoRemove CLSID
    {
        ForceRemove {141DBAF1-55FB-11D1-B83E-00A0C933BE86} =
        s 'Explode'
        {
            ProgID = s 'DX3DTransform.Microsoft.Explode.1'
            VersionIndependentProgID =
            s 'DX3DTransform.Microsoft.Explode'
            ForceRemove 'Programmable'
            InprocServer32 = s '%MODULE%'
            {
                 val ThreadingModel = s 'Both'
            }
        }
    }
}

Component Categories

Transforms can declare themselves in the following categories.

The third and fourth categories are unrelated to the first two. Transforms are either in the first or second category, and optionally in the third and/or fourth. The third category is useful for procedural surfaces, which also function as image transforms. Procedural surfaces are useful when categorized distinctly because they can be used as surface inputs to transforms. For details on the fourth category, see the following on the distinction between authoring and run-time transforms.

For corresponding GUID values, refer to Dxtguid.c in the include directory.

Property Pages

If you want your transform to have user-set variables that define properties specific to your transform, you will need to define a custom interface. If you define a custom interface, inherit from IDispatch if your transform is not time-based; or inherit from IDXEffect if your transform performs modifications over time.

After you write a custom interface, you need to design a property page, which is a user interface used to change the properties specific to your transform. For example, if your transform is like the Wipe sample transform, you might want to define a custom interface that sets the width of the alpha blend used with the transform. In this case, you would also want to create a property page with a dialog box used to enter the percentage of the output width to use for the alpha blend.

IDXEffect Interface

The following code shows how the IDXEffect interface is declared in the Dxtrans.idl file.

interface IDXEffect : IDispatch
{
    HRESULT get_Capabilities([out, retval] long *pVal);
    HRESULT get_Progress([out, retval] float *pVal);
    HRESULT put_Progress([in] float newVal);
    HRESULT get_StepResolution([out, retval] float *pVal);
    HRESULT get_Duration([out, retval] float *pVal);
    HRESULT put_Duration([in] float newVal);
};

This is an optional interface for your transform. It exposes a standard way for controlling animated effects. This makes it convenient for external applications like DirectAnimation to control animations through scripting. The transform might have other animation parameters that would be exposed through custom interfaces.

The Capabilities are a way for the transform to indicate whether the animation is periodic. If so, the transform output at Progress = 0 is the same as the output at Progress = 1. It is also a way to indicate that the transform is a morph. A morph transform takes two inputs; the result is the first input at Progress = 0 and the second input at Progress = 1.

The Progress is the primary animation property, which has a range from 0.0 to 1.0. To produce an animated effect, you would step sequentially through values of Progress, displaying transform output at each step. Typically, at each step you would set Progress to the desired value and then call the IDXTransform::Execute method to obtain the corresponding result.

The StepResolution property is optional but is useful if the primary animation property will lead to different results at discrete points in the [0,1] range. For example, if StepResolution is 0.25, the transform will only produce five distinct results. If StepResolution is set to zero, the transform produces a continuous range of results.

Duration is a recommended amount of time for the primary animation property to change from zero to one. It is optional for a container to use this recommended duration. There is also a put_Duration method for use by visual authoring tools. This allows the transform to persist a modified duration value from the originally specified one.

Quality Property on IDXTransform

The Quality property for a transform exists so that the performance of a transform that requires many calculations can be adjusted. The Quality property is represented by a floating point number that should be set between 0.0 and 1.0, with 1.0 indicating the highest quality. The value can be set and retrieved using the IDXTransform::SetQuality and IDXTransform::GetQuality methods.

For instance, if you want to create a three-dimensional (3-D) transform that creates a sphere, you might use the Quality property to set the number of faces you'll use. Lower quality would mean fewer faces and would result in higher performance. The Quality property defaults to 0.5 and is set by the container of the transform.

MapBoundsIn2Out

This method takes an array of DXBNDS objects (inBnds) whose number is equal to the number of inputs the transform accepts on Setup, and in the corresponding order. The transform fills an output DXBNDS structure (outBnds), which represents the bounds of the output of the transform, resulting from a call to Execute. These bounds are based on the current property settings where the inputs passed to Setup have the bounds specified in inBnds. If NULL is passed for the input bounds parameter, this method uses the bounds of the current input data objects.

This method is important because containers of transforms typically need to obtain bounds of the transform outputs based on the extents of their inputs before the specific inputs are available. Extent analysis is done before actual inputs are available for a variety of optimizations, including culling, pruning, and dirty rectangle techniques. It is also used for setting the z-resolution in the rendering device, which tends to be a critical resource that should be used carefully.

Therefore, transforms need to have a way to report their output extents based on the settings of their parameters and the bounds of their inputs. These bounds do not need to be tight because it is acceptable if they describe a bounding box that is larger than the actual box. They do, however, need to be true bounds that are guaranteed not to be exceeded by the object. If they are not true bounds, a variety of rendering artifacts might result. These can include dirty pixels because of dirty rectangle optimization and front/back clipping because of underestimates of the z-range.

To simplify DirectX Transform, an implementation of MapBoundsIn2Out is provided in the base class. It calls a DetermineBnds method on the transform and passes a bound as an in/out parameter. The result is determined from the input bounds in one of the following two ways.

The transform then needs to manipulate the provided bound any way it desires into a bound for the output. There are two helper functions of the CDXBnds class that operate on bounds and that are provided for transforms. One is Expand and the other is Scale. The former is additive, the latter is multiplicative, and both are in relation to the center of the bound. That is, Expand will add half the given x-, y-, and z-values to the maximum component and subtract this value from the minimum component of the corresponding bounds. Scale will multiply the given x-, y-, and z-components of the bounds relative to the midpoint of these components. In most cases an application of one or both operations with proper parameters will suffice for determining the output bound. The bound parameter to DetermineBnds that carried the input bound is intended to be overwritten with the resulting output bound.

One simplifying approach that a transform might use is to determine a lifetime maximum x-, y-, and z-scale across the whole progression of an effect and repeatedly scale the input bound by these same scale factors. This lends itself to simplicity, but in certain cases, it results in overly exaggerated bounds that will render the container's optimizations ineffective.

Alternatively, a sophisticated transform can implement its own MapBoundsIn2Out to estimate the output bound based on the presently set parameters and input extents. Remember that the tighter the reported bound is, the more effective the container optimizations will be.

The DXETool application provides a way to test the correctness of the reported bounds at the time of an execute by checking them against the actual bounds as obtained from the resulting output. The application is located in the Dxmedia/Bin/X86 folder of the Software Development Kit (SDK).

Generation IDs

The generation ID associated with any surface or mesh represents a "version number" of the object. To make sure your transform is using the most recent version of the inputs, you should check their generation IDs at the beginning of your execution function. When you modify your transform's output, you should increment the output's generation ID. See the CDXBaseNTo1 helper functions for methods to manage generation IDs for your transform.

On a call to Execute, a transform needs to check these generation IDs and determine if one of its inputs has changed since the last execution. If the transform caches its inputs (which they often do as an optimization) and the input's generation ID has changed, the transform needs to regenerate its input cache.

Furthermore, a transform needs to increment the generation ID of its output every time the output changes. A container needs to check the generation ID of the output to control any optimization scheme it might have. Generation IDs are also useful for cases of asynchronous loads, where a container makes the request for a new resolution and learns that the resulting new output is ready based on checking its generation ID. This can be the case for progressive meshes downloads or images from the Internet.

Author-time vs. Run-time Transforms

Certain parties might be interested in this distinction because they want to sell the author-time version of their transforms, but freely distribute their run-time version. The typical distinction between the two is as follows:

The Wipe sample shows how to produce both an author-time and a run-time version of a transform from a common source. It also illustrates how to tag an author-time transform with a copyright string. The example is located in the Samples\Multimedia\Dtrans\C++\Wipe\Wipedll folder.

DirectX Transform Security Mechanism

DirectX Transform allows for a security mechanism for any transforms you write. For more information, see the Copyright Information article.

Checking for Optional Inputs

For optional inputs it is acceptable for a container to either not provide the input parameter completely, or to provide NULL in place of the input in the call to Setup. This implies that the transform needs to check if an optional input is NULL and deal with it appropriately. If a transform uses the helper function GetInput, both cases will be accounted for automatically, and the helper function will report that no input is present in both cases.

Supporting Relative URLs

Because transforms can be so easily integrated into Web pages, you might want your transform to support relative URLs if a URL is one of its properties. To do this, a transform must support the IBindHost interface. It is an optional interface for transforms to support only if they are interested in obtaining a site-specific bind host. A container is responsible to query for this interface, and if available, to call SetBindHost with the proper parameter. DirectAnimation does this. For more information about this interface, see the Platform SDK.

For example, transforms that require a URL as a property can use the container's bind host to obtain a base URL and to take a relative URL as the custom property. This will help in content writing so that absolute URLs are not required. For example, the MetaStream transform supports this interface.

Quick Setup

There is no explicit support for this in the DirectX Transform architecture. Every transform is expected to use specific knowledge of its functionality in order to perform a set up efficiently.

Transforms and Clip Lists

A DirectDraw surface can use a clipping rectangle to define a valid region for drawing onto the output surface. If there are multiple valid drawing regions on the output surface, DirectDraw uses a clip list to define them. Regions of the surface that are not covered by the clip list will remain unchanged by all drawing operations.

When writing transform output to DirectDraw surfaces such as video memory, the DirectX transform base class does not use clip list information of the destination surface to render its output. To render the output into the correct regions, it is first necessary to call the IDXTransform::Execute on each region of the clip list and assemble the clipped output object in system memory. When this is complete, the DirectDraw surface can be written directly to video memory.

Things You Need to Know About 2-D Image Transforms

This section contains the following topics.

Surface Pick Correlation

The following interface is intended for pick correlation.

interface IDXSurfacePick : IUnknown
{
    HRESULT PointPick([in]const DXVEC *pInPoint,
        [out]long * plInputSurfaceIndex,
        [out]DXVEC * pOutPoint );
}

It provides for pick correlation between a given point and the inputs to an image transform. It returns the hit input and the point at which the hit happened in the local coordinates of that input.

This interface is essential for image transforms because they might composite their results to a surface provided by the container at which point the information required for the pick correlation to the input is usually lost. The correlation needs to occur before the final compositing takes place.

There are helper functions that aid in this pick correlation. See the Wipe example to see how they are used. This example is located in the Samples\Multimedia\Dtrans\C++\Wipe\Wipedll folder. Also, for single image input and single image output transforms, the base class does the pick correlation with no need for added code in the transform itself. The base class makes use of the MapBoundsOut2In of the transform, which would need to be implemented correctly for the base class to work correctly.

It is possible that an image transform either has no inputs or cannot map the pick event back to one of the inputs. In those cases, PointPick will return DXT_S_HITOUTPUT if the pick point intersects the output surface. For example, this is the case for the gradient procedural surface.

Supporting IDXTScaleOutput

This is an optional interface for zero input image transforms and/or DXSurfaces to consider supporting. Containers invoke the SetOutputSize method on this interface to inform the transform of the desired size (width and height, in pixels) to render to. For example, this interface allows a very useful exchange of information between a container and a procedural surface. The DXETool application queries for this interface and invokes it upon window resize.

Things You Need to Know About 3-D Image Transforms

This section contains the following topics.

Geometry Convention for 3-D Effects

Use consistent, right-handed conventions.

The texture coordinates have an origin at the upper-left corner. Positive x goes right and positive y goes down. This is consistent with the Microsoft® Direct3D® Retained Mode convention and also with the dynamic HTML coordinate system for Web pages, which makes it convenient to use an HTML-rendered page as a texture.

To obtain the preceding conventions, set the Direct3D Retained Mode right-handed global flag. For example, this will cause the LoadFile method (from an .x file that is left-hand based) to convert the ordering of vertices in faces and the GenerateNormals method to be based on a counterclockwise ordering of vertices.

It is important to follow these conventions in order for the transform to be correctly hostable in DirectAnimation and DXETool. All meshes that are generated internally to the transform need to be based on these conventions. All C++ applications that use transforms (like DXETool) need to provide meshes and to interpret resulting meshes according to these conventions.

Mesh Pick Correlation

In the case of transforms that produce 3-D meshes, the container is expected to do the pick correlation based on the resulting MeshBuilder through Direct3D Retained Mode mechanisms. If the container needs to trace the pick event into the inputs (beyond the output mesh), by convention, the following needs to happen.

The following are cases where labeling is necessary for clarity.

In these cases, the container needs to call the ExternalUtil::SetAppData method to set a quantity called AppData on the submesh, texture, or surface. This AppData is used as the key for figuring out what was picked. For this to work, transforms need to communicate the AppData value to the elements of the output mesh.

In most cases, this copying over of the AppData happens automatically, and the transform writer doesn't need to do anything special. SetAppData and GetAppData are supported on DXSurfaces, Direct3D Retained Mode meshes, and textures. Also, the CreateD3DRMTexture DXSurface method and the AddMeshBuilder Direct3D Retained Mode MeshBuilder method copy over the AppData value to their result by default. It is common for these methods to be used to construct the output mesh from the inputs. For example, this includes a transform with a single mesh input and a mesh output that maintains an identical structure in the output as in the input and tweaks only vertices for an effect. It uses AddMeshBuilder to construct the output mesh from the input and doesn't need to do anything special to copy the AppData value.

In other rare cases, the transform synthesizes parts of the output from the inputs; that is, the relation is not a straightforward, one-to-one situation like the previous one. For example, more than one input might contribute to a mesh or to a texture. In that case, it is the responsibility of the transform to introduce a policy by which it computes AppData for the output mesh or texture from the AppData of its ingredient inputs. For example, if two input surfaces are overlaid to produce a texture that is used in the output, it is the responsibility of the transform to assign AppData that corresponds to the composite texture. One possible policy for a transform that fades from surface A to surface B is to copy over to the output texture the AppData of surface A for a Progress setting of approximately 0 ([0, 0.25]), and the AppData of Surface B for a Progress setting of approximately 1 ([0.75, 1]), and no AppData otherwise. This is also a possible policy when the transform is a morph between two input meshes.

If the AppData value is NULL, this implies that no AppData was set on the picked entity, and, therefore, the pick event is not traceable to the inputs. In the previous example, this would be the case for a Progress property in the range [0.25, 0.75].

Containers (like DirectAnimation) need to do ray correlation with the output mesh, which results in a mesh, a texture, and a texture coordinate. The AppData can be obtained from the texture and the mesh, which (according to the previous convention) will point to the corresponding input.

Using Direct3DRMMeshBuilder3

Direct3DRMMeshBuilder3 is the data object for 3-D meshes and is part of Direct3D Retained Mode, not DirectX Transform. As a result, you need Direct3D Retained Mode version 6.x to use 3-D transforms. A MeshBuilder3 has some added richness, especially useful for transforms. It supports the AddTriangles method for efficiently adding a collection of triangles (including strips, fans, and combinations thereof, such as disjoint collections) with optional per-vertex information (like texture coordinates, normals, and colors).

Furthermore, a MeshBuilder might include sub-MeshBuilders in a tree fashion. This places a requirement on transforms to be able to inquire for and process such submeshes. Submeshes share a pool of vertices in the root mesh, but have their own face sets that point into vertices in the pool. Submeshes enable the control of attributes such as color, visibility, texture, and material properties at the submesh level, and hence they provide added flexibility to the input of transforms in allowing such input to effectively be a composite geometry.

Submeshes provide a way to apply transforms (like Explode) on composite objects (like a car), instead of needing to apply a transform repeatedly to the different parts of the car (door, wheels, and so on). Note that this is different from the case of transforming (exploding) a collection of two distinct objects (like a car and a truck), in which case two different instances of the transform would be required. Notice that a transform (explosion) typically is dependent on a reference point like the center of the object being transformed (exploded), and hence transforming (exploding) two objects separately is different from transforming (exploding) the collection of the two.

You need to compute per-vertex normals initially and attach them to faces in a MeshBuilder. Later, and after vertices change, you can call GenerateNormals to update these normals. GenerateNormals doesn't create the normals from scratch.

It is not possible to use Direct3D Retained Mode Frame objects as inputs to transforms. This would introduce the complexity of parsing and interpreting general frames on every transform. Instead, MeshBuilders with submeshes are used, which are significantly simpler to deal with, even though they support only a limited capacity of aggregation.

You have the option of computing per-vertex normals and attaching them to vertices in a MeshBuilder. Alternatively you can call GenerateNormals to have Retained Mode compute these normals for you by averaging the normals of the faces that share the particular vertex. After vertices change, you can either call GenerateNormals again to recompute these normals or transform the normals yourself.

The transform architecture doesn't provide for view-dependent procedural meshes because there is no way for the container to communicate the view to the transform. The way to do this is to implement a Direct3D Retained Mode External Visual and obtain the view parameters from Direct3D Retained Mode directly.

Using a DXSurface as a 3-D texture

Use the IDXSurfaceFactory::CreateD3DRMTexture method on the interface to convert a DXSurface into a Direct3D Retained Mode texture.

Frames of Reference

Usually 3-D transforms operate in relation to a context like a point, axis, or plane. The transform has a choice of extracting such context from the extents of the inputs or of exposing a custom interface that permits a container to specify such context. For example, an explosion usually emanates from a point; it is incorrect to assume the origin to be that point because the input object might be positioned off-center.

Extent of Synthetically Generated Geometry

Transforms that synthesize the 3-D output geometry internally usually take no 3-D meshes as inputs. You are advised to produce their geometry in the [-1,1] cubed extent. This is not a requirement, but is a useful normalizing factor across containers.

Maintaining Coherency of Meshes

Certain meshes are originally or are pre-processed by a transform into a form where the same vertices are replicated across more than one face. This is done in order to have different textures for the different adjacent faces (hence different texture coordinates for the different instances of the vertex). Because it is common for transforms to operate by moving vertices along trajectories, it becomes important to move the different instances of the same vertex along the same trajectory, or else tearing will occur in the resulting output mesh. In some cases, tearing is a by-product of the nature of the effect, such as in the case of Explode.

A similar situation arises if the input mesh has vertices on edges of other faces. Because transforms generally do not preserve linearity, this will result in tearing, as well. This can only be avoided by insuring that there are no vertices on edges of other faces.


Top of Page Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.