home *** CD-ROM | disk | FTP | other *** search
- /*
- * WRay_Pick.c
- *
- * QuickDraw 3D 1.6 Sample
- * Robert Dierkes
- *
- * 07/28/98 RDD Created.
- */
-
- /*------------------*/
- /* Include Files */
- /*------------------*/
- #include "QD3D.h"
- #include "QD3DMath.h"
- #include "QD3DGroup.h"
- #include "QD3DPick.h"
- #include "QD3DTransform.h"
- #include "QD3DView.h"
-
- #include "WRay_Error.h"
- #include "WRay_Document.h"
- #include "WRay_Memory.h"
- #include "WRay_Scene.h"
- #include "WRay_System.h"
- #include "WRay_Pick.h"
-
- #include <math.h>
-
-
- /*------------------*/
- /* Constants */
- /*------------------*/
-
- /*------------------*/
- /* Macros */
- /*------------------*/
- #define kFirstHit 0
- #define kHitDistance (kCylRadius + 0.01f)
- #define HAS_Object(validMask) ((validMask & kQ3PickDetailMaskObject) !=0)
- #define HAS_Distance(validMask) ((validMask & kQ3PickDetailMaskDistance)!=0)
-
-
- /*----------------------*/
- /* Global Declarations */
- /*----------------------*/
- static TQ3PickObject gPick = NULL;
- static TQ3Ray3D gWorldRay;
- static TQ3Vector3D gWorldDelta;
-
- static TQ3GroupPosition gBallGroupPosn = NULL;
- static TQ3GeometryObject gBallGeo = NULL;
- static TQ3GeometryObject gCurGeoHit = NULL;
-
-
- /*----------------------*/
- /* Local Prototypes */
- /*----------------------*/
- static
- TQ3PickObject Pick_New(
- TQ3Ray3D *pWorldRay);
- static
- TQ3Status Pick_Document(
- TDocumentPtr pDoc,
- TQ3PickObject pickObj,
- unsigned long *pNumHits);
- static
- TQ3Status Pick_InitializeWorldRay(
- TQ3PickObject pickObj,
- TQ3Ray3D *pWorldRay,
- TQ3Vector3D *pWorldDelta);
- static
- TQ3Status Pick_ComputeReflectedWorldRay(
- TQ3PickObject pickObj,
- TQ3Ray3D *pWorldRay,
- TQ3Vector3D *pWorldDelta);
- static
- TQ3GeometryObject Pick_CreateMovingObject(
- TDocumentPtr pDoc,
- TQ3Ray3D *pRay,
- TQ3GroupPosition *pGroupPosn);
- static
- TQ3Status Pick_GetNearestObject(
- TQ3PickObject pickObj,
- float minDistance,
- TQ3GeometryObject *pGeoHit,
- TQ3GeometryObject *pPrevGeoHit);
- static
- TQ3Status Pick_HighlightObject(
- TDocumentPtr pDoc,
- TQ3GeometryObject geoObj,
- TQ3Boolean doHighlight);
-
-
- /*
- * Pick_Initialize
- */
- TQ3Boolean Pick_Initialize(
- void)
- {
- TQ3Status status = kQ3Failure;
-
- gPick = NULL;
- gBallGroupPosn = NULL;
- gBallGeo = NULL;
- gCurGeoHit = NULL;
-
- Q3Point3D_Set(&gWorldRay.origin, 0.0, 0.0, 0.0);
- Q3Vector3D_Set(&gWorldRay.direction, 0.0, 0.0, 1.0);
-
- gPick = Pick_New(&gWorldRay);
-
- return (gPick != NULL) ? kQ3True : kQ3False;
- }
-
-
- /*
- * Pick_Exit
- */
- TQ3Boolean Pick_Exit(
- void)
- {
- Object_Dispose_NULL(&gPick);
- Object_Dispose_NULL(&gBallGeo);
- Object_Dispose_NULL(&gCurGeoHit);
-
- return kQ3True;
- }
-
-
- #pragma mark -
-
- /*
- * Pick_New
- */
- TQ3PickObject Pick_New(
- TQ3Ray3D *pWorldRay)
- {
- TQ3WorldRayPickData rayPickData;
- TQ3PickObject pickObject = NULL;
-
- /*
- * Tolerance values are only used for Point, Line, Ellipse,
- * NURB Curve, PolyLine, Mesh geometries and are measured
- * in world space.
- */
- #define kVertexTolerance 0.01
- #define kEdgeTolerance 0.005
-
- rayPickData.data.sort = kQ3PickSortNearToFar;
-
- rayPickData.data.mask = kQ3PickDetailMaskObject |
- kQ3PickDetailMaskXYZ |
- kQ3PickDetailMaskDistance |
- kQ3PickDetailMaskNormal;
- rayPickData.data.numHitsToReturn = 1;
-
- /* Make sure ray is normalized */
- Q3Vector3D_Normalize(&pWorldRay->direction, &pWorldRay->direction);
- rayPickData.ray = *pWorldRay;
-
- rayPickData.vertexTolerance = kVertexTolerance;
- rayPickData.edgeTolerance = kEdgeTolerance;
-
- /* Create the new world ray pick object */
- pickObject = Q3WorldRayPick_New(&rayPickData);
- DEBUG_ASSERT(pickObject != NULL, Q3WorldRayPick_New);
-
- return pickObject;
- }
-
-
- /*
- * Pick_Document
- */
- static
- TQ3Status Pick_Document(
- TDocumentPtr pDoc,
- TQ3PickObject pickObj,
- unsigned long *pNumHits)
- {
- TQ3Status status = kQ3Success;
- TQ3ViewStatus viewStatus = kQ3ViewStatusError;
-
- if (Q3View_StartPicking(pDoc->fView, pickObj) == kQ3Failure) {
- ERROR_DEBUG_STR("Pick_Document: Q3View_StartPicking failed.");
- return kQ3Failure;
- }
-
- do {
- Document_Submit_Objects(pDoc, pDoc->fView);
-
- viewStatus = Q3View_EndPicking(pDoc->fView);
- }
- while (viewStatus == kQ3ViewStatusRetraverse);
-
- DEBUG_ASSERT(viewStatus == kQ3ViewStatusDone, Pick_Document);
-
- status = Q3Pick_GetNumHits(pickObj, pNumHits);
-
- return status;
- }
-
-
- #pragma mark -
-
- /*
- * Pick_IsAnimating
- */
- TQ3Boolean Pick_IsAnimating(
- void)
- {
- return (gBallGeo != NULL) ? kQ3True : kQ3False;
- }
-
-
- /*
- * Pick_BeginAnimation
- */
- TQ3Status Pick_BeginAnimation(
- TDocumentPtr pDoc)
- {
- TQ3Status status = kQ3Failure;
-
- if (Pick_IsAnimating() == kQ3True) {
- /* Animation already taking place */
- return status;
- }
-
- /* Setup initial world ray origin and direction */
- status = Pick_InitializeWorldRay(gPick, &gWorldRay, &gWorldDelta);
- if (status == kQ3Failure) {
- return status;
- }
-
- /* Create ball */
- gBallGeo = Pick_CreateMovingObject(pDoc, &gWorldRay, &gBallGroupPosn);
- DEBUG_ASSERT(gBallGeo != NULL, Pick_CreateMovingObject);
- if (gBallGeo == NULL) {
- return kQ3Failure;
- }
-
- return status;
- }
-
-
- /*
- * Pick_Animate
- *
- * Move ball through the scene avoiding objects using
- * ray picking to check for geometries in its path.
- */
- TQ3Status Pick_Animate(
- TDocumentPtr pDoc)
- {
- TQ3Status status = kQ3Failure;
- unsigned long numHits;
- TQ3GeometryObject prevGeoHit = NULL;
-
- if (Pick_IsAnimating() == kQ3False) {
- /* If animation hasn't started then do nothing */
- return kQ3Failure;
- }
-
- /* Submit scene for picking */
- status = Pick_Document(pDoc, gPick, &numHits);
- DEBUG_ASSERT(status == kQ3Success, Pick_Document);
-
- if (numHits > 0) {
- status = Pick_GetNearestObject(gPick, kHitDistance, &gCurGeoHit, &prevGeoHit);
-
- if (gCurGeoHit != prevGeoHit) {
- if (prevGeoHit != NULL) {
- /* Unhighlight the ball & previous geometry */
- Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
- Pick_HighlightObject(pDoc, prevGeoHit, kQ3False);
- Object_Dispose_NULL(&prevGeoHit);
- }
-
- if (gCurGeoHit != NULL) {
- /* Highlight the ball & the new closest geometry*/
- Pick_HighlightObject(pDoc, gBallGeo, kQ3True);
- Pick_HighlightObject(pDoc, gCurGeoHit, kQ3True);
-
- /* status = */ Pick_ComputeReflectedWorldRay(gPick, &gWorldRay, &gWorldDelta);
-
- System_Sound();
- }
- }
- }
- else {
- /* Nothing hit */
- if (gCurGeoHit != NULL) {
- /* Unhighlight the ball & current geometry */
- Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
- Pick_HighlightObject(pDoc, gCurGeoHit, kQ3False);
- Object_Dispose_NULL(&gCurGeoHit);
- }
- }
-
- Q3Pick_EmptyHitList(gPick);
-
- /* Advance ball by delta amount */
- Q3Point3D_Vector3D_Add(&gWorldRay.origin, &gWorldDelta, &gWorldRay.origin);
- Q3Ellipsoid_SetOrigin(gBallGeo, &gWorldRay.origin);
-
- /* Set ray again */
- status = Q3WorldRayPick_SetRay(gPick, &gWorldRay);
- DEBUG_ASSERT(status == kQ3Success, Q3WorldRayPick_SetRay);
-
- return status;
- }
-
-
- /*
- * Pick_EndAnimation
- */
- TQ3Status Pick_EndAnimation(
- TDocumentPtr pDoc)
- {
- if (Pick_IsAnimating() == kQ3False) {
- /* Animation not taking place */
- return kQ3Failure;
- }
-
- /* Unhighlight anything that's still highlighted */
- if (gCurGeoHit != NULL) {
- /* Unhighlight the ball & current geometry */
- Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
- Pick_HighlightObject(pDoc, gCurGeoHit, kQ3False);
- Object_Dispose_NULL(&gCurGeoHit);
- }
- Object_Dispose_NULL(&gBallGeo);
-
- /* Remove temporary ball group from main group */
- if (gBallGroupPosn != NULL) {
- DEBUG_ASSERT(gCurGeoHit == NULL, Pick_EndAnimation);
- gCurGeoHit = Q3Group_RemovePosition (pDoc->fModel, gBallGroupPosn);
- Object_Dispose_NULL(&gCurGeoHit);
- gBallGroupPosn = NULL;
- }
-
- Document_Draw(pDoc);
-
- return kQ3Success;
- }
-
-
- #pragma mark -
-
- /*
- * Pick_InitializeWorldRay
- *
- * Create an world delta vector with a random direction with a
- * magnitude that controls the rate. Normalize this vector to
- * make the actual world ray. The world ray is initially positioned
- * at the origin.
- */
- static
- TQ3Status Pick_InitializeWorldRay(
- TQ3PickObject pickObj,
- TQ3Ray3D *pWorldRay,
- TQ3Vector3D *pWorldDelta)
- {
- TQ3Status status = kQ3Failure;
-
- Q3Point3D_Set(&pWorldRay->origin, 0.0, 0.0, 0.0);
-
- /* Generate a random vector in XY plane */
- Q3Vector3D_Set(pWorldDelta, System_RandomFloat(), System_RandomFloat(), 0.0);
- Q3Vector3D_Normalize(pWorldDelta, &pWorldRay->direction);
-
- /* Set world ray's origin and direction */
- status = Q3WorldRayPick_SetRay(pickObj, pWorldRay);
- DEBUG_ASSERT(status == kQ3Success, Q3WorldRayPick_SetRay);
-
- return status;
- }
-
-
- /*
- * Pick_ComputeReflectedWorldRay
- *
- * Given: pDelta, pRay->direction (a normalized pDelta), normal
- * Find: new pDelta with a negative angle relative to the angle between delta and normal
- *
- * ++
- * /!|\
- * / !| \
- * / N!| \
- * D / !| \ D'
- * / V| \
- * / | \
- * / D1| \
- * v<----- V ----->v
- * D2 D2'
- *
- * ! N = Normal
- * / D = Delta
- * | D1 = Delta component
- * - D2 = Delta component
- * \ D' = Reflection of D
- * c = scalar for D projection onto N
- *
- * D • N
- * c = ----- but since N • N = 1 then c = D • N
- * N • N
- *
- * D = D1 + D2
- * D1 = cN
- *
- * D = cN + D2 therefore
- * D2 = D - cN
- *
- * D2' = Negate(D2)
- * D' = D1 + D2'
- */
- static
- TQ3Status Pick_ComputeReflectedWorldRay(
- TQ3PickObject pickObj,
- TQ3Ray3D *pWorldRay,
- TQ3Vector3D *pWorldDelta)
- {
- TQ3Status status;
- TQ3Vector3D normal,
- newDelta,
- delta1,
- delta2;
- float c;
-
- status = Q3Pick_GetPickDetailData (pickObj, kFirstHit, kQ3PickDetailMaskNormal, &normal);
- DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
- if (status == kQ3Failure) {
- return status;
- }
-
- /* We negate pWorldDelta to reverse it's direction giving us the newDelta */
- Q3Vector3D_Negate(pWorldDelta, &newDelta);
-
- /* Compute delta1 by projecting delta onto normal */
- c = Q3Vector3D_Dot(&newDelta, &normal);
- Q3Vector3D_Scale(&normal, c, &delta1);
- Q3Vector3D_Subtract(&newDelta, &delta1, &delta2);
-
- /* Compute deltaPrime from delta1 and negated delta2. This is delta reflected through the normal */
- Q3Vector3D_Negate(&delta2, &delta2);
- Q3Vector3D_Add(&delta1, &delta2, pWorldDelta);
-
- /* Make sure pick ray is normalized */
- Q3Vector3D_Normalize(pWorldDelta, &pWorldRay->direction);
-
- return status;
- }
-
-
- #pragma mark -
-
- /*
- * Pick_CreateMovingObject
- */
- static
- TQ3GeometryObject Pick_CreateMovingObject(
- TDocumentPtr pDoc,
- TQ3Ray3D *pRay,
- TQ3GroupPosition *pGroupPosn)
- {
- TQ3EllipsoidData data;
- TQ3GeometryObject geometry = NULL;
- TQ3AttributeSet attr = NULL;
- TQ3GroupObject group = NULL;
-
- #define kObjectRadius (kCylRadius * 0.95f)
- #define kDefaultObjectColor 0.8, 0.8, 0.0
-
- data.origin = pRay->origin;
-
- data.orientation.x = 0.0;
- data.orientation.y = kObjectRadius;
- data.orientation.z = 0.0;
-
- data.majorRadius.x = 0.0;
- data.majorRadius.y = 0.0;
- data.majorRadius.z = kObjectRadius;
-
- data.minorRadius.x = kObjectRadius;
- data.minorRadius.y = 0.0;
- data.minorRadius.z = 0.0;
-
- data.uMin = data.vMin = 0.0f;
- data.uMax = data.vMax = 1.0f;
- data.caps = kQ3EndCapNone;
-
- data.interiorAttributeSet = NULL;
- data.ellipsoidAttributeSet = NULL;
-
- attr = Q3AttributeSet_New ();
- if (attr != NULL) {
- TQ3ColorRGB color = { kDefaultObjectColor };
-
- data.ellipsoidAttributeSet = attr;
- Q3AttributeSet_Add(attr, kQ3AttributeTypeDiffuseColor, &color);
-
- geometry = Q3Ellipsoid_New(&data);
- DEBUG_ASSERT(geometry != NULL, Q3Ellipsoid_New);
- Object_Dispose_NULL(&attr);
- }
-
- /* Put geometry in an unpickable group */
- group = Q3OrderedDisplayGroup_New();
- DEBUG_ASSERT(group != NULL, Q3OrderedDisplayGroup_New);
-
- if (group != NULL) {
- TQ3DisplayGroupState state;
-
- if (Q3DisplayGroup_GetState(group, &state) == kQ3Success) {
- state &= ~kQ3DisplayGroupStateMaskIsPicked;
- Q3DisplayGroup_SetState(group, state);
- }
-
- /* Add geometry to group but don't decrement reference */
- Q3Group_AddObject(group, geometry);
-
- /* Add group to main group */
- *pGroupPosn = Q3Group_AddObject(pDoc->fModel, group);
- Object_Dispose_NULL(&group);
- }
- else {
- /* Error */
- Object_Dispose_NULL(&geometry);
- }
-
- return geometry;
- }
-
-
- /*
- * Pick_GetNearestObject
- *
- * Find and return object hit if it's within minDistance.
- *
- * In this sample code since we're picking with a single ray
- * and our moving object is several units wide portions of
- * this object may pass through other objects because we're
- * testing with a ray cast from the center of our moving object
- * rather than near it's outer extents.
- */
- static
- TQ3Status Pick_GetNearestObject(
- TQ3PickObject pickObj,
- float minDistance,
- TQ3GeometryObject *pGeoHit,
- TQ3GeometryObject *pPrevGeoHit)
- {
- TQ3Status status = kQ3Failure;
- TQ3PickDetail pickDetailValidMask;
- float distance;
- TQ3GeometryObject objectHit = NULL;
-
- #define kDistanceTolerance 0.025 /* TODO This may need refinement */
-
- status = Q3Pick_GetPickDetailValidMask(pickObj, kFirstHit, &pickDetailValidMask);
-
- /* Get distance from ray to intersected geometry */
- if (HAS_Distance(pickDetailValidMask)) {
-
- status = Q3Pick_GetPickDetailData (pickObj, kFirstHit, kQ3PickDetailMaskDistance, &distance);
- DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
-
- /* Is this geometry within "minDistance"? */
- if ((distance <= (minDistance - kDistanceTolerance)) ||
- (distance <= (minDistance + kDistanceTolerance)))
- {
- if (HAS_Object(pickDetailValidMask)) {
- /* Get the object */
- status = Q3Pick_GetPickDetailData (pickObj, kFirstHit, kQ3PickDetailMaskObject, &objectHit);
-
- if (objectHit == *pGeoHit) {
- /* We've hit the same geometry */
- Object_Dispose_NULL(&objectHit);
- }
- else {
- /* Return new geometry hit */
- *pPrevGeoHit = *pGeoHit; /* Need to unhighlight this if non-NULL */
- *pGeoHit = objectHit;
- }
- }
- }
- else {
- *pPrevGeoHit = *pGeoHit; /* Need to unhighlight this if non-NULL */
- *pGeoHit = NULL;
- }
- }
-
- return status;
- }
-
-
- /*
- * Pick_HighlightObject
- */
- static
- TQ3Status Pick_HighlightObject(
- TDocumentPtr pDoc,
- TQ3GeometryObject geoObj,
- TQ3Boolean doHighlight)
- {
- TQ3Status status = kQ3Failure;
- TQ3AttributeSet attr = NULL;
- TQ3Switch isHighlighted = kQ3On;
- TQ3Boolean doesContain,
- doUpdate = kQ3False;
-
- DEBUG_ASSERT(pDoc != NULL, Pick_HighlightObject);
- DEBUG_ASSERT(geoObj != NULL, Pick_HighlightObject);
-
- status = Q3Geometry_GetAttributeSet(geoObj, &attr);
- DEBUG_ASSERT(status == kQ3Success, Q3Geometry_GetAttributeSet);
- DEBUG_ASSERT(attr != NULL, Q3Geometry_GetAttributeSet__attr);
-
- if (status == kQ3Success) {
- if (attr != NULL) {
- doesContain = Q3AttributeSet_Contains(
- attr, kQ3AttributeTypeHighlightState);
-
- /* Add highlight if geometry doesn't contain one */
- if (doHighlight == kQ3True) {
- if (doesContain == kQ3False) {
- Q3AttributeSet_Add(
- attr, kQ3AttributeTypeHighlightState, &isHighlighted);
- doUpdate = kQ3True;
- }
- }
- else {
- /* Remove highlight if there is one */
- if (doesContain == kQ3True) {
- Q3AttributeSet_Clear(
- attr, kQ3AttributeTypeHighlightState);
- doUpdate = kQ3True;
- }
- }
-
- if (doUpdate == kQ3True) {
- status = Q3Geometry_SetAttributeSet(geoObj, attr);
- DEBUG_ASSERT(status == kQ3Success, Q3Geometry_SetAttributeSet);
-
- Document_Draw(pDoc);
- }
- }
- }
- Object_Dispose_NULL(&attr);
-
- return status;
- }
-