// Copyright 2019 Diffuse Logic, all rights reserved.

#ifndef BELLA_SDK_NODEAPI_H
#define BELLA_SDK_NODEAPI_H

#include "bella_types.h"
#include "bella_sceneapi.h"
#include "../dl_core/dl_logging.h"
#include "../dl_core/dl_version.h"

namespace dl {

DL_API Version bellaSdkVersion(); // Used to write sdkVersion in nodelib libInfo.

namespace bella_sdk {

//=================================================================================================
// Bella Node API overview.
//=================================================================================================
// A node library consists at minimum of a .bnd (Bella Node Definition) file defining one or more
// nodes. As this type of nodelib is just a glorified json file, it can only define nodes that can
// hold data, and not have any actual functionality.
//
// To implement a nodelib with functionality, you will create a shared .bnl (Bella Node Library)
// library using this API, which exposes a set of extern "C" functions, beginning with one named
// "bndText", which returns your .bnd text directly from the shared library:
//
typedef const char* ( DL_STDCALL *pFnBndText )( void );
//
// Here is a bnd that defines a 2D texture node that doesn't inherit from any other node, so we
// provide the expected uvCoord & outColor attrs, and will implement them below (an example of
// inheriting from bella's texture3d base node is given further down in the header).
//
//    DL_C_EXPORT const char* DL_STDCALL bndText()
//    {
//        const char* s_bndTextRaw = R"(
//        {
//            "libInfo": {
//                "name": "My Super Bella Node Library",
//                "author": "Bella Nodes Inc.",
//                "description": "My Super Node library for for Bella. Copyright (c) 2025 My Company.",
//                "url": "mysite.com",
//                "prefix": "msbn",
//                "version": "@LIB_VERSION@",
//                "sdkVersion": "@SDK_VERSION@",
//                "svgIcon": "
//                    <svg width='288' height='288' viewBox='0 0 288.00 288.00'>
//                      <g><rect x='32' y='32' fill='#B4B4B4' fill-opacity='1' stroke-width='0.2' stroke-linejoin='round' width='224' height='224'/></g>
//                    </svg>"
//            },
//            "msbnChecker": {
//                //
//                // This is a 2D checker node that doesn't inherit any other node, so
//                // it must provide the expected uvCoord input, and outColor output.
//                //
//                "inputs": {
//                    "color1": {
//                        "type": "rgba",
//                        "value": { "r": 0.1, "g": 0.2, "b": 0.3, "a": 1.0 }
//                    },
//                    "color2": {
//                        "type": "rgba",
//                        "value": { "r": 0.3, "g": 0.2, "b": 0.1, "a": 1.0 }
//                    },
//                    "factor": {
//                        "type": "real",
//                        "value": 1.0
//                    },
//                    "uvCoord": { "type":  "vec2" }
//                },
//                "outputs": {
//                    "outValue": { "type": "real" },
//                    "outColor": { "type": "rgba" }
//                },
//                "layout": [
//                    "color1",
//                    "color2",
//                    "factor",
//                    "uvCoord"
//                ],
//                "svgIcon": "
//                    <svg width='288' height='288' viewBox='0 0 288.00 288.00'>
//                      <g><rect x='32' y='32' fill='#B4B4B4' fill-opacity='1' stroke-width='0.2' stroke-linejoin='round' width='224' height='224'/></g>
//                    </svg>"
//            }
//        })";
//        static String s_bndTextFinal = String( s_bndTextRaw )
//            .replace( "@LIB_VERSION@", currentLibVersion()
//            .replace( "@SDK_VERSION@", dl::bellaSdkVersion()
//            .toString()
//        );
//        return s_bndTextFinal.buf();
//    }
//
// Note the "libInfo" object, which must  be present for both .bnd and .bnl nodelibs. The most
// important attribute of the libInfo is the "prefix", which must be a valid C identifier, as it
// is required to be prefixed to all of your node typenames. You should choose something for the
// prefix which is unlikely to collide with other nodelibs; we do not yet have any sort of registry
// for these prefixes, but we will add one if it becomes necessary.
//
// In addition to prefix, the version and sdkVersion attributes are required, with the others being
// optional. The loaded nodelib will be identified by the hash of its filename (without extension
// and lowercased), so its filename should not be changed after publishing. Using this hash as the
// nodelib's identifier allows the use of private nodelibs which do not leak identifying info
// through bella files.
//
// The runtime LibInfo object also provides a libHash() method, which is the sha256 hash of the
// file itself, allowing to verify that a downloaded nodelib has not been tampered with. The bella
// CLI provides a --fileHash argument allowing to easily generate the same hash from your nodelib
// before publishing it.
//
// If the bnd text is successfully parsed, we will look to see if you provide optional functions
// for allocating/freeing any global data you may need:
//
//    DL_C_EXPORT int DL_STDCALL libInit();
//    DL_C_EXPORT int DL_STDCALL libFree();
//
// Next we will look for several more functions, based on the node typenames we parsed from your
// bnd text. nodeInit is called when an instance of the node is first created, allowing to allocate
// any extra data you may need. nodePrep is called just prior to rendering, allowing to set up and
// cache data to be used during evaluation. nodeEval is called during rendering and so must run as
// quickly as possible. nodeModify is called prior to rendering, and allows modifying the scene
// (when the target node is null), or the target node to which it is attached. nodeChanged will be
// called whenever an attribute in your node is changed, allowing to dynamically change other
// attributes in response. The Free function is called whenever an instance of your node is freed,
// allowing to clean up any data you allocated in nodeInit.
//
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerInit( INode* inode, void** data );
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerFree( const INode* inode, void** data );
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerPrep( INode* inode, void* data );
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerEval( EvalCtx* ctx, void* output );
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerModify( INode* modifier, ModifyFlags* flags, INode* target );
//    DL_C_EXPORT void DL_STDCALL msbnCheckerChanged( IInput* input )
//
// If a node provides the nodeInit function, it must also provide nodeFree, and only nodes which
// provide both of these will be checked for nodePrep and nodeEval, since these inherently make use
// of the data allocated & freed in nodeInit and nodeFree (this is important because nodes may
// inherit from other nodes, and we would not want to find nodeInit for the node, but nodePrep for
// a base node, so these four are considered a set). nodeChanged and nodeModify are optional, and
// may be used in conjunction with init/free or not.
//
// This loading process will be performed for all node types parsed from your bnd text, such that
// at the end of the process, we have loaded your node definitions and their implementations.
//
//=================================================================================================
// libInit & libFree.
//=================================================================================================
// If found, these procedures will be called once on library load, and once on library unload. Node
// libraries may use them to manage any global state:
//
//    static std::atomic<int> s_state = ATOMIC_VAR_INIT( 0 );
//    
//    DL_C_EXPORT int DL_STDCALL libInit()
//    {
//        auto i = ++s_state;
//        DL_ASSERT( i == 1 );
//        return 0;
//    }
//    
//    DL_C_EXPORT int DL_STDCALL libFree()
//    {
//        auto i = --s_state;
//        DL_ASSERT( i == 0 );
//        return 0;
//    }
//
// If libInit is implemented, then so must be libFree, else bella will refuse to load your node.
// These should both return 0 on success, otherwise 1 (we may define other error codes later).
//
typedef int( DL_STDCALL *pFnLibInit )( void );
typedef int( DL_STDCALL *pFnLibFree )( void );

//=================================================================================================
// nodeInit.
//=================================================================================================
// Arguments passed to nodeInit are the node instance, and a pointer to its user data, which you
// may fill with data. Generally, you will want to make a struct for the data, with InputInfo and
// OutputInfo helpers for your attributes. These can be created manually, but we have a pattern to
// simplify things and automatically keep definitions & use in sync, which involves defining this
// DL_ITERATE_ATTRS macro (described in more depth further down in this header) for your node:
//
//    #undef DL_ITERATE_ATTRS
//    #define DL_ITERATE_ATTRS(X) \
//        X(I, Rgba, color1 ) \
//        X(I, Rgba, color2 ) \
//        X(I, Real, factor ) \
//        X(I, Vec2, uvCoord ) \
//        X(O, Real, outValue ) \
//        X(O, Rgba, outColor )
//
// This is then used to add InputInfo & OutputInfo fields for all the specified input & output
// attrs in your node's data:
//
//    struct MsbnChecker
//    {
//        DL_ITERATE_ATTRS( DL_MAKE_ATTRS );
//    };
//
// It is then also used when initializing the node:
// 
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerInit( INode* inode, void** data )
//    {
//        DL_INIT_NODEINFO( MsbnChecker );
//        DL_ITERATE_ATTRS( DL_INIT_ATTRS );
//        return true;
//    }
//
// The DL_MAKE_ATTRS, DL_INIT_NODEINFO and DL_INIT_ATTRS macros are described in more detail
// further down in this header.
//
typedef bool( DL_STDCALL *pFnNodeInit )( INode* node, void** data );

//=================================================================================================
// nodeFree.
//=================================================================================================
// nodeFree is called from the node's destructor. NOTE that you must make sure never to hold onto
// bella_sdk::Node wrappers over the INode* instances passed to any of these functions, as this
// will cause the node to live forever.
//
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerFree( const INode* inode, void** data )
//    {
//        DL_FREE_NODEINFO( MsbnChecker );
//    }
//
// The DL_FREE_NODEINFO macro is described in more detail further down in this header.
//
typedef bool( DL_STDCALL* pFnNodeFree )( const INode* node, void** data );

//=================================================================================================
// nodePrep.
//=================================================================================================
// nodePrep is called just prior to rendering, so it is here that you want to check whether your
// inputs are connected (since if they are not, you can cache their immediate values and avoid
// evaluations during nodeEval). This is done for you if you use the Input/OutputInfo helpers. This
// is important, because it is not allowed to write *any* data from within nodeEval, since it is
// called in a multithreaded context. As with nodeInit we use the DL_ITERATE_ATTRS pattern here:
//
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerPrep( INode* inode, void* data )
//    {
//        DL_PREP_NODEINFO( MsbnChecker );
//        DL_ITERATE_ATTRS( DL_PREP_ATTRS );
//        return true;
//    }
//
// This is a very powerful function, for example used by the bella "smart" nodes (e.g. pbr, uber)
// to read their attribute values and generate/synchronize an entirely different node graph (in the
// case of pbr/uber they create a blendMaterial with several sub-materials, attaching textures from
// the pbr/uber, and so forth, ultimately returning the ID of the blendMaterial) in the scene. They
// then cache and return this node ID when outNode is requested in their nodeEval function.
//
// The DL_PREP_NODEINFO and DL_PREP_ATTRS macros are described in more detail further down in this
// header.
//
typedef bool( DL_STDCALL *pFnNodePrep )( INode* node, void* data );

//=================================================================================================
// nodeEval.
//=================================================================================================
// nodeEval is called during rendering, and you should take care to optimize performance here as
// much as possible. It is also called in a multithreaded context, so it is not allowed to do *any*
// writing of data from within the function; all such must be handled beforehand in nodePrep. Here,
// we see how to handle being asked to provide our node's outValue and outColor output attributes.
//
//    DL_C_EXPORT bool DL_STDCALL msbnCheckerEval( EvalCtx* ctx, void* output )
//    {
//        DL_EVAL_NODEINFO( MsbnChecker );
//        DL_EVAL_IINFO( factor );
//        
//        if ( info->outValue.matches( ctx ) )
//            return writeOutput( output, factor );
//
//        if ( info->outColor.matches( ctx ) )
//        {
//            DL_EVAL_IINFO( color1 );
//            DL_EVAL_IINFO( color2 );
//            DL_EVAL_IINFO( uvCoord );
//
//            auto choice = ( ( uvCoord.v - floor( uvCoord.v ) ) < 0.5 ) ^
//                          ( ( uvCoord.u - floor( uvCoord.u ) ) < 0.5 );
//
//            auto rgba = ( choice & 1 ) ? color2 : color1;
//            rgba.rgb = ( Rgb )( rgba.v3 * factor );
//
//            return writeOutput( output, rgba );
//        }
//
//        return false;
//    }
//
// The DL_EVAL_NODEINFO and DL_EVAL_IINFO macros are described in more detail further down in this
// header.

typedef bool( DL_STDCALL *pFnNodeEval )( EvalCtx* ctx, void* output );

//=================================================================================================
// nodeModify.
//=================================================================================================
// nodeModify is called just prior to rendering, and allows making modifications to a node, or to
// the scene. It will be called at least once, with target=null, with the expectation being that
// you may modify the scene itself (which may be obtained from the passed 'modifier' node). If it
// is also attached to any node in the heirarchy, it will then be called with that node as target,
// providing an opportunity to modify the target node itself.
// 
// NOTE: when called with a target, this function will be called on multiple threads simultaneously
// so you must not make any modifications outside of the target node. When called with target null,
// modifiers are called in a single thread, according to their priority attribute value (note that
// this is in the user's control, so you still cannot make assumptions about the order). In either
// case these calls must be idempotent - they must leave the scene in the same state, regardless
// when, or how many times they may be called (as the node may be getting called when initially
// setting up the scene, or for example, the call may be occurring during an IPR update, in which
// case you would not want to add duplicates of previously-added nodes, and so forth).
// 
// Note as well that when called to modify the scene, this occurs very early in the process, so
// other operations, such as execution of smart nodes, will not yet have been executed, and neither
// will other nodes have yet had their prep functions called.
// 
// The return value must indicate whether any modification was actually made, since we need to run
// through and re-check dependencies, and so forth, if changes are made. For the same reason, you
// must set the ModifyFlags properly, to indicate the types of changes you have made.
//
//    DL_NODE_EXPORT bool msbnCheckerModify( INode* modifier, ModifyFlags* flags, INode* target )
//    {
//        if ( !modifier || !target || !target->isTypeOf( "mesh" ) )
//            return false; // This node doesn't do scene modification, so just return false.
//
//        // Wrap these for easier use.
//        auto mesh = bella_sdk::Node( target );
//        auto mod = bella_sdk::Node( modifier );
//
//        for ( UInt i = 0; i < mesh["steps"].inputCount(); ++i )
//        {
//            auto points = mesh["steps"][i]["points"].asBufferT<Pos3f>();
//
//            for ( UInt j = 0; j < points.count; ++j )
//                std::swap( points.ptr[j].x, points.ptr[j].y );
//        }
//
//        // Indicate we changed geometry.
//        ModifyFlagsEnable( flags, ModifyFlags_Geometry );
//
//        // Return true because the target mesh was modified.
//        return true;
//    }
//
typedef bool( DL_STDCALL *pFnNodeModify )( INode* modifier, ModifyFlags* flags, INode* target );

//=================================================================================================
// nodeChanged.
//=================================================================================================
// nodeChanged allows a node to update its state whenever one of its inputs is changed. This can be
// used to implement enum string inputs that should change the values of other inputs when they are
// changed, to update the input's enabled state, and so forth. This is not yet well used or tested,
// so we don't recommend actually trying to implement it at this time (we envision providing a more
// comprehensive mechanism for this purpose in the future).
//
//    DL_C_EXPORT void DL_STDCALL msbnCheckerChanged( IInput* iinput )
//    {
//        auto input = bella_sdk::Input( iinput );
//
//        if ( input.name() == "color1" )
//            input.node()["color2"] = input.asRgba(); // NOTE: not using eval here.
//    }
//
typedef bool( DL_STDCALL *pFnNodeChanged )( IInput* input );

//=================================================================================================
// EvalCtx.
//=================================================================================================
// You will not deal with this much; it is created in the engine and passed through the chain of
// evaluation. Basically, somewhere in the engine we want to get the outColor of a texture or some
// other attribute, so an EvalCtx is created to hold information about the output and the call,
// along with any other information necessary to properly evaluate it. For the most part you just
// pass along the EvalCtx you are given, when you subsequently need to do evaluations of your own
// (though this is largely abstracted if you use InputInfo & OutputInfo and their helper macros).
//

#define DL_NODEAPI_BOOLFN(NAME, METHOD_ARGS, FN_SIG, CALL_ARGS) \
    typedef Bool( DL_STDCALL *NAME##Fn ) FN_SIG; \
    NAME##Fn _##NAME = nullptr; \
    Bool NAME METHOD_ARGS const { return _##NAME && _handle && _##NAME CALL_ARGS; }

struct DL_API DL_ALIGN( 8 ) CamCtx
{
    // API Version 26.1.0.0
    const void*     _handle         = nullptr;          // 8
    Float           _focusDist      = 8.0f;             // 4
    Float           _focalLen       = 42.0f;            // 4
    Vec2f           _sensorSize     = Vec2f::zero;      // 8
    Pos2u           _resolution     = Pos2u::zero;      // 8
    Byte            _pad0[32]       = { 0 };            // 32  ___64
    Mat4f           _transform      = Mat4f::identity;  // 64  ___64

    // Project a point in world coordinates to screen UV.
    //
    DL_NODEAPI_BOOLFN( getScreenUV, ( Pos3 worldPoint, Pos2* uv ),
        ( const void*, Pos3, Pos2* ), ( _handle, worldPoint, uv ) )
};

struct DL_API DL_ALIGN( 8 ) EnvCtx
{
    // API Version 26.1.0.0
    const void*     _handle         = nullptr;          // 8
};

struct DL_API DL_ALIGN( 8 ) HitCtx
{
    // API Version 26.1.0.0
    const void*     _handle         = nullptr;          // 8
};

enum EvalFlags
{
    // API Version 26.1.0.0
    EvalFlags_None      = 0,
    EvalFlags_ForScalar = ( 1 << 1 ),
    EvalFlags_ForNormal = ( 1 << 2 ),
    EvalFlags_ForLinear = EvalFlags_ForScalar | EvalFlags_ForNormal
};
DL_FLAGS_FUNCTIONS( EvalFlags )

struct DL_API DL_ALIGN( 8 ) EvalCtx
{
    // API Version 26.1.0.0
    UInt64          _hash           = 0;                // 8
    const INode*    _xform          = nullptr;          // 8
    const IInput*   _input          = nullptr;          // 8
    const IOutput*  _output         = nullptr;          // 8
    const void*     _data           = nullptr;          // 8
    const CamCtx*   _camCtx         = nullptr;          // 8
    const EnvCtx*   _envCtx         = nullptr;          // 8
    const HitCtx*   _hitCtx         = nullptr;          // 8   ___64
                                                        
    Mat4            _transform      = Mat4::identity;   // 128 ___64x2
                                                        
    EvalFlags       _flags          = EvalFlags_None;   // 4
    Mat3f           _tangentFrame   = Mat3f::identity;  // 36
    Aabbf           _bbox           = Aabbf::identity;  // 24  ___64
                                                        
    Pos3f           _localPosition  = Pos3f::zero;      // 12
    Pos3f           _worldPosition  = Pos3f::zero;      // 12 // Not currently set.
    Byte            _pad0[23]       = { 0 };            // 23
    UInt8           _numUVs         = 0;                // 1
    const Vec2f*    _uvs            = nullptr;          // 8
    const Vec2f*    _stashedUVs     = nullptr;          // 8   ___64
                                                        
    Vec2f           _scratchUVs[16] = { 0 };            // 128 ___64x2

    // Create an EvalCtx for an input. If the context is being used to evaluate a texture, you may
    // use the _scratchUVs instead of providing space of your own and calling setUVs.
    //
    static DL_FI EvalCtx make( const IInput* input, Bool useScratchUVs = false )
    {
        auto ctx = EvalCtx();
        ctx.setInput( input );
        if ( useScratchUVs )
            ctx.useScratchUVs();
        return ctx;
    }

    // Create an EvalCtx for an output. If the context is being used to evaluate a texture, you may
    // use the _scratchUVs instead of providing space of your own and calling setUVs.
    //
    static DL_FI EvalCtx make( const IOutput* output, Bool useScratchUVs = false )
    {
        auto ctx = EvalCtx();
        ctx.setOutput( output );
        if ( useScratchUVs )
            ctx.useScratchUVs();
        return ctx;
    }

    // The context has a hash, usually taken from the xform, which may be used as seed for
    // generating e.g. pseudorandom colors that stay consistent for instances from frame to frame.
    //
    DL_FI UInt64        hash() const                    { return _hash; }

    // It may be (it usually is) that evaluation is occurring in the context of some instance,
    // and if so, that instance (an xform node), along with its cached transform (not the xform's
    // transform, but the complete transform of the path to this instance), will be found here.
    //
    DL_FI const INode*  xform() const                   { return _xform; }
    DL_FI Mat4          transform() const               { return _transform; }
    DL_FI void          setTransform( Mat4 xform )      { _transform = xform; }

    // It is always an output that we are evaluating, e.g. to get the outColor of a texture, and
    // these provide that output. However sometimes it is clearer or more convenient to use the
    // input, and you can set the state using either of them. But note that input() may or may
    // not be null, regardless whether output() is. Also note that the output() may be swapped for
    // another when doing nested evaluations, so it is possible at times for it to be a different
    // output than the input's actual output.
    // 
    // That said, these are used almost exclusively by the evalOutput template functions further
    // down in this header, which in turn are used exclusively by the bella Output implementation,
    // and are not something API users need to concern themselves with.
    //
    DL_FI const IInput* input() const { return _input; }
    DL_FI void setInput( const IInput* input )
    {
        _input  = input;
        _output = input ? input->output() : nullptr;
        _data   = _output && _output->node() ? _output->node()->data() : nullptr;
    }

    DL_FI const IOutput*output() const { return _output; }
    DL_FI void setOutput( const IOutput* output )
    {
        _output = output;
        _data   = output && output->node() ? output->node()->data() : nullptr;
    }

    // This is opaque node data that is passed along through the chain of evaluation. setData()
    // is used by node implementations to point the context at a sub-object in their own node data
    // (e.g. a Texture3D included in the checker node's Checker struct, and delegated to for
    // base texture3d outputs), for calling base node implementations they inherit.
    // 
    // Like setOutput and setInput above, these are likewise used entirely by the implementation
    // itself, and API users should not have any need to use them.
    //
    DL_FI const void*   data() const                    { return _data; }
    DL_FI void          setData( const void* data )     { _data = data; }

    // When evaluating an output, we sometimes need to know how it will be used, for example when
    // reading a value from a texture file, whether it will be used as a scalar value and should
    // not have color space applied. The values of these may therefore also be necessary for node
    // implementations, to return proper values, or for instance for code that wants to bake a
    // texture, to indicate the purpose of the evaluation. As you can see in the EvalFlags enum
    // above, EvalFlags_ForLinear is a superset of EvalFlags_ForScalar and EvalFlags_ForLinear.
    //
    DL_FI void          setForScalar( Bool enable )     { EvalFlagsEnable( _flags, EvalFlags_ForScalar, enable ); }
    DL_FI void          setForNormal( Bool enable )     { EvalFlagsEnable( _flags, EvalFlags_ForNormal, enable ); }
    DL_FI void          setForLinear( Bool enable )     { EvalFlagsEnable( _flags, EvalFlags_ForLinear, enable ); }
                                                        
    DL_FI Byte          isForScalar() const             { return EvalFlagsEnabled( _flags, EvalFlags_ForScalar ); }
    DL_FI Byte          isForNormal() const             { return EvalFlagsEnabled( _flags, EvalFlags_ForNormal ); }
    DL_FI Byte          isForLinear() const             { return EvalFlagsEnabled( _flags, EvalFlags_ForLinear ); }

    // Often we are evaluating textures, and the UVs for the current evaluation will be passed
    // along here. However, you rarely (if ever) need to concern yourself with these, since the
    // system takes care of connecting unconnected uvCoord inputs to the appropriate UV source in
    // the core, and you should evaulate the uvCoord in order to obtain UVs properly projected,
    // and/or modified by texforms and other nodes. That is to say, the bella core is the main
    // consumer of these, which it stores for automatic connection to any unconnected uvCoord
    // attributes, during render preprocess.
    // 
    // However, if your node is something like the bump map, which needs to pass modified UVs to
    // another texture, you might store the uvs() pointer, fill a temporary buffer with shifted
    // UVs, call setUVs, and evaluate the texture, then call setUVs to put the original UVs back.
    //
    DL_FI UInt          numUVs() const                  { return _uvs == _scratchUVs ? 16 : _numUVs; }
    DL_FI void          setNumUVs( UInt8 num )          { _numUVs = num; }
                                                        
    DL_FI const Vec2f*  uvs() const                     { return _uvs; }
    DL_FI void          setUVs( const Vec2f* uvs )      { _uvs = uvs; }

    // Stashed UVs are used e.g. when evaluating a projected texture, where we will compute the
    // projection based on the current UVs, write them to our scratch UVs, call stashUVs() to have
    // our scratch UVs returned from uvs(), evaluate the texture, and then call unswapUVs() to put
    // the original UVs back into uvs(). This is mainly used internally, prior to evaluating a
    // texture
    //
    DL_FI void          stashUVs()                      { _stashedUVs = _uvs; _uvs = _scratchUVs; }
    DL_FI void          unstashUVs()                    { _uvs = _stashedUVs; _stashedUVs = nullptr; }
    DL_FI Vec2f*        scratchUVs()                    { return _scratchUVs; }

    // These are used in situations like sampling textures for baking, where you would typically
    // use EvalCtx::make( output, useScratchUVs=true ), and then use the context in a loop, setting
    // UV 0 to the current loop UV.
    //
    DL_FI void          useScratchUVs()                 { _uvs = _scratchUVs; }
    DL_FI void          setUV0( Vec2 uv )               { _scratchUVs[0] = Vec2f::make( uv.u, uv.v ); }
    DL_FI void          setUV0( Vec2f uv )              { _scratchUVs[0] = uv; }
    DL_FI void          setUV0( Float u, Float v )      { _scratchUVs[0] = Vec2f::make( u, v ); }
    DL_FI void          setUV0( Double u, Double v )    { _scratchUVs[0] = Vec2f::make( u, v ); }

    // These are set by the core for a given evaluation, but as with UVs it may be necessary in
    // some cases to temporarily override them for a subsequent evaluation you make in your code.
    //
    DL_FI Aabb          bbox() const                    { return ( Aabb )_bbox; }
    DL_FI void          setBbox( Aabb bbox )            { _bbox = ( Aabbf )bbox; }
                                                        
    DL_FI Mat3          tangentFrame() const            { return ( Mat3 )_tangentFrame; }
    DL_FI void          setTangentFrame( Mat3 frame )   { _tangentFrame = ( Mat3f )frame; }
                                                        
    DL_FI Pos3          localPosition() const           { return ( Pos3 )_localPosition; }
    DL_FI void          setLocalPosition( Pos3 pos )    { _localPosition = ( Pos3f )pos; }

    // Sub-contexts.
    
    DL_FI const CamCtx& camCtx() const { static CamCtx _s = {}; return _camCtx ? *_camCtx : _s; }
    DL_FI const EnvCtx& envCtx() const { static EnvCtx _s = {}; return _envCtx ? *_envCtx : _s; }
    DL_FI const HitCtx& hitCtx() const { static HitCtx _s = {}; return _hitCtx ? *_hitCtx : _s; }

    // Since evaluations are performed on a per-output basis, we must set the output to be eval'd
    // in the EvalCtx; when one output ends up evaluating another, we must set it as current, but
    // also remember the previous one and restore it afterward. The system takes care of this, so
    // there should be no reason for clients to use this.
    //
    class DL_API EvalScope
    {
    public:
        EvalScope( EvalCtx* ctx, const IOutput* next ) : _ctx( ctx ), _prev( nullptr )
        {
            if ( ctx ) { _prev = ctx->output(); ctx->setOutput( next ); }
        }
        ~EvalScope()
        {
            if ( _ctx ) { _ctx->setOutput( _prev ); }
        }
    private:
        EvalCtx* _ctx = nullptr;
        const IOutput* _prev = nullptr;
    };

    // This is used by node implementations to evaluate using implementations of their base nodes.
    // If a node wishes to use the implementations of its base nodes, it will include a copy of the
    // structures they use, in its own node data, and use this object to temporarily point an eval
    // context to those structures, before evaluating the base node outputs. You will not likely
    // use this directly, it will be used by the various eval helpers defined and described below.
    //
    class DL_API DataScope
    {
    public:
        DataScope( EvalCtx* ctx, const void* data ) : _ctx( ctx ), _data( nullptr )
        {
            if ( ctx ) { _data = ctx->data(); ctx->setData( data ); }
        }
        ~DataScope()
        {
            if ( _ctx ) { _ctx->setData( _data ); }
        }
    private: 
        EvalCtx* _ctx = nullptr;
        const void* _data = nullptr;
    };
};

DL_SASSERT( std::is_standard_layout<CamCtx>() );
DL_SASSERT( std::is_standard_layout<EnvCtx>() );
DL_SASSERT( std::is_standard_layout<HitCtx>() );
DL_SASSERT( std::is_standard_layout<EvalCtx>() );

DL_SASSERT( sizeof( void* )     == 8 );
DL_SASSERT( sizeof( EvalFlags ) == 4 );
DL_SASSERT( sizeof( CamCtx )    == 136 );
DL_SASSERT( sizeof( EnvCtx )    == 8 );
DL_SASSERT( sizeof( HitCtx )    == 8 );
DL_SASSERT( sizeof( EvalCtx )   == 448 );

//=================================================================================================
// Helpers used internally for implementing evaluation of outputs.
//=================================================================================================

template<typename T> static DL_FI Bool evalOutput( EvalCtx* ctx, const IOutput* output, T& value )
{
    if ( !output )
        return false;

    auto node = output->node();
    if ( !node )
        return false;

    EvalCtx::EvalScope es( ctx, output );
    return node->nodeEval( ctx, &value );
}

template<> DL_FI Bool evalOutput( EvalCtx* ctx, const IOutput* output, BufRef& value )
{
    if ( !output )
        return false;

    auto node = output->node();
    if ( !node )
        return false;

    auto type = output->bufferType();
    if ( type == DataType_Null )
        return false;

    EvalCtx::EvalScope es( ctx, output );
    BufRef bufRef;
    if ( !node->nodeEval( ctx, &bufRef ) )
        return false;

    value = bufRef;
    return true;
}

//=================================================================================================
// A helper used internally for evaluation of inputs.
//=================================================================================================

template<typename T> static DL_FI T evalInput( EvalCtx* ctx, const IInput* input, T def = T() )
{
    T t;
    if ( !input->connected() )
        return input->get( t, false ) ? t : def; // NOTE: no eval (and it's disconnected anyway).

    return evalOutput( ctx, input->output(), t ) ? t : def;
}

//=================================================================================================
// Input helper for structs used in node eval. These are added to node data structs, and are used
// by the init/prep functions to set them up for use in eval. You can see they take care of input
// validation and caching values if necessary during prep. These are usually created using the
// MAKE/INIT/PREP/EVAL macros (see examples below).
//=================================================================================================

template<typename T> struct InputInfo
{
    String          path;
    const IInput*   input;
    T               cache;
    Bool            useCache;
    Bool            isUvCoord;

    DL_FI Bool init( const INode* node )
    {
        input       = nullptr;
        cache       = T();
        useCache    = false;
        isUvCoord   = false;

        if ( !node )
        {
            logError( "Cannot init '%s' due to null input.", path.buf() );
            return false;
        }

        auto tmp = node->findInput( path );
        if ( !tmp )
        {
            logError( "Input '%s' not found on node '%s'.", path.buf(), node->id().buf() );
            return false;
        }

        if ( tmp->type() != dl::bella_sdk::attrType<T>() )
        {
            logError( "Wrong type for '%s': .", tmp->path().buf(), tmp->typeString().buf() );
            return false;
        }

        if ( tmp->name() == "uvCoord" )
        {
            isUvCoord = true;
        }

        input = tmp;
        return true;
    }

    DL_FI Bool prep()
    {
        if ( !input )
        {
            logError( "Cannot prep '%s' due to null input.", path.buf() );
            return false;
        }

        useCache = !isUvCoord && !input->connected();
        return input->get( cache, false ); // NOTE: no eval, we're caching immediate value.
    }

    DL_FI T eval( EvalCtx* ctx = nullptr ) const
    {
        T t;
        return useCache || !input || !input->get( t, true, ctx ) ? cache : t;
    }
};

// These macros are used to simplify usage of InputInfo.

#define DL_MAKE_IINFO(T, NAME) \
    InputInfo<T> NAME = { #NAME, nullptr, T(), true };

#define DL_INIT_IINFO(NAME) \
    if ( !info->NAME.init(inode) ) \
        { logWarning("nodeInit() failed for "#NAME); return false; }

#define DL_PREP_IINFO(NAME) \
    if ( !info->NAME.prep() ) \
        { logWarning("nodePrep() failed for "#NAME); return false; }

#define DL_EVAL_IINFO(NAME) \
    auto NAME = info->NAME.eval( ctx );

//=================================================================================================
// Output helper for structs used in node eval. Like InputInfo, these are added to node data and
// used, along with their MAKE/INIT/PREP macros, to implement node functionality, as shown in the
// examples below.
//=================================================================================================

template<typename T> static DL_FI Bool writeOutput( void* dst, T src )
{
    if ( !dst )
        return false;

    memcpy( dst, &src, sizeof( T ) );
    return true;
}

template<> DL_FI Bool writeOutput( void* dst, String src )
{
    if ( !dst )
        return false;

    *( ( String* )dst ) = src;
    return true;
}

#define DL_WRITE_OUTPUT( ATTR, VAL ) \
    if ( ATTR.matches( ctx ) ) \
        return writeOutput( output, VAL );

template<typename T> struct OutputInfo
{
    const String    name;
    const IOutput*  output;

    DL_FI Bool init( const INode* node )
    {
        output = nullptr;

        const auto tmp = node ? node->output( name ) : nullptr;
        if ( !tmp )
        {
            logError( "Output '%s' not found on node '%s'.", name.buf(), node->id().buf() );
            return false;
        }

        output = tmp;
        return true;
    }

    DL_FI Bool prep()
    {
        return true; // Nothing to do.
    }

    DL_FI Bool matches( EvalCtx* ctx ) const
    {
        return ctx && ctx->output() == output;
    }

    DL_FI Bool write( void* dst, T v )
    {
        return writeOutput( dst, v );
    }
};

// These macros are used to simplify usage of OutputInfo.

#define DL_MAKE_OINFO(T, NAME) \
    OutputInfo<T> NAME = { #NAME, nullptr };

#define DL_INIT_OINFO(NAME) \
    if ( !info->NAME.init( inode ) ) \
        { logWarning("nodeInit() failed for "#NAME); return false; }

#define DL_PREP_OINFO(NAME) \
    if ( !info->NAME.prep() ) \
        { logWarning("nodePrep() failed for "#NAME); return false; }

//=================================================================================================
// Helpers for use in node implementations.
//=================================================================================================

// This function is intended to be called at the beginning of a node Init function, like this:
//
//    auto info = initNodeInfo<T>( inode, data );
//    if ( !info )
//        return false;
//
// If data points to non-null data, it is assumed to be pointing to and instance of the node's data
// struct (the T), Otherwise, an instance of T is allocated, assigned to the storage pointed to by
// the data parameter, and returned. This is usually used by way of the DL_INIT_NODEINFO macro.
//
template<typename T> static DL_FI T* initNodeInfo( const INode* node, void** data )
{
    if ( !node )
    {
        logError( "Null node passed to initNodeInfo." );
        return nullptr;
    }

    if ( data && *data )
        return ( T* )*data;

    *data = new T();
    return ( T* )*data;
}

// This function is intended to be called at the beginning of a node Prep function, like this:
//
//    auto info = getNodeInfo<T>( inode, data );
//    if ( !info )
//        return false;
//
// If simply interprets the given data as T, and is generally used by way of DL_PREP_NODEINFO.
//
template<typename T> static DL_FI T* getNodeInfo( const INode* node, void* data )
{
    if ( !node )
    {
        logError( "Null node passed to getNodeInfo." );
        return nullptr;
    }

    if ( !data )
    {
        logError( "Null data passed to getNodeInfo." );
        return nullptr;
    }

    return ( T* )data;
}

// This function is intended to be called at the beginning of a node Eval function, like this:
//
//    auto info = getNodeInfo<T>( ctx );
//    if ( !info )
//        return false;
//
// If simply extracts the EvalCtx data as T, and is usually used by way of DL_EVAL_NODEINFO.
//
template<typename T> static DL_FI T* getNodeInfo( EvalCtx* ctx )
{
    return ctx ? ( T* )ctx->data() : nullptr;
}

// This function is intended to be called in a node Free function, like this:
//
//    return freeNodeInfo<T>( inode, data );
//
// It is always safe to call this, and do nothing else, since the storage pointed to by the data
// parameter was allocated by you, or is null. This is usually used by way of DL_FREE_NODEINFO.
//
template<typename T> static DL_FI Bool freeNodeInfo( const INode* node, void** data )
{
    DL_UNUSED( node );
    DL_ASSERT( node->data() == *data );

    delete ( T* )*data;
    *data = nullptr;
    return true;
}

// The below macros are for simplifying the setup of node data/init/prep, and they abstract use
// of the helpers defined above. How they work is a bit esoteric (google X macros), so here is an
// example, given this .bnd definition:
//
//    "myNode": {
//        "inputs": {
//            "frequency": { "type": "real" },
//            "multiplier": { "type": "real" }
//        },
//        "outputs": {
//            "outValue": { "type": "real" }
//        }
//    }
//
// So we will want a "MyNode" struct for our data, and it should get InputInfo & OutputInfo fields
// for its attributes. We begin by redefining DL_ITERATE_ATTRS to describe our inputs & outputs,
// where the macro is "X", the first argument is "I" or "O" for Input or Ouput, and the remaining
// arguments give the type and name of the attribute:
//
//    #undef DL_ITERATE_ATTRS
//    #define DL_ITERATE_ATTRS(X) \
//        X(I, Real, frequency) \
//        X(I, Real, multiplier) \
//        X(O, Real, outValue)
//
// This list will then be used with DL_ITERATE_ATTRS and the DL_[MAKE|INIT|PREP]_ATTRS macros that
// are defined below, to create Input/OutputInfos in our data struct, to call init() and prep() on
// them in our node's init() and prep() functions.
//
//    struct MyNode
//    {
//        DL_ITERATE_ATTRS( DL_MAKE_ATTRS );
//    };
//
//    DL_C_EXPORT bool DL_STDCALL myNodeInit( INode* inode, void** data )
//    {
//        DL_INIT_NODEINFO( MyNode );
//        DL_ITERATE_ATTRS( DL_INIT_ATTRS );
//        return true;
//    }
//
//    DL_C_EXPORT bool DL_STDCALL myNodeFree( const INode* inode, void** data )
//    {
//        DL_FREE_NODEINFO( MyNode );
//    }
//
//    DL_C_EXPORT bool DL_STDCALL myNodePrep( INode* inode, void* data )
//    {
//        DL_PREP_NODEINFO( MyNode );
//        DL_ITERATE_ATTRS( DL_PREP_ATTRS );
//        return true;
//    }
//
//    DL_C_EXPORT bool DL_STDCALL myNodeEval( EvalCtx* ctx, void* output )
//    {
//        DL_EVAL_NODEINFO( MyNode );
//
//        if ( info->outValue.matches( ctx ) )
//        {
//            DL_EVAL_IINFO( frequency );
//            DL_EVAL_IINFO( multiplier );
//            return writeOutput( output, frequency * multiplier );
//        }
//
//        return false;
//    }
//
// In this way we define our attributes only once, and ensure we cannot forget to init or prepare
// them, if we add more later.

#define DL_INIT_NODEINFO(T) \
    auto info = initNodeInfo<T>( inode, data ); \
    if ( !info ) { return false; }

#define DL_PREP_NODEINFO(T) \
    auto info = getNodeInfo<T>( inode, data ); \
    if ( !info ) { return false; }

#define DL_EVAL_NODEINFO(T) \
    auto info = getNodeInfo<T>( ctx ); \
    if ( !info ) { return false; }

#define DL_FREE_NODEINFO(T) \
    return freeNodeInfo<T>( inode, data )

#define DL_MAKE_ATTRS(IO,T,A,...) DL_MAKE_##IO##INFO( T, A )
#define DL_INIT_ATTRS(IO,T,A,...) DL_INIT_##IO##INFO( A )
#define DL_PREP_ATTRS(IO,T,A,...) DL_PREP_##IO##INFO( A )

//=================================================================================================
// Helpers for implementing init, prep, and eval with inherited nodes. Note that these are only
// useful for base & derived nodes defined in your own library, since there is no linkage between
// it and core bella. Inheriting from core bella nodes involves implementing their attributes and
// handling init/prep yourself (meaning, you literally add them to your node data, and init/prep
// them like any others).
//=================================================================================================

// This function is intended to be called in a node Init function, like this:
//
//    if ( !initBase( fileTextureInit, &info->fileTexture, node ) )
//        return false;
//
// Where fileTexture is a base of this node type, and info->fileTexture is a FileTexture struct
// embedded in our node data struct, like this: struct MyNode { FileTexture fileTexture; };
//
template<typename Func> static DL_FI Bool initBase( Func init, void* data, INode* node )
{
    return init( node, &data );
}

// This function is intended to be called in a node Prep function, like this:
//
//    if ( !prepBase( fileTexturePrep, &info->fileTexture, node ) )
//        return false;
//
// Where fileTexture is a base of this node type, and info->fileTexture is a FileTexture struct
// embedded in the current node's data.
//
template<typename Func> static DL_FI Bool prepBase( Func prep, void* data, INode* node )
{
    return prep( node, data );
}

// This function is intended to be called in a node Eval function, like this:
//
//    if ( !info->outNormal.matches( ctx ) )
//        return evalBase( fileTextureEval, &info->fileTexture, ctx, output );
//
// This you can see, is used to delegate evaluation to the base node's implementation, when the
// current node does not want to either ignore or override the base node behavior. It takes care of
// calling EvalCtx::setData with the base node data, and then restoring it after the call.
//
template<typename Func> static DL_FI Bool evalBase( Func eval, void* data, EvalCtx* ctx, void* output )
{
    EvalCtx::DataScope ds( ctx, data );
    return eval( ctx, output );
}

// This function is intended to be called in a node Eval function, like this:
//
//    auto map = evalBase( info->fileTexture.outVector, &info->fileTexture, ctx );
//
// This simplifies directly evaluating the output of a base node implementation, given data stored
// in the current node's data. It takes care of calling EvalCtx::setData with the base node data,
// and then restoring it after the call.
//
template<typename T> static DL_FI T evalBase( const OutputInfo<T>& info, void* data, EvalCtx* ctx )
{
    T t;
    EvalCtx::DataScope ds( ctx, data );
    return info.output->eval( t, ctx ) ? t : T();
}

// Macros for simplifying use of the above helpers in init/prep functions. Note that it is not
// necessary to worry about handling bases of bases since the base node implementations called by
// these macros will in turn call those of their bases in the same way, if they have them, and so
// forth.
//
#define DL_INIT_BASEINFO(T,BASE) \
    DL_INIT_NODEINFO(T) \
    if ( !initBase( BASE##Init, &info->BASE, inode ) ) \
        return false;

#define DL_PREP_BASEINFO(T,BASE) \
    DL_PREP_NODEINFO(T) \
    if ( !prepBase( BASE##Prep, &info->BASE, inode ) ) \
        return false;

//=================================================================================================
// This section provides support for inheriting from texture and texture3d. It also provides an
// example for how this is done, by embedding the data struct for a base node in the struct for
// a derived one.
//=================================================================================================

struct Texture DL_FINAL
{
    // API Version 26.1.0.0
    DL_MAKE_IINFO( Vec2, uvCoord );
    DL_MAKE_OINFO( Rgba, outColor );
    DL_MAKE_OINFO( Rgba, outColorInv );
    DL_MAKE_OINFO( Real, outAlpha );
    DL_MAKE_OINFO( Real, outAlphaInv );
    DL_MAKE_OINFO( Real, outAverage );
    DL_MAKE_OINFO( Real, outAverageInv );
    DL_MAKE_OINFO( Real, outMin );
    DL_MAKE_OINFO( Real, outMax );
    DL_MAKE_OINFO( Real, outR );
    DL_MAKE_OINFO( Real, outG );
    DL_MAKE_OINFO( Real, outB );
    DL_MAKE_OINFO( Real, outA );
    DL_MAKE_OINFO( Real, outRInv );
    DL_MAKE_OINFO( Real, outGInv );
    DL_MAKE_OINFO( Real, outBInv );
    DL_MAKE_OINFO( Real, outAInv );
};

struct Texture3D DL_FINAL
{
    // API Version 26.1.0.0
    Texture texture;

    DL_MAKE_IINFO( String, projection );
    DL_MAKE_IINFO( String, space );
    DL_MAKE_IINFO( Mat4, xform );

    enum Projection { Surface, Cubic, Planar, Spherical, Cylindrical, Screen, ScreenFit };
    enum Space { Object, Uniform, World };

    Projection projectionType = Surface;
    Space spaceType = Object;
    Vec2 resolution = Vec2::zero;
    Real aspectRatio = 1.0;

    Bool isSurface() const      { return projectionType == Surface; }
    Bool isCubic() const        { return projectionType == Cubic; }
    Bool isPlanar() const       { return projectionType == Planar; }
    Bool isSpherical() const    { return projectionType == Spherical; }
    Bool isCylindrical() const  { return projectionType == Cylindrical; }
    Bool isScreen() const       { return projectionType == Screen; }
    Bool isScreenFit() const    { return projectionType == ScreenFit; }

    Bool isObjectSpace() const  { return spaceType == Object; }
    Bool isUniformSpace() const { return spaceType == Uniform; }
    Bool isWorldSpace() const   { return spaceType == World; }

    void setProjection( String type )
    {
        if ( type.compareNoCase( "cubic" ) == 0 )
            projectionType = Cubic;
        else if ( type.compareNoCase( "planar" ) == 0 )
            projectionType = Planar;
        else if ( type.compareNoCase( "spherical" ) == 0 )
            projectionType = Spherical;
        else if ( type.compareNoCase( "cylindrical" ) == 0 )
            projectionType = Cylindrical;
        else if ( type.compareNoCase( "screen" ) == 0 )
            projectionType = Screen;
        else if ( type.compareNoCase( "screenFit" ) == 0 )
            projectionType = ScreenFit;
        else
            projectionType = Surface;
    }

    void setSpace( String type )
    {
        if ( type.compareNoCase( "object" ) == 0 )
            spaceType = Object;
        else if ( type.compareNoCase( "world" ) == 0 )
            spaceType = World;
        else
            spaceType = Uniform;
    }

    void prep()
    {
        setSpace( space.eval() );
        setProjection( projection.eval() );
    }
};

// These macros are for your convenience in subclassing texture and texure3d. First we will inherit
// texture3d in our node definition, which means we can skip all the attrs defined in texture and
// texture3d:
//
//    "msbnChecker3d": {
//        "bases": [ "texture3d" ],
//        "inputs": {
//            "color1": {
//                "type": "rgba",
//                "value": { "r": 0.1, "g": 0.2, "b": 0.3, "a": 1.0 }
//            },
//            "color2": {
//                "type": "rgba",
//                "value": { "r": 0.3, "g": 0.2, "b": 0.1, "a": 1.0 }
//            },
//            "factor1": {
//                "type": "real",
//                "value": 0.5
//            },
//            "factor2": {
//                "type": "real",
//                "value": 1.0
//            }
//        },
//        "outputs": {
//            "outValue": { "type": "real" }
//        },
//        "layout": [
//            "color1",
//            "color2",
//            "factor1",
//            "factor2",
//            {
//                "header": { "en": "Mapping" },
//                "expand": true,
//                "layout": [
//                    "uvCoord",
//                    "projection",
//                    "space",
//                    "xform"
//                ]
//            }
//        ],
//        "svgIcon": "
//            <svg width='288' height='288' viewBox='0 0 288.00 288.00'>
//              <g><ellipse fill='#B4B4B4' fill-opacity='1' stroke-width='0.2' stroke-linejoin='round' cx='144' cy='144' rx='112' ry='112'/></g>
//            </svg>"
//    }
//
// You can see in the layout that we are able to refer to inherited attributes, as our definition
// will be augmented by bella when it parses our bnd text. Next we come to the implementation of
// this, where we once again define only our attrs in our DL_ITERATE_ATTRS macro:
// 
//    #undef DL_ITERATE_ATTRS
//    #define DL_ITERATE_ATTRS(X) \
//        X(I, Rgba, color1 ) \
//        X(I, Rgba, color2 ) \
//        X(I, Real, factor1 ) \
//        X(I, Real, factor2 ) \
//        X(O, Real, outValue )
//
// However this time, we add an instance of Texture3D to our node data struct, in order to gain
// all of its Input/OutputInfo helpers:
// 
//    struct MsbnChecker3d
//    {
//        Texture3D texture3d;
//        DL_ITERATE_ATTRS( DL_MAKE_ATTRS );
//    };
//
// Likewise, in our Init/Prep functions we must init & prep both our own attrs and the base attrs,
// using the DL_ITERATE_TEXTURE_ATTRS and DL_ITERATE_TEXTURE3D_ATTRS macros provided below:
//
//    DL_C_EXPORT bool DL_STDCALL msbnChecker3dInit( INode* inode, void** data )
//    {
//        DL_INIT_NODEINFO( MsbnChecker3d );
//        DL_ITERATE_ATTRS( DL_INIT_ATTRS );
//        DL_ITERATE_TEXTURE_ATTRS( DL_INIT_ATTRS, texture3d.texture );
//        DL_ITERATE_TEXTURE3D_ATTRS( DL_INIT_ATTRS, texture3d );
//        return true;
//    }
// 
//    DL_C_EXPORT bool DL_STDCALL msbnChecker3dFree( const INode* inode, void** data )
//    {
//        DL_FREE_NODEINFO( MsbnChecker3d );
//    }
//
//    DL_C_EXPORT bool DL_STDCALL msbnChecker3dPrep( INode* inode, void* data )
//    {
//        DL_PREP_NODEINFO( MsbnChecker3d );
//        DL_ITERATE_ATTRS( DL_PREP_ATTRS );
//        DL_ITERATE_TEXTURE_ATTRS( DL_PREP_ATTRS, texture3d.texture );
//        DL_ITERATE_TEXTURE3D_ATTRS( DL_PREP_ATTRS, texture3d );
//        return true;
//    }
//
// Now in our eval, we will use DL_EVAL_IINFO for our own attrs, and otherwise call eval(ctx)
// directly on InputInfo fields in our Texture and Texture3D structures. Finally, once we have
// handled all our own outputs (just outValue in this node), we will compute our outColor and
// pass it to the writeTexture helper provided below:
//
//    DL_C_EXPORT bool DL_STDCALL msbnChecker3dEval( EvalCtx* ctx, void* output )
//    {
//        DL_EVAL_NODEINFO( MsbnChecker3d );
//        DL_EVAL_IINFO( factor1 );
//        DL_EVAL_IINFO( factor2 );
//    
//        factor1 *= factor2;
//    
//        if ( info->outValue.matches( ctx ) )
//            return writeOutput( output, factor1 );
//    
//        DL_EVAL_IINFO( color1 );
//        DL_EVAL_IINFO( color2 );
//    
//        auto uvCoord = info->texture3d.texture.uvCoord.eval( ctx );
//        auto choice = ( ( uvCoord.v - floor( uvCoord.v ) ) < 0.5 ) ^
//                      ( ( uvCoord.u - floor( uvCoord.u ) ) < 0.5 );
//    
//        auto rgba = ( choice & 1 ) ? color2 : color1;
//        rgba.rgb = ( Rgb )( rgba.v3 * factor1 );
//    
//        return writeTexture( ctx, output, info->texture3d.texture, rgba );
//    }
//
// This augments msbnChecker3d's own attributes with all those from the base texture and texture3d
// nodes, and there is nothing special you must do about the texture3d's projection, as this is
// also handled by the system, with projected UVs being computed and placed in the Evalctx for the
// duration of the evaluation (the originals are still available in EvalCtx::stashedUVs() if you
// should need them).
//
#define DL_ITERATE_TEXTURE_ATTRS(X,SRC) \
    X(I, Vec2, SRC.uvCoord) \
    X(O, Rgba, SRC.outColor) \
    X(O, Rgba, SRC.outColorInv) \
    X(O, Real, SRC.outAlpha) \
    X(O, Real, SRC.outAlphaInv) \
    X(O, Real, SRC.outAverage) \
    X(O, Real, SRC.outAverageInv) \
    X(O, Real, SRC.outMin) \
    X(O, Real, SRC.outMax) \
    X(O, Real, SRC.outR) \
    X(O, Real, SRC.outG) \
    X(O, Real, SRC.outB) \
    X(O, Real, SRC.outA) \
    X(O, Real, SRC.outRInv) \
    X(O, Real, SRC.outGInv) \
    X(O, Real, SRC.outBInv) \
    X(O, Real, SRC.outAInv)

#define DL_ITERATE_TEXTURE3D_ATTRS(X,SRC) \
    X(I, String, SRC.projection) \
    X(I, String, SRC.space) \
    X(I, Mat4,   SRC.xform )

// As shown above, use this helper to send your computed color to the desired texture output.
//
static inline Bool writeTexture( EvalCtx* ctx, void* output, const Texture& tex, Rgba rgba )
{
    DL_WRITE_OUTPUT( tex.outColor, rgba );
    DL_WRITE_OUTPUT( tex.outAlpha, rgba.a );
    DL_WRITE_OUTPUT( tex.outAverage, rgba.rgb.avg() );
    DL_WRITE_OUTPUT( tex.outColorInv, rgba.inv() );
    DL_WRITE_OUTPUT( tex.outAlphaInv, 1.0 - rgba.a );
    DL_WRITE_OUTPUT( tex.outAverageInv, rgba.rgb.inv().avg() );
    DL_WRITE_OUTPUT( tex.outR, rgba.r );
    DL_WRITE_OUTPUT( tex.outG, rgba.g );
    DL_WRITE_OUTPUT( tex.outB, rgba.b );
    DL_WRITE_OUTPUT( tex.outA, rgba.a );
    DL_WRITE_OUTPUT( tex.outRInv, 1.0 - rgba.r );
    DL_WRITE_OUTPUT( tex.outGInv, 1.0 - rgba.g );
    DL_WRITE_OUTPUT( tex.outBInv, 1.0 - rgba.b );
    DL_WRITE_OUTPUT( tex.outAInv, 1.0 - rgba.a );
    DL_WRITE_OUTPUT( tex.outMin, rgba.rgb.min() );
    DL_WRITE_OUTPUT( tex.outMax, rgba.rgb.max() );

    return false;
}

} // bella_sdk
} // dl

#endif // BELLA_SDK_NODEAPI_H
