HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
MSS_CustomBrushState.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2026
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  * This code is for creating the state to go with this op.
27 */
28 
29 #include "MSS_CustomBrushState.h"
30 
31 #include <DM/DM_Defines.h>
32 #include <DM/DM_ViewportType.h>
33 #include <GR/GR_DisplayOption.h>
34 #include <GU/GU_PrimCircle.h>
35 #include <MSS/MSS_SingleOpState.h>
36 #include <OP/OP_OperatorTable.h>
37 #include <PRM/PRM_Parm.h>
38 #include <RE/RE_Render.h>
39 #include <SOP/SOP_Node.h>
40 #include <UT/UT_DSOVersion.h>
41 
42 #include <GR/GR_Uniforms.h>
43 
44 #define MSS_CLICK_BUTTONS (DM_PRIMARY_BUTTON|DM_SECONDARY_BUTTON)
45 
46 using namespace HDK_Sample;
47 
48 // register the state
49 void
51 {
52  m->registerState(
53  new PI_StateTemplate("proto_custombrush", // state name
54  "Custom Brush", // English name
55  "SOP_proto_custombrush", // icon name
59  PI_NETMASK_SOP, // marks this as a SOP state
60  0));
61 }
62 
63 // our state has no parameters
66 
67 BM_State *
69  BM_SceneManager *scene)
70 {
71  return new MSS_CustomBrushState((JEDI_View &)view, templ, scene);
72 }
73 
75  JEDI_View &view,
76  PI_StateTemplate &templ,
77  BM_SceneManager *scene,
78  const char *cursor)
79  : MSS_SingleOpState(view, templ, scene, cursor),
80  myBrushHandle((DM_SceneManager &)workbench(), "MSS_CustomBrushState")
81 {
82  myIsBrushVisible = false;
83  myResizingCursor = false;
84 
85  // create brush geometry
86  GU_PrimCircleParms cparms;
87  cparms.gdp = &myBrushCursor;
88  cparms.order = 3; // quadratic
89  cparms.imperfect = 0; // rational
90  cparms.xform.identity();
91 #if defined(HOUDINI_11)
92  GU_PrimCircle::build(cparms, GEOPRIMBEZCURVE); // Bezier
93 #else
94  GU_PrimCircle::build(cparms, GEO_PRIMBEZCURVE); // Bezier
95 #endif
96 
97  myBrushCursorXform.identity();
98  myBrushRadius = 0.1;
99 
100  // only use this state in 3D viewports
102 }
103 
105 {
106  // Nothing needed.
107 }
108 
109 const char *
111 {
112  return "MSS_CustomBrushState";
113 }
114 
115 int
117 {
118  int result = MSS_SingleOpState::enter(how);
119  // ask for handleMouseEvent to be called when the mouse moves or a
120  // mouse button is clicked
121  wantsLocates(1);
123  updatePrompt();
124 
125  // turn off the highlight so we can see the color we are painting
126  OP_Node *op = getNode();
127  if(op)
128  op->setHighlight(0);
129  return result;
130 }
131 
132 void
134 {
135  // cleanup
136  wantsLocates(0);
138  myIsBrushVisible = false;
139  redrawScene();
141 }
142 
143 void
145 {
147  wantsLocates(1);
149  updatePrompt();
150 
151  // turn off the highlight so we can see the color we are painting
152  OP_Node *op = getNode();
153  if(op)
154  op->setHighlight(0);
155 }
156 
157 void
159 {
160  wantsLocates(0);
162  myIsBrushVisible = false;
163  redrawScene();
165 }
166 
167 int
169 {
170  SOP_Node *sop = (SOP_Node *)getNode();
171  if (!sop)
172  return 1; // consumed but useless
173 
174  fpreal t = getTime();
175  int x = event->state.values[DEVICE_DIM_X];
176  int y = event->state.values[DEVICE_DIM_Y];
177 
178  if (event->reason == UI_VALUE_START &&
179  (event->state.altFlags & UI_ALT_KEY ||
180  event->state.altFlags & UI_SHIFT_KEY))
181  {
182  // prepare for resizing the brush
183  myResizeCursorX = x;
184  myResizeCursorY = y;
185  myResizingCursor = true;
186  }
187  else if (myResizingCursor)
188  {
189  // scale the brush's radius
190  fpreal dist = x - myLastCursorX +
191  y - myLastCursorY;
192 
193  myBrushRadius *= powf(1.01, dist);
194 
195  if (event->reason == UI_VALUE_CHANGED)
196  myResizingCursor = false;
197 
198  updateBrush(myResizeCursorX, myResizeCursorY);
199  }
200  else if (event->reason == UI_VALUE_LOCATED)
201  {
202  // re-position the brush
203  updateBrush(x, y);
204  }
205  else
206  {
207  // Apply a stroke
208  //
209  // The set*() method calls below will automatically record the undo
210  // actions. Since we do automatic matching of the sop node type via the
211  // same name as the state, we're assuming here for simplicity that the
212  // parameters exist.
213 
214  UT_Vector3 rayorig, dir;
215  mapToWorld(x, y, dir, rayorig);
216 
217  xformToObjectCoord(rayorig);
218  xformToObjectVector(dir);
219 
220  bool begin = (event->reason == UI_VALUE_START ||
221  event->reason == UI_VALUE_PICKED);
222  if(begin)
224 
225  sop->setFloat("origin", 0, t, rayorig.x());
226  sop->setFloat("origin", 1, t, rayorig.y());
227  sop->setFloat("origin", 2, t, rayorig.z());
228 
229  sop->setFloat("direction", 0, t, dir.x());
230  sop->setFloat("direction", 1, t, dir.y());
231  sop->setFloat("direction", 2, t, dir.z());
232 
233  sop->setFloat("radius", 0, t, myBrushRadius);
234 
235  {
236  UT_String str = (event->state.values[DEVICE_DIM_W] == DM_SECONDARY_BUTTON)
237  ? "erase" : "paint";
238  sop->setString(str, CH_STRING_LITERAL, "operation", 0, t);
239  }
240 
241  OP_Context context(t);
242 
243  bool set_op = false;
244  if (begin)
245  {
246  // indicate the begin of a stroke
247  set_op = true;
248  sop->setString(UT_String("begin"), CH_STRING_LITERAL,"event",0,t);
249  }
250 
251  // indicate a stroke is active
252  bool active = (event->reason == UI_VALUE_ACTIVE ||
253  event->reason == UI_VALUE_PICKED);
254  if (active)
255  {
256  if(set_op)
257  {
258  // trigger a cook of the CustomBrush SOP so it can cook with
259  // the current stroke values
260  sop->getCookedGeo(context);
261  }
262 
263  set_op = true;
264  sop->setString(UT_String("active"), CH_STRING_LITERAL, "event",0,t);
265  }
266  // If the brush event is an end, we need to close the undo block.
267  if (event->reason == UI_VALUE_CHANGED ||
268  event->reason == UI_VALUE_PICKED)
269  {
270  if(set_op)
271  {
272  // trigger a cook of the CustomBrush SOP so it can cook
273  // with the current stroke values
274  sop->getCookedGeo(context);
275  }
276 
277  // now change the stroke parameter to indicate a no-op.
278  sop->setString(UT_String("end"), CH_STRING_LITERAL, "event", 0, t);
279 
280  // trigger a cook of the CustomBrush SOP so it can cook with
281  // the end stroke values
282  sop->getCookedGeo(context);
283 
284  // now change the stroke parameter to indicate a no-op.
285  sop->setString(UT_String("nop"), CH_STRING_LITERAL, "event", 0, t);
286 
288  }
289 
290  updateBrush(x, y);
291  }
292 
293  myLastCursorX = x;
294  myLastCursorY = y;
295 
296  return 1;
297 }
298 
299 void
301 {
302  if (!isPreempted() && myIsBrushVisible)
303  {
304  UT_Color clr;
305 
306  if (r.uniforms())
307  {
309  r.uniforms()->setObjModelMatrix(UT_Matrix4D(myBrushCursorXform));
310  }
311  else
312  {
313  r->pushMatrix();
314  r->multiplyMatrix(myBrushCursorXform);
315  }
316 
317  if(ghost)
318  {
319  // color for obstructed parts of the brush
320  clr = UT_Color(UT_RGB, 0.625,0.4,0.375);
321  }
322  else
323  {
324  // color for unobstructed parts of the brush
325  clr = UT_Color(UT_RGB, 1, 0.1, 0);
326  }
327 
328  myBrushHandle.renderWire(r, 0, 0, 0, clr, &myBrushCursor);
329 
330  if (r.uniforms())
331  {
333  }
334  else
335  {
336  r->popMatrix();
337  }
338  }
339 }
340 
341 void
343 {
344  showPrompt("LMB to apply stroke. MMB to erase. Shift-LMB to adjust radius.");
345 }
346 
347 void
349 {
350  // get cameraspace to worldspace transform
351  getViewportItransform(myBrushCursorXform);
352 
353  // determine the direction the camera a facing
354  UT_Vector3 forward = rowVecMult3(UT_Vector3(0, 0, -1), myBrushCursorXform);
355 
356  // position the brush under the pointer and one unit away from the camera
357  UT_Vector3 rayorig, dir;
358  mapToWorld(x, y, dir, rayorig);
359  UT_Vector3 delta(1.0 / dot(dir, forward) * dir);
360  myBrushCursorXform.translate(delta.x(), delta.y(), delta.z());
361 
362  // scale the brush
363  myBrushCursorXform.prescale(myBrushRadius, myBrushRadius, 1);
364 
365  // ensure the brush is visible
366  myIsBrushVisible = true;
367  redrawScene();
368 }
exint pushObjectUniforms()
const GU_Detail * getCookedGeo(OP_Context &, int forced=0)
void popMatrix(bool all_matrices=true, RE_MatrixMode mmode=RE_MATRIX_VIEWING)
GA_API const UT_StringHolder dist
UT_Matrix4T< fpreal64 > UT_Matrix4D
void interrupt(BM_SimpleState *state=0) override
void interrupt(BM_SimpleState *=0) override
fpreal getTime() const
Obtains the current global time.
void mapToWorld(float x, float y, UT_Vector3 &dir, UT_Vector3 &rayorig)
Map viewport coordinates to worldspace location and direction.
int isPreempted() const
Definition: BM_State.h:271
void beginDistributedUndoBlock(const char *operation, UT_UndoBlockType blocktype, bool ignore_log=false)
void xformToObjectCoord(UT_Vector3 &p)
static PRM_Template * ourTemplateList
parameters for this state
UT_Vector3T< float > UT_Vector3
#define UI_SHIFT_KEY
const unsigned PI_NETMASK_SOP
constexpr SYS_FORCE_INLINE T & z() noexcept
Definition: UT_Vector3.h:669
#define DM_SECONDARY_BUTTON
Definition: DM_Defines.h:216
GLint y
Definition: glcorearb.h:103
**But if you need a result
Definition: thread.h:622
int enter(BM_SimpleState::BM_EntryType how) override
void setString(const UT_StringRef &val, CH_StringMeaning meaning, int parmi, int vectori, fpreal t)
void showPrompt(const char *msg)
Set the status bar text.
Temporary container for either a RV_Render and an RE_Render.
void popObjectUniforms(exint l=-1)
void exit() override
virtual int registerState(PI_StateTemplate *type)
void newModelState(BM_ResourceManager *m)
int enter(BM_SimpleState::BM_EntryType how) override
called when the user enters the state
struct _cl_event * event
Definition: glcorearb.h:2961
void pushMatrix(bool all_matrices=true, RE_MatrixMode mmode=RE_MATRIX_VIEWING)
bool setHighlight(bool on_off)
static BM_State * ourConstructor(BM_View &view, PI_StateTemplate &templ, BM_SceneManager *scene)
used by DM to create our state
MSS_CustomBrushState(JEDI_View &view, PI_StateTemplate &templ, BM_SceneManager *scene, const char *cursor=BM_DEFAULT_CURSOR)
void wantsLocates(int yesNo)
Definition: BM_State.h:266
void removeClickInterest(int buttons)
fpreal64 dot(const CE_VectorT< T > &a, const CE_VectorT< T > &b)
Definition: CE_Vector.h:138
void setViewportMask(unsigned mask)
void prescale(T sx, T sy, T sz, T sw=1)
Definition: UT_Matrix4.h:719
#define MSS_CLICK_BUTTONS
void exit() override
called when the user leaves the state
virtual void updatePrompt()
sets the prompt's text
void identity()
Set the matrix to identity.
Definition: UT_Matrix4.h:1126
UI_DeviceEvent state
Definition: UI_Event.h:61
void updateBrush(int x, int y)
repositions the brush's guide geometry
void resume(BM_SimpleState *=0) override
GLint GLenum GLint x
Definition: glcorearb.h:409
GR_Uniforms * uniforms()
that also have some descendant prim *whose name begins with which in turn has a child named baz where *the predicate active
GLdouble t
Definition: glad.h:2397
void redrawScene()
GEO_API const TypeMask GEOPRIMBEZCURVE
#define UI_ALT_KEY
void setObjModelMatrix(const UT_Matrix4D &v, const UT_Matrix4D *inv=nullptr)
Definition: GR_Uniforms.h:115
void translate(T dx, T dy, T dz=0)
Definition: UT_Matrix4.h:769
PcpNodeRef_ChildrenIterator begin(const PcpNodeRef::child_const_range &r)
Support for range-based for loops for PcpNodeRef children ranges.
Definition: node.h:587
UT_Vector3T< T > rowVecMult3(const UT_Vector3T< T > &v, const UT_Matrix4T< S > &m)
Definition: UT_Matrix4.h:1924
void setFloat(int parmi, int vectori, fpreal t, fpreal value, PRM_AddKeyType add_key=PRM_AK_MARK_PENDING)
fpreal64 fpreal
Definition: SYS_Types.h:283
void renderWire(RE_RenderContext r, int pickflag, uint id1, uint id2, const UT_Color &color, GU_Detail *gdp=NULL, const UT_DMatrix4 *xform=NULL)
int handleMouseEvent(UI_Event *event) override
Respond to mouse or keyboard events.
void addClickInterest(int buttons)
void resume(BM_SimpleState *state=0) override
#define DM_VIEWPORT_PERSPECTIVE
UI_Reason reason
Definition: UI_Event.h:63
const char * className() const override
The name and type of this class:
GLboolean r
Definition: glcorearb.h:1222
void getViewportItransform(UT_Matrix4 &xform)
Get cameraspace to worldspace transform.
void multiplyMatrix(const UT_Matrix4 &m)
constexpr SYS_FORCE_INLINE T & y() noexcept
Definition: UT_Vector3.h:667
static GEO_Primitive * build(const GU_PrimCircleParms &parms, GA_PrimitiveTypeId type=GEO_PRIMCIRCLE)
OP_Node * getNode() const
void xformToObjectVector(UT_Vector3 &v)
state
Definition: core.h:2289
void endDistributedUndoBlock(bool ignore_log=false)
void doRender(RE_RenderContext r, int x, int y, int ghost) override
Render the brush "cursor" geometry:
constexpr SYS_FORCE_INLINE T & x() noexcept
Definition: UT_Vector3.h:665