Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Source/WRay_Pick.c
/* |
* 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; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14