SOP/MSS_CustomBrushState.C

/*
 * Copyright (c) 2013
 *      Side Effects Software Inc.  All rights reserved.
 *
 * Redistribution and use of Houdini Development Kit samples in source and
 * binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. The name of Side Effects Software may not be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *----------------------------------------------------------------------------
 * This code is for creating the state to go with this op.
*/

#include "MSS_CustomBrushState.h"

#include <DM/DM_Defines.h>
#include <DM/DM_ViewportType.h>
#include <GR/GR_Detail.h>
#include <GR/GR_DisplayOption.h>
#include <GU/GU_PrimCircle.h>
#include <MSS/MSS_SingleOpState.h>
#include <OP/OP_OperatorTable.h>
#include <PRM/PRM_Parm.h>
#include <RE/RE_Render.h>
#include <SOP/SOP_Node.h>
#include <UT/UT_DSOVersion.h>

#define MSS_CLICK_BUTTONS (DM_PRIMARY_BUTTON|DM_SECONDARY_BUTTON)

using namespace HDK_Sample;

// register the state
void
newModelState(BM_ResourceManager *m)
{
    m->registerState(
        new PI_StateTemplate("proto_custombrush", // state name
                             "Custom Brush", // English name
                             "SOP_proto_custombrush", // icon name
                             (void *)MSS_CustomBrushState::ourConstructor,
                             MSS_CustomBrushState::ourTemplateList,
                             PI_VIEWER_SCENE,
                             PI_NETMASK_SOP, // marks this as a SOP state
                             0));
}

// our state has no parameters
PRM_Template *
MSS_CustomBrushState::ourTemplateList = 0;

BM_State *
MSS_CustomBrushState::ourConstructor(BM_View &view, PI_StateTemplate &templ,
                                   BM_SceneManager *scene)
{
    return new MSS_CustomBrushState((JEDI_View &)view, templ, scene);
}

MSS_CustomBrushState::MSS_CustomBrushState(
    JEDI_View &view,
    PI_StateTemplate &templ,
    BM_SceneManager *scene,
    char *cursor) : MSS_SingleOpState(view, templ, scene, cursor)
{
    myIsBrushVisible    = false;
    myResizingCursor    = false;

    // create brush geometry
    GU_PrimCircleParms  cparms;
    cparms.gdp = &myBrushCursor;
    cparms.order = 3; // quadratic
    cparms.imperfect = 0; // rational
    cparms.xform.identity();
    GU_PrimCircle::build(cparms, GEOPRIMBEZCURVE); // Bezier

    myBrushCursorXform.identity();
    myBrushRadius = 0.1;

    // only use this state in 3D viewports
    setViewportMask(DM_VIEWPORT_PERSPECTIVE);
}

MSS_CustomBrushState::~MSS_CustomBrushState()
{
    // Nothing needed.
}

const char *
MSS_CustomBrushState::className() const
{
    return "MSS_CustomBrushState";
}

int
MSS_CustomBrushState::enter(BM_SimpleState::BM_EntryType how)
{
    int result = MSS_SingleOpState::enter(how);
    // ask for handleMouseEvent to be called when the mouse moves or a
    // mouse button is clicked
    wantsLocates(1);
    addClickInterest(MSS_CLICK_BUTTONS);
    updatePrompt();

    // turn off the highlight so we can see the color we are painting
    OP_Node *op = getNode();
    if(op)
        op->setHighlight(0);
    return result;
}

void
MSS_CustomBrushState::exit()
{
    // cleanup
    wantsLocates(0);
    removeClickInterest(MSS_CLICK_BUTTONS);
    myIsBrushVisible = false;
    redrawScene();
    MSS_SingleOpState::exit();
}

void
MSS_CustomBrushState::resume(BM_SimpleState *state)
{
    MSS_SingleOpState::resume(state);
    wantsLocates(1);
    addClickInterest(MSS_CLICK_BUTTONS);
    updatePrompt();

    // turn off the highlight so we can see the color we are painting
    OP_Node *op = getNode();
    if(op)
        op->setHighlight(0);
}

void
MSS_CustomBrushState::interrupt(BM_SimpleState *state)
{
    wantsLocates(0);
    removeClickInterest(MSS_CLICK_BUTTONS);
    myIsBrushVisible = false;
    redrawScene();
    MSS_SingleOpState::interrupt(state);
}

int
MSS_CustomBrushState::handleMouseEvent(UI_Event *event)
{
    SOP_Node *sop = (SOP_Node *)getNode();
    if (!sop)
        return 1; // consumed but useless

    float t = getTime();
    int x = event->state.values[X];
    int y = event->state.values[Y];

    if (event->reason == UI_VALUE_START &&
            (event->state.altFlags & UI_ALT_KEY ||
             event->state.altFlags & UI_SHIFT_KEY))
    {
        // prepare for resizing the brush
        myResizeCursorX = x;
        myResizeCursorY = y;
        myResizingCursor = true;
    }
    else if (myResizingCursor)
    {
        // scale the brush's radius
        fpreal dist = x - myLastCursorX +
                      y - myLastCursorY;

        myBrushRadius *= powf(1.01f, dist);

        if (event->reason == UI_VALUE_CHANGED)
            myResizingCursor = false;

        updateBrush(myResizeCursorX, myResizeCursorY);
    }
    else if (event->reason == UI_VALUE_LOCATED)
    {
        // re-position the brush
        updateBrush(x, y);
    }
    else
    {
        // apply a stroke
        UT_Vector3 rayorig, dir;
        mapToWorld(x, y, dir, rayorig);

        bool begin = (event->reason == UI_VALUE_START ||
                      event->reason == UI_VALUE_PICKED);
        if(begin)
            beginDistributedUndoBlock("Stroke", ANYLEVEL);

        PRM_ParmList *parmlist = sop->getParmList();
        PRM_Parm *parm;

        parm = parmlist->getParmPtr("origin");
        if(parm)
        {
            parm->setValue(t, rayorig.x(), 0, 0);
            parm->setValue(t, rayorig.y(), 0, 1);
            parm->setValue(t, rayorig.z(), 0, 2);
        }

        parm = parmlist->getParmPtr("direction");
        if(parm)
        {
            parm->setValue(t, dir.x(), 0, 0);
            parm->setValue(t, dir.y(), 0, 1);
            parm->setValue(t, dir.z(), 0, 2);
        }

        parm = parmlist->getParmPtr("radius");
        if(parm)
            parm->setValue(t, myBrushRadius);

        parm = parmlist->getParmPtr("operation");
        if(parm)
        {
            const char *str = (event->state.values[W] == DM_SECONDARY_BUTTON) ?  "erase" : "paint";
            parm->setValue(t, UT_String(str), CH_STRING_LITERAL);
        }

        OP_Context context(t);
        parm = parmlist->getParmPtr("event");

        bool set_op = false;
        if(begin && parm)
        {
            // indicate the begin of a stroke
            set_op = true;
            parm->setValue(t, UT_String("begin"), CH_STRING_LITERAL);
        }

        // indicate a stroke is active
        bool active = (event->reason == UI_VALUE_ACTIVE ||
                       event->reason == UI_VALUE_PICKED);
        if (active && parm)
        {
            if(set_op)
            {
                // trigger a cook of the CustomBrush SOP so it can cook with
                // the current stroke values
                sop->getCookedGeo(context);
            }

            set_op = true;
            parm->setValue(t, UT_String("active"), CH_STRING_LITERAL);
        }
        // If the brush event is an end, we need to close the undo block.
        if (event->reason == UI_VALUE_CHANGED ||
            event->reason == UI_VALUE_PICKED)
        {
            if(parm)
            {
                if(set_op)
                {
                    // trigger a cook of the CustomBrush SOP so it can cook
                    // with the current stroke values
                    sop->getCookedGeo(context);
                }

                // now change the stroke parameter to indicate a no-op.
                parm->setValue(t, UT_String("end"), CH_STRING_LITERAL);

                // trigger a cook of the CustomBrush SOP so it can cook with
                // the end stroke values
                sop->getCookedGeo(context);

                // now change the stroke parameter to indicate a no-op.
                parm->setValue(t, UT_String("nop"), CH_STRING_LITERAL);
            }

            endDistributedUndoBlock();
        }

        updateBrush(x, y);
    }

    myLastCursorX = x;
    myLastCursorY = y;

    return 1;
}

void
MSS_CustomBrushState::doRender(RE_Render *r, short, short, int ghost)
{
    if (!isPreempted() && myIsBrushVisible)
    {
        r->pushMatrix();
        r->multiplyMatrix(myBrushCursorXform);

        GR_DisplayOption dopt;
        if(ghost)
        {
            // color for obstructed parts of the brush
            dopt.wireColor() = UT_Color(UT_RGB, 0.625, 0.4, 0.375);
        }
        else
        {
            // color for unobstructed parts of the brush
            dopt.wireColor() = UT_Color(UT_RGB, 1, 0.1, 0);
        }

        GR_Detail rgdp;
        rgdp.wireDraw(&myBrushCursor, *r, getViewportLOD(), 1 /*drawselect*/,
                      0 /*hidden_geometry*/, &dopt, false);

        r->popMatrix();
    }
}

void
MSS_CustomBrushState::updatePrompt()
{
    showPrompt("LMB to apply stroke.  MMB to erase.  Shift-LMB to adjust radius.");
}

void
MSS_CustomBrushState::updateBrush(int x, int y)
{
    // get cameraspace to worldspace transform
    getViewportItransform(myBrushCursorXform);

    // determine the direction the camera a facing
    UT_Vector3 forward = rowVecMult(UT_Vector4(0, 0, -1, 0), myBrushCursorXform);

    // position the brush under the pointer and one unit away from the camera
    UT_Vector3 rayorig, dir;
    mapToWorld(x, y, dir, rayorig);
    UT_Vector3 delta(1.0 / dot(dir, forward) * dir);
    myBrushCursorXform.translate(delta.x(), delta.y(), delta.z());

    // scale the brush
    myBrushCursorXform.prescale(myBrushRadius, myBrushRadius, 1);

    // ensure the brush is visible
    myIsBrushVisible = true;
    redrawScene();
}

Generated on Mon Jan 28 00:45:45 2013 for HDK by  doxygen 1.5.9