aboutsummaryrefslogtreecommitdiff
path: root/mayaPlug/shaveSDKCALLBACKS.cpp
blob: 070ed808d77d904d697c625e779508166a169dab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
// Shave and a Haircut
// (c) 2019 Epic Games
// US Patent 6720962

#include <maya/MDagPath.h>
#include <maya/MFloatMatrix.h>
#include <maya/MFloatPoint.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFloatVector.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MFnField.h>
#include <maya/MFnNonAmbientLight.h>
#include <maya/MGlobal.h>
#include <maya/MItSelectionList.h>
#include <maya/MMatrix.h>
#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MRenderUtil.h>
#include <maya/MVector.h>
#include <maya/MVectorArray.h>

#include "shaveGlobals.h"
#include "shaveHairShape.h"
#include "shaveRender.h"
#include "shaveRenderCallback.h"
#include "shaveSDKTYPES.h"
#include "shaveTextureStore.h"
#include "shaveUtil.h"


extern "C"
{
#include "shaveSDKCALLBACKS.h"

VERT	zeroVert = { 0.0f, 0.0f, 0.0f };

// Callback functions (shave calls you)

#ifndef _DEBUG
VERT SHAVEapply_GI(VERT v,CURVEINFO *cc)
{
	return (zeroVert);
}
#endif

void SHAVEapply_inst_color(
		WFTYPE *instance_geom, int hairID, int slgID, unsigned long shaveINSTID
)
{}



VERT SHAVEdisplace_root(VERT *root,CURVEINFO *ci,int nodeID)
{
	NODETEXINFO*	nodeTexInfo = getTexInfoLookup((unsigned)nodeID);
	VERT			ret = zeroVert;

	if ((nodeTexInfo != NULL)
	&&	(nodeTexInfo->maxPasses > 0)
	&&	(ci->hairID < nodeTexInfo->count)
	&&	(nodeTexInfo->displacement))
	{
		//
		// See SHAVEapply_texture() for an explanation of why we're doing
		// this modulus.
		//
		int pass = ci->depthpass % nodeTexInfo->maxPasses;

		ret = nodeTexInfo->displacement[pass][ci->hairID];
	}

	return ret;
}


static int	shadowTicksLeft = 0;
static bool	progressBarActive = false;

//
// This function gets called every 100 hairs when rendering so that you can
// make a progress bar. Estimated total is the number of times this
// function should get called during a render.  If you want to cancel the
// render you should return 1 for the rest of the calls to this function
// until the render releases control.
//
int SHAVEprogress(int actual, int estimated_total)
{
	//
	// If actual == -1, or if the render has been cancelled, then kill off
	// the progress bar.
	//
	if (progressBarActive && ((actual == -1) || shaveRenderCancelled))
	{
		MGlobal::executeCommand("shave_closeProgressBar");
		progressBarActive = false;
	}

	if (shadowRender)
	{
		if ((actual > 0) && (--shadowTicksLeft > 0)) return 0;

		shadowTicksLeft = 400;
	}

	if (shaveRenderCancelled) return 1;

	//
	// We don't need a progress bar in batch mode.
	//
	if (MGlobal::mayaState() != MGlobal::kInteractive) return 0;

	int	killit = 0;

	if (shaveEnableProgressBar)
	{
		if (actual > estimated_total)
			actual = estimated_total;
		
		if (actual == 0)
		{
			if (!progressBarActive)
			{
				MString	cmd = MString("shave_progressBarInit ")
						+ (double)estimated_total
						+ " \"" + (shadowRender?"Shadow":"Hair") + "\"";

				MGlobal::executeCommand(cmd);
				progressBarActive = true;
			}
		}
		else
		{
			MGlobal::executeCommand("shave_progressBarQuery()", killit);

			if (!killit)
			{
				MGlobal::executeCommand(
					MString("shave_progressBarStep ") + (double)actual
				);
			}
			else
			{
				shaveRenderCancelled = true;
				MGlobal::executeCommand("shave_closeProgressBar");
				progressBarActive = false;
			}
		}

		if (shaveRender::getFrameGlobals().verbose)
		{
			float pct = ((float)actual/(float)estimated_total)*100;
			fprintf(stderr, "Done %f percent.\n",pct);
		}
	}

	return killit;
}


void SHAVEcoord_convertTOSHAVE(VERT *in)
{}


void SHAVEcoord_convertFROMSHAVE(VERT *in)
{}
#ifdef _DEBUG
VERT
SHAVEapply_GI( VERT vv, CURVEINFO * ci )
{
	VERT            t;

	t.x = 0.0f;
	t.y = 0.0f;
	t.z = 0.0f;
	return ( t );
}
extern float
SHAVEapply_falloff( int lightNUM, VERT pos, float cone )
{
	return ( cone );
}
#endif

float SHAVEapply_texture(
	CURVEINFO*		ci,
	VERT			rest_root_worldpos,
	unsigned long	shaveINSTID,
	int				parm,
	float			inbound_value
)
{
	NODETEXINFO* nodeTexInfo = getTexInfoLookup(shaveINSTID);
	if ((nodeTexInfo != NULL)
	&&	(nodeTexInfo->maxPasses > 0)
	&&	(ci->hairID < nodeTexInfo->count))
	{
		//
		// If there are multiple shaveHairShapes in the scene, then Shave
		// will use the same number of passes for all of them.  For
		// example, if one shaveHairShape specifies 2 passes and another
		// specifies 5, then both shaveHairShapes will be called for 5
		// passes.
		//
		// So it is possible that we are being called with a pass number
		// which is larger than the number of passes specified for this
		// particular shaveHairShape.  In that case, we use a modulus to
		// wrap around to the values used in earlier passes.
		//
		// No, this isn't a horrible kludge, this is really what Shave is
		// expecting.  :-)
		//
		int passToUse = ci->depthpass % nodeTexInfo->maxPasses;
passToUse=0;
		//
		// Copy the UV values for this hair into the CURVEINFO so that
		// later callbacks have access to them.
		//
		ci->u = nodeTexInfo->u[passToUse][ci->hairID];
		ci->v = nodeTexInfo->v[passToUse][ci->hairID];

		if (((int)ci->hairID < nodeTexInfo->maxIndex[parm])
		&&	nodeTexInfo->textured[parm])
		{
			float texVal = (float)(nodeTexInfo->textureLookup[passToUse][parm][ci->hairID]);

			//printf("tex %i hair %i value %f\n",parm,ci->hairID,texVal );fflush(stdout);

			return (texVal); // * inbound_value);
		}
	}

	return inbound_value;
}


void MAYAexternal_forces(VERT *lastpos, VERT *velocity, int y)
{
	const MDagPathArray&	fieldList = shaveUtil::getFields();

	unsigned int fieldCount = fieldList.length();

	if (fieldCount)
	{
		MStatus stat; 
		MDagPath dagPath;
		MFnField field;
		MPoint p(lastpos->x, lastpos->y, lastpos->z);
		MVector v(velocity->x, velocity->y, velocity->z);
		MPointArray pointArray;
		pointArray.append(p);
		MVectorArray forces;
		MVectorArray velocityArray;
		velocityArray.append(v);
		MDoubleArray massArray;
		unsigned int	i;

		for(i = 0; i < fieldCount; i++)
		{
			//	shaveUtil::getFields() only updates the field list when a
			//	new one is added, not when one is deleted, so we have to
			//	check the field to make sure that it still exists.
			if (fieldList[i].isValid())
			{
				field.setObject(fieldList[i]);
				stat = field.getForceAtPoint(
						pointArray, velocityArray, massArray, forces
					);
			}
		}

		velocity->x+=((float)forces[0].x)*((float)0.01);
		velocity->y+=((float)forces[0].y)*((float)0.01);
		velocity->z+=((float)forces[0].z)*((float)0.01);
	}
}


//  These methods must be used when running multiple threads.
//
//  Call MAYAcache_forces(0) from Maya's main thread (or its proxy) to create
//  the cache for *all* of the current node's guides.
//
//  From within individual threads you can then call MAYAapply_cached_forces(..)
//  for each guide vertex you want forces applied to.
//
//  When done, call MAYAcache_forces(1) from Maya's main thread (or its proxy)
//  to free up the cache.
//
MVectorArray forcesCache;

void MAYAcache_forces(int clearCache)
{
    forcesCache.clear();

    if (clearCache == 0)
    {
        const MDagPathArray& fieldList = shaveUtil::getFields();

        unsigned int fieldCount = fieldList.length();

        if (fieldCount > 0)
        {
            shaveHairShape* hairShape = shaveUtil::getLoadedHairShape();

            //  TODO:
            //
            //  We want the new guide positions here, but they're not yet
            //  available. The call to this function is part of the process
            //  of calculating those new positions.
            //
            //  For now we'll work around the problem by calculating the
            //  forces using the old positions. If we call getGuides() at
            //  this point it will still return the old positions because
            //  they haven't been updated yet, but it will also mark the
            //  guide cache as clean, which messes things up later down the
            //  line. So instead we call getDirtyGuides() which will return
            //  the current guide cache without attempting to update it.
            //
            const shaveHairShape::GuidesSnapshot& curGuides = hairShape->getDirtyGuides();
            const shaveHairShape::GuidesSnapshot& prevGuides = hairShape->getPrevGuides();
            MVectorArray points;
            MVectorArray velocities;
            MDoubleArray masses;

            points.setLength(
                static_cast<unsigned int>(curGuides.guides.size() * SHAVE_VERTS_PER_GUIDE)
            );

            velocities.setLength(
                static_cast<unsigned int>(curGuides.guides.size() * SHAVE_VERTS_PER_GUIDE)
            );

            //  If the previous guide snapshot has the same time as the
            //  current one, or a different number of guides, then we won't
            //  be able to use it to calculate velocities and will have to
            //  assume zero velocity.
            //
            float deltaT = curGuides.frame - prevGuides.frame;
            bool haveVelocity = (
                       (fabs(deltaT) > 0.000001f)
                    && (prevGuides.guides.size() == curGuides.guides.size())
                );

            unsigned int vertIdx = 0;

            for (size_t g = 0; g < curGuides.guides.size(); ++g)
            {
                const shaveHairShape::Guide& guide = curGuides.guides[g];

                for (unsigned int v = 0; v < SHAVE_VERTS_PER_GUIDE; ++v)
                {
                    points[vertIdx] = static_cast<MVector>(guide.verts[v]);

                    if (haveVelocity)
                    {
                        velocities[vertIdx] = static_cast<MVector>(
                            (guide.verts[v] - prevGuides.guides[g].verts[v]) / deltaT
                        );
                    }
                    else
                    {
                         velocities[vertIdx] = MVector::zero;
                    }

                    ++vertIdx;
                }
            }

            for (unsigned int f = 0; f < fieldCount; ++f)
            {
                //  shaveUtil::getFields() only updates the field list when a
                //  new one is added, not when one is deleted, so we have to
                //  check the field to make sure that it still exists.
                //
                if (fieldList[f].isValid())
                {
                    MFnField fieldFn(fieldList[f]);

                    fieldFn.getForceAtPoint(
                        points, velocities, masses, forcesCache
                    );
                }
            }
        }
    }
}

//  Forces are added to whatever value is already in velocity.
//
void MAYAapply_cached_forces(int guideNum, int vertNum, VERT* velocity)
{
    if ((guideNum >= 0) && (guideNum < static_cast<int>(forcesCache.length()))
    &&  (vertNum >= 0) && (vertNum < SHAVE_VERTS_PER_GUIDE))
    {
        MVector& delta = forcesCache[guideNum * SHAVE_VERTS_PER_GUIDE + vertNum];
        velocity->x += static_cast<float>(delta.x * 0.01);
        velocity->y += static_cast<float>(delta.y * 0.01);
        velocity->z += static_cast<float>(delta.z * 0.01);
    }
}

VERT SHAVEapply_illumination(int LIGHTID, VERT wpos,VERT vector, VERT color)
{
	return color;
}

#ifndef _DEBUG
float SHAVEapply_falloff(int lightID, VERT p, float intensity)
{
	MStatus	st;

	//
	// When doing native illumination, the light samples taken in
	// SHAVEapply_illuminationWF() will already have taken decay into
	// account, so we don't want to further apply it here.
	//
	if (!shaveRender::getFrameGlobals().useNativeIllumination
	&&	(intensity > 0.0f))
	{
		int	lightIndex = shaveUtil::getLightIndexFromID(lightID);

		if (lightIndex >= 0)
		{
			MDagPath lightDag = shaveUtil::globalLightList[lightIndex].path;

			//
			// Only non-ambient lights have decay.
			//
			MFnNonAmbientLight	lightFn(lightDag, &st);

			if (st)
			{
				short	decay = lightFn.decayRate();

				if (decay != 0)
				{
					//
					// How far is the sample point from the light?
					//
					MTransformationMatrix lightWorldMatrix;
					lightWorldMatrix = lightDag.inclusiveMatrix();

					MVector lightPos = lightWorldMatrix.translation(MSpace::kWorld);
					MVector	samplePos((double)p.x, (double)p.y, (double)p.z);
					float	dist = (float)(samplePos - lightPos).length();

					//
					// Maya's lights don't decay within the first unit of
					// distance.
					//
					if (dist > 1.0)
					{
						switch (decay)
						{
							case 1:	// Linear
								intensity = intensity / dist;
							break;

							case 2:	// Quadratic
								intensity = intensity / (dist * dist);
							break;

							case 3:	// Cubic
								intensity = intensity / (dist * dist * dist);
							break;

							default:
							break;
						}
					}
				}
			}
		}
	}

	return intensity;
}
#endif

void SHAVEapply_illuminationWF(int LIGHTID, WFTYPE* samples)
{
	// ranges are 0.0 - 1.0 (return isn't clipped until after shading)
	// modify or replace 'samples->color' - it contains light info for
	// current test before shave shadows are applied
	// samples->v is the position
	// samples->totalverts is the total number of points

	if (shaveRender::getFrameGlobals().useNativeIllumination
	&&	(samples != NULL)
	&&	(samples->totalverts > 0))
	{
		int	lightIndex = shaveUtil::getLightIndexFromID(LIGHTID);

		if (lightIndex >= 0)
		{
			MStatus	status;
			MTransformationMatrix lightWorldMatrix;
			MDagPath lightDag = shaveUtil::globalLightList[lightIndex].path;

			lightWorldMatrix = lightDag.inclusiveMatrix();
			MVector lightTranslation = lightWorldMatrix.translation(MSpace::kWorld );

			MFloatVectorArray	normals;
			MFloatPointArray	pointArray;
			unsigned int		i;

			for (i = 0; i < (unsigned int)samples->totalverts; i++)
			{
				pointArray.append(
					samples->v[i].x, samples->v[i].y, samples->v[i].z
				);

				MFloatVector sampleAsVec(
								samples->v[i].x,
								samples->v[i].y,
								samples->v[i].z
							 );

				MFloatVector vec(lightTranslation - sampleAsVec);
				vec.normalize();
				normals.append(vec);
			}

			MFloatVectorArray vertColorArray;
			MFloatVectorArray vertTranspArray;
			MFloatMatrix camMatrix;
			MString attrName = lightDag.fullPathName() + ".lightIntensity";
			status = MRenderUtil::sampleShadingNetwork(
						attrName,					//attribute to sample
						(int)pointArray.length(),	//samples
						false,						//shadows
						false,						//reuse shad maps
						camMatrix,					//camMatrix
						&pointArray,				//points 
						NULL,						//u coords
						NULL,						//v coords
						&normals,					//normals
						NULL,						//ref points
						NULL,						//u tan
						NULL,						//v tan
						NULL,						//filter size
						vertColorArray, 			//out color
						vertTranspArray				//out transp
					);

			if (status)
			{
				for (i = 0; i < vertColorArray.length(); i++)
				{
					samples->color[i].x = vertColorArray[i].x;
					samples->color[i].y = vertColorArray[i].y;
					samples->color[i].z = vertColorArray[i].z;
				}
			}
		}
	}
}


// this is a callback for applying atmospherics/depth cueing
VERT SHAVEapply_atmosphere(VERT wpos,VERT inbound_color )
{
	// range = 0.0 - 1.0 (shave will clip out of bound returns)
	// do your tint here based on wpos
	// here's how:
	// fogval=your_compute_func_for_fog(wpos);
	// if (fog_val>1.0) fog_val=1.0;
	// if (fog_val<0.0) fog_val=0.0;
	//	inbound_color.x=inbound_color.x*(1.0-fog_val)+fog_color.x*fog_val;
	//	inbound_color.y=inbound_color.y*(1.0-fog_val)+fog_color.y*fog_val;
	//	inbound_color.z=inbound_color.z*(1.0-fog_val)+fog_color.z*fog_val;
	return inbound_color;
}


//
// This function callback gives you the opportunity to apply external
// vertpaint to any/every channel in shave.  Normally the inbound_value
// will be 1.0 (unless you've painted a map inside shave) the return,
// should contain a value who's range is (float) 0-1.
//
float SHAVEapply_VMAP(long SHAVEINSTID,int VERTID,int chan, float inbound_value)
{
	return applyVertTexValue(SHAVEINSTID, VERTID, chan, inbound_value);
}


//	This is called whenever Shave finishes rendering a tile into the pixel
//	buffer.
void SHAVEdraw_tile_callback(VERT* min, VERT* max)
{
	shaveRenderCallback::tileRendered(
		(unsigned int)min->x,
		(unsigned int)max->x,
		(unsigned int)min->y,
		(unsigned int)max->y
	);
}

}