Changeset 6309
- Timestamp:
- Feb 2, 2006, 1:19:58 PM (20 years ago)
- Location:
- trunk/psLib
- Files:
-
- 3 edited
-
src/astro/psSphereOps.c (modified) (31 diffs)
-
test/astro/tst_psSphereOps.c (modified) (17 diffs)
-
test/astro/verified/tst_psSphereOps.stderr (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/psLib/src/astro/psSphereOps.c
r6039 r6309 8 8 * @author Dave Robbins, MHPCC 9 9 * 10 * @version $Revision: 1.1 0$ $Name: not supported by cvs2svn $11 * @date $Date: 2006-0 1-18 23:49:06$10 * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $ 11 * @date $Date: 2006-02-02 23:19:58 $ 12 12 * 13 13 * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii … … 44 44 { 45 45 psSphereRot r,s,t; 46 47 // directly from ADD -- there must be a better way?!46 //The following represents: r =a rotation about the z-axis by alphaP, s =a rotation about 47 // the y-axis by deltaP, and t =a rotation about the z-axis by phiP. 48 48 r.q0=0; 49 49 r.q1=0; … … 71 71 bool psMemCheckSphereRot(psPtr ptr) 72 72 { 73 //See if the ptr corresponds to a psSphereRot* 73 74 return ( psMemGetDeallocator(ptr) == (psFreeFunc)sphereRotFree ); 74 75 } 75 76 76 77 //This function is really a second allocate function for psSphereRot's that uses quaternion 78 // component inputs instead of angle inputs as in psSphereRotAlloc. 77 79 psSphereRot* psSphereRotQuat(double q0, 78 80 double q1, … … 80 82 double q3) 81 83 { 84 //allocate space for a new sphere rotation and set deallocator 82 85 psSphereRot* rot = (psSphereRot*)psAlloc(sizeof(psSphereRot)); 83 86 psMemSetDeallocator(rot, (psFreeFunc)sphereRotFree); 84 87 88 //The magnitude of a rotation quaternion should = 1 so we normalize here in case the 89 // inputs have not been. 85 90 double len = sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3); 86 91 rot->q0 = q0 / len; … … 92 97 } 93 98 94 psSphereRot* psSphereRotConjugate(psSphereRot *out, const psSphereRot *in) 95 { 99 psSphereRot* psSphereRotConjugate(psSphereRot *out, 100 const psSphereRot *in) 101 { 102 //if input sphere rotation is NULL, return NULL 96 103 if (in == NULL) { 97 104 psError(PS_ERR_BAD_PARAMETER_NULL, true, … … 99 106 return NULL; 100 107 } 108 //if output sphere rotation is NULL, allocate a new sphere rotation to return 101 109 if (out == NULL) { 102 110 out = (psSphereRot* ) psAlloc(sizeof(psSphereRot)); 103 111 psMemSetDeallocator(out, (psFreeFunc)sphereRotFree); 104 112 } 113 //Since q3 is the magnitude and q0,q1,q2 are direction, the conjugate rotation 114 // is formed by -q0,-q1,-q2,+q3. Note that this is the same as q0,q1,q2,-q3. 105 115 out->q0 = -in->q0; 106 116 out->q1 = -in->q1; … … 111 121 } 112 122 113 /******************************************************************************114 XXX: We convert Right Ascension angles to the range 0:PI. Is that acceptable?115 XXX: Should we do something for Declination as well?116 *****************************************************************************/117 123 psSphere* psSphereRotApply(psSphere* out, 118 124 const psSphereRot* transform, 119 125 const psSphere* coord) 120 126 { 127 //Make sure that input coordinates and rotation are not NULL 121 128 PS_ASSERT_PTR_NON_NULL(transform, NULL); 122 129 PS_ASSERT_PTR_NON_NULL(coord, NULL); 123 130 //If output coordinates is NULL allocate a new psSphere 124 131 if (out == NULL) { 125 132 out = psSphereAlloc(); 126 133 } 127 // double r = -coord->r; 128 // apply the transform by creating a new psSphereRot from the input coord 134 //apply the transform by creating a new psSphereRot from the input coord 129 135 // and combining it with the input transform (see ADD) 130 136 double cosD = cos(coord->d); … … 134 140 sin(coord->d), 135 141 0.0); 136 137 /* psSphereRot *test = psSphereRotICRSToGalactic(); 138 if (fabs(coord->d-DEG_TO_RAD(27.1283)) < 0.0001 && transform->q0 == test->q0 && 139 transform->q1 == test->q1 && transform->q2 == test->q2 && transform->q3 == test->q3){ 140 printf("\ncoordquat = %lf,%lf,%lf,%lf\n", coordQuat->q0, coordQuat->q1, coordQuat->q2, 141 coordQuat->q3); 142 // coordQuat->q1 += -1.0; 143 test->q3 = - test->q3; 144 *(psSphereRot**)&transform = test; 145 } 146 psFree(test); 147 */ 148 // Inserted by PAP 142 //Get the conjugate of the input rotation. Need to calculate r*p*R to apply rotation 143 // where R = r-conjugate. 149 144 psSphereRot *conjugate = psSphereRotConjugate(NULL, transform); 150 145 psSphereRot *temp = psSphereRotCombine(NULL, transform, coordQuat); 151 146 psSphereRot *result = psSphereRotCombine(NULL, temp, conjugate); 147 //From ADD we can find the new sphere coordinates, 148 // r is calculated as tan^-1 (q1/q0), d is sin^-1 (q2) 152 149 out->r = atan2(result->q1, result->q0); 153 // out->r = atan2(result->q1, result->q0);154 150 out->d = asin(result->q2); 155 151 out->rErr = 0.0; 156 152 out->dErr = 0.0; 157 158 if (out->r < -0.0001) { 153 //Simply for convention, we make sure all output r-parameters are positive in the range 154 // of 0 to 2pi 155 if (out->r < -0.000001) { 159 156 out->r += 2.0 * M_PI; 160 157 } … … 165 162 psFree(coordQuat); 166 163 return out; 167 // Pau.168 169 #if 0170 // psSphereRot* coordQuatConjugate = psSphereRotQuat(171 // coordQuat->q0, coordQuat->q1, coordQuat->q2, coordQuat->q3);172 // coordQuat = psSphereRotInvert(coordQuat);173 174 // calculate q=(rp)r'175 // coordQuat = psSphereRotCombine(coordQuat, transform, coordQuat);176 // coordQuat = psSphereRotCombine(coordQuat, coordQuat, coordQuatConjugate);177 // N.B., we can recycle coordQuat right away due to the implementation of178 // psSphereRotCombine; it puts the input values in a local variable first179 180 // out->r = atan2(coordQuat->q1,coordQuat->q0);181 // out->d = asin(coordQuat->q2);182 183 184 // psSphereRot *inv = psSphereRotInvert(transform);185 psSphereRot *inv = (psSphereRot*)psAlloc(sizeof(psSphereRot));186 *inv = *transform;187 // psSphereRot *result = psSphereRotCombine(NULL, transform, coordQuat);188 // inv = psSphereRotInvert(inv);189 // psSphereRotCombine(result, result, inv);190 // psSphereRot *result = psSphereRotCombine(NULL, inv, coordQuat);191 // psSphereRotCombine(result, result, transform);192 // psSphereRot *result = psSphereRotCombine(NULL, coordQuat, transform);193 // psSphereRotCombine(result, inv, result);194 195 psSphereRot *result = psSphereRotCombine(NULL, coordQuat, transform);196 inv = psSphereRotInvert(inv);197 // *(psSphereRot**)&transform = psSphereRotInvert( *(psSphereRot**)&transform);198 result = psSphereRotCombine(result, inv, result);199 // *(psSphereRot**)&transform = psSphereRotInvert( *(psSphereRot**)&transform);200 201 // out->r = atan2(result->q0, result->q1);202 out->r = atan2(result->q1, result->q0);203 out->d = asin(result->q2);204 out->rErr = 0.0;205 out->dErr = 0.0;206 psFree(inv);207 psFree(result);208 209 psFree(coordQuat);210 // psFree(coordQuatConjugate);211 212 return out;213 #endif214 164 } 215 165 … … 218 168 const psSphereRot* rot2) 219 169 { 170 //Make sure that input rotations are not NULL 220 171 PS_ASSERT_PTR_NON_NULL(rot1, NULL); 221 172 PS_ASSERT_PTR_NON_NULL(rot2, NULL); 222 173 //If output rotation is NULL, allocate a new psSphereRot to return 223 174 if (out == NULL) { 224 175 out = (psSphereRot* ) psAlloc(sizeof(psSphereRot)); … … 235 186 double b3 = rot2->q3; 236 187 237 // following came from ADD 238 // out->q0 = b3*a0 + b2*a1 - b1*a2 + b0*a3; 239 // out->q1 = b3*a1 - b2*a0 + b1*a3 + b0*a2; 240 // out->q2 = b3*a2 + b2*a3 + b1*a0 - b0*a1; 188 //Combine rot1 & rot2. Formulas here came from ADD. 241 189 out->q0 = a3*b0 + a0*b3 + a1*b2 - a2*b1; 242 190 out->q1 = a3*b1 - a0*b2 + a1*b3 + a2*b0; 243 191 out->q2 = a3*b2 + a0*b1 - a1*b0 + a2*b3; 244 245 192 out->q3 = b3*a3 - b2*a2 - b1*a1 - b0*a0; 246 193 … … 252 199 double phiP) 253 200 { 201 //This function should produce identical results to psSphereRotConjugate in most 202 // or possibly all situations. Creates the inverse rotation from the input angles. 254 203 return (psSphereRotAlloc(-phiP, -deltaP, -alphaP)); 255 204 } … … 258 207 { 259 208 psF64 T; 260 261 209 // Check for null parameter 262 210 PS_ASSERT_PTR_NON_NULL(time, NULL); 263 264 211 // Convert psTime to MJD 265 212 psF64 MJD = psTimeToMJD(time); 266 267 213 // Check the specified MJD is greater than 1900 268 214 if ( MJD < MJD_1900 ) { … … 270 216 return NULL; 271 217 } 272 273 218 // Calculate number of Julian centuries since 1900 274 219 T = ( MJD - MJD_1900 ) / JULIAN_CENTURY; 275 220 //Formulas for phiP, deltaP, alphaP came from ADD. 276 221 psF64 phiP = - DEG_TO_RAD(270.0); 277 222 psF64 deltaP = - (DEG_TO_RAD(23.0) + … … 289 234 { 290 235 psF64 T; 291 292 236 // Check for null parameter 293 237 PS_ASSERT_PTR_NON_NULL(time, NULL); 294 295 238 // Convert psTime to MJD 296 239 psF64 MJD = psTimeToMJD(time); 297 298 240 // Check the specified MJD is greater than 1900 299 241 if ( MJD < MJD_1900 ) { … … 301 243 return NULL; 302 244 } 303 304 245 // Calculate number of Julian centuries since 1900 305 246 T = ( MJD - MJD_1900 ) / JULIAN_CENTURY; 306 247 248 //Formulas for phiP, deltaP, alphaP came from ADD. Notice that the formulas are the 249 // same as for EclipticToICRS except that alpha=-phiP, deltaP=-deltaP, phiP=-alphaP to 250 // produce the inverse rotation as in psSphereRotInverse. 307 251 psF64 alphaP = DEG_TO_RAD(270.0); 308 252 psF64 deltaP = DEG_TO_RAD(23.0) + … … 317 261 } 318 262 319 // XXX: This is bug 245: alphaP swaps with phiP from psSphereTransformGalacticToICRS()320 263 psSphereRot* psSphereRotGalacticToICRS(void) 321 264 { 322 /* psF64 phiP = - DEG_TO_RAD(180.0-192.85948); 323 psF64 deltaP = - DEG_TO_RAD(90.0 - 27.12825); 324 psF64 alphaP = - DEG_TO_RAD(90.0+32.93192); 325 */ 265 //Formulas for alphaP, deltaP, phiP came from the ADD for ICRSToGalactic. Notice that 266 // this is the reason the inverse gets allocated and returned here. 326 267 psF64 alphaP = DEG_TO_RAD(180.0-192.85948); 327 268 psF64 deltaP = DEG_TO_RAD(90.0 - 27.12825); … … 332 273 psSphereRot* psSphereRotICRSToGalactic(void) 333 274 { 275 //Formulas for alphaP, deltaP, phiP came from ADD. 334 276 psF64 alphaP = DEG_TO_RAD(180.0-192.85948); 335 277 psF64 deltaP = DEG_TO_RAD(90.0 - 27.12825); … … 339 281 } 340 282 341 /****************************************************************************** 342 The basic idea is to project both positions onto the linear plane, with 343 position1 at the center, then calculate the linear offset between those 344 projections. 345 346 XXX: Do I need to check for unacceptable transformation parameters? Maybe, 347 if the points are on the North/South Pole, etc? 348 349 XXX: Do I need to somehow scale this projection? 350 351 XXX: Does PS_LINEAR mode make sense? The result must be returned in psSphere 352 regardless of the mode. 353 354 XXX: How to compound errors? 355 *****************************************************************************/ 283 //Calculates the difference between coordinates in position1 & 2 and returns this offset. 356 284 psSphere* psSphereGetOffset(const psSphere* position1, 357 285 const psSphere* position2, … … 359 287 psSphereOffsetUnit unit) 360 288 { 289 //Make sure that input coordinates are not NULL & that mode & unit are valid 361 290 PS_ASSERT_PTR_NON_NULL(position1, NULL); 362 291 PS_ASSERT_PTR_NON_NULL(position2, NULL); 363 364 292 // Check positions near 90 degree and issue warnings if necessary 365 293 if (position1->d >= DEG_TO_RAD(90.0)) { … … 373 301 return NULL; 374 302 } 375 376 303 // Allocate return structure 377 304 psSphere* tmp = psSphereAlloc(); … … 380 307 // onto tangent plane, set point projected into psSphere structure x->r y->d 381 308 if (mode == PS_LINEAR) { 309 //The basic idea is to project both positions onto the linear plane, with position1 310 // at the center, then calculate the linear offset between those projections. 382 311 psProjection* proj = psProjectionAlloc(position1->r, 383 312 position1->d, … … 385 314 1.0, 386 315 PS_PROJ_TAN); 387 388 316 // Perform projection onto tangent plane 389 317 psPlane* lin = psProject(position2, proj); 390 391 318 // Set return values 392 319 tmp->r = lin->x; 393 320 tmp->d = lin->y; 394 395 321 // Free data structures allocated 396 322 psFree(proj); … … 410 336 tmp->dErr = 0.0; 411 337 412 // Convert to desired units338 // Convert output to desired units 413 339 if (unit == PS_ARCSEC) { 414 340 tmp->r = RAD_TO_SEC(tmp->r); … … 428 354 return NULL; 429 355 } 356 430 357 // Invalid mode 431 358 } else { 432 433 359 psError(PS_ERR_BAD_PARAMETER_VALUE, true, 434 360 PS_ERRORTEXT_psCoord_OFFSET_MODE_UNKNOWN, … … 442 368 } 443 369 444 /****************************************************************************** 445 XXX: Do we need to check for unacceptable transformation parameters? Maybe, 446 if the points are on the North/South Pole, etc? 447 448 XXX: Do we need to somehow scale this projection? 449 450 XXX: I copied the algorithm from the ADD exactly. 451 452 XXX: Should we compound errors? 453 *****************************************************************************/ 454 370 //Applies the specified offset to the input position coordinates and returns the new position. 455 371 psSphere* psSphereSetOffset(const psSphere* position, 456 372 const psSphere* offset, … … 458 374 psSphereOffsetUnit unit) 459 375 { 376 //Make sure that input coordinates are not NULL & that mode & unit are valid 460 377 PS_ASSERT_PTR_NON_NULL(position, NULL); 461 378 PS_ASSERT_PTR_NON_NULL(offset, NULL); … … 469 386 // new sphere coordinate 470 387 if (mode == PS_LINEAR) { 471 472 388 // Allocate plane coordinate and set coordinate 473 389 psPlane* lin = psPlaneAlloc(); 474 390 lin->x = offset->r; 475 391 lin->y = offset->d; 476 477 392 // Allocate and set projection structure 478 393 psProjection* proj = psProjectionAlloc(position->r, … … 481 396 1.0, 482 397 PS_PROJ_TAN); 483 484 398 // Project tangent plane coord to spherical coord 485 399 tmp = psDeproject(lin, proj); 486 487 400 // Free data structures used 488 401 psFree(proj); … … 492 405 // to the position and wrap to 0 to 2pi 493 406 } else if (mode == PS_SPHERICAL) { 494 495 407 // Convert offset unit to radians 496 408 if (unit == PS_ARCSEC) { … … 526 438 // Invalid mode report error 527 439 } else { 528 529 440 psError(PS_ERR_BAD_PARAMETER_VALUE, true, 530 441 PS_ERRORTEXT_psCoord_OFFSET_MODE_UNKNOWN, -
trunk/psLib/test/astro/tst_psSphereOps.c
r6226 r6309 5 5 * @author d-Rob, MHPCC 6 6 * 7 * @version $Revision: 1.1 7$ $Name: not supported by cvs2svn $8 * @date $Date: 2006-0 1-27 20:08:58 $7 * @version $Revision: 1.18 $ $Name: not supported by cvs2svn $ 8 * @date $Date: 2006-02-02 23:19:58 $ 9 9 * 10 10 * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii … … 64 64 double a2 = (ALPHA_P + PHI_P)/2.0; 65 65 double a3 = (ALPHA_P + PHI_P)/2.0; 66 66 //From Mathworld, this is another way to calculate the quaternions of a rotation 67 67 double q0 = sin(a0)*sin(DELTA_P/2); 68 68 double q1 = cos(a1)*sin(DELTA_P/2); 69 69 double q2 = sin(a2)*cos(DELTA_P/2); 70 70 double q3 = cos(a3)*cos(DELTA_P/2); 71 71 //Check that the quaternion components all match 72 72 if (DBL_EPSILON < fabs(q0 - myST->q0)) { 73 73 psError(PS_ERR_UNKNOWN,true,"myST->q0 is %lf, should be %lf\n", myST->q0, q0); … … 99 99 double a2 = (ALPHA_P + PHI_P)/2.0; 100 100 double a3 = (ALPHA_P + PHI_P)/2.0; 101 101 //From Mathworld, this is another way to calculate the quaternions of a rotation 102 102 double q0 = sin(a0)*sin(DELTA_P/2); 103 103 double q1 = cos(a1)*sin(DELTA_P/2); … … 111 111 return 1; 112 112 } 113 113 //Check that the quaternion components all match 114 114 if (FLT_EPSILON < fabs(q0 - myST->q0)) { 115 115 psError(PS_ERR_UNKNOWN,true,"myST->q0 is %lf, should be %lf\n", myST->q0, q0); … … 135 135 } 136 136 137 138 137 // We do a simple identity transformation on a few RA, DEC pairs. 139 138 psS32 testSphereRotApply1( void ) 140 139 { 141 142 140 psSphere *in = psSphereAlloc(); 143 141 psSphere *out = psSphereAlloc(); … … 145 143 psSphere *rc = NULL; 146 144 psSphere *temp2 = psSphereAlloc(); 147 // psSphereRot *myST = psSphereRotAlloc(0.0, 0.0, 0.0);148 145 psSphereRot *myST = psSphereRotAlloc(ALPHA_P, DELTA_P, PHI_P); 149 146 psSphereRot *yourST = psSphereRotInvert(ALPHA_P, DELTA_P, PHI_P); … … 155 152 in->rErr = 0.0; 156 153 in->dErr = 0.0; 157 154 //Here we apply the sphere rotation, then the inverse 158 155 temp2 = psSphereRotApply(temp2, myST, in); 159 156 out = psSphereRotApply(out, yourST, temp2); 160 157 //Check that out matches in 161 158 if (ERROR_TOL < fabs(out->r - in->r)) { 162 159 psError(PS_ERR_UNKNOWN,true,"out->r is %f, should be %f\n", out->r, in->r); … … 206 203 psS32 testSphereRotApplyCelestial( void) 207 204 { 205 //Test cases below were provided in ADD. 208 206 int numTestPoints = 3; 209 207 // ICRS coordinates … … 220 218 221 219 for (int x = 0; x < numTestPoints; x++) { 222 220 //Setup the appropriate rotations 223 221 psTime* time = psTimeFromMJD(t[x]); 224 222 psSphereRot* toEcliptic = psSphereRotICRSToEcliptic(time); … … 246 244 return 1; 247 245 } 246 //The second condition here (d - 90) is used b/c 90 is a pole. 248 247 if ( (fabs(RAD_TO_DEG(icrsFromEcliptic->r) - alpha[x]) > TOLERANCE && 249 248 fabs(RAD_TO_DEG(icrsFromEcliptic->d) - 90.0) > TOLERANCE ) || … … 258 257 psFree(icrsFromEcliptic); 259 258 260 // apply/unapply Galactic 261 // if (x == 1){ 262 // psSphereRot *temp = psSphereRotICRSToGalactic(); 263 // *fromGalactic = *temp; 264 // fromGalactic->q3 *= -1.0; 265 // psFree(temp); 266 // } 259 //Setup galactic transformations 267 260 psSphere* galactic = psSphereRotApply(NULL, toGalactic, icrs); 268 261 psSphere* icrsFromGalactic = psSphereRotApply(NULL, fromGalactic, galactic); … … 277 270 return 3; 278 271 } 272 //The second condition here (d - 90) is used b/c 90 is a pole. 279 273 if ( (fabs(RAD_TO_DEG(icrsFromGalactic->r) - alpha[x]) > TOLERANCE && 280 274 fabs(RAD_TO_DEG(icrsFromGalactic->d) - 90.0) > TOLERANCE ) || … … 306 300 307 301 //Test Set for NULL position 302 psLogMsg(__func__,PS_LOG_INFO,"Following should generate an error"); 308 303 output = psSphereSetOffset(empty, offset, PS_SPHERICAL, PS_DEGREE); 309 304 if (output != NULL) { … … 313 308 } 314 309 //Test Set for NULL offset 310 psLogMsg(__func__,PS_LOG_INFO,"Following should generate an error"); 315 311 output = psSphereSetOffset(offset, empty, PS_SPHERICAL, PS_DEGREE); 316 312 if (output != NULL) { … … 320 316 } 321 317 //Test Get for NULL position1 318 psLogMsg(__func__,PS_LOG_INFO,"Following should generate an error"); 322 319 output = psSphereGetOffset(empty, origin, PS_LINEAR, PS_RADIAN); 323 320 if (output != NULL) { … … 327 324 } 328 325 //Test Get for NULL position2 326 psLogMsg(__func__,PS_LOG_INFO,"Following should generate an error"); 329 327 output = psSphereGetOffset(origin, empty, PS_LINEAR, PS_RADIAN); 330 328 if (output != NULL) { … … 354 352 offset->d = 1.0; 355 353 output = psSphereSetOffset(origin, offset, PS_LINEAR, PS_RADIAN); 356 /* if ( fabs(output->r - 1.0) > 0.0001 || fabs(output->d - 1.0) > 0.0001 ) { 357 psError(PS_ERR_BAD_PARAMETER_VALUE, false, 358 "psSphereSetOffset failed to return correct linear offset values.\n"); 359 printf("\n SphereSetOffset should be %lf, %lf and is %lf, %lf\n", offset->r, offset->d, 360 output->r, output->d); 361 return 6; 362 } 363 */ 364 // output->r *= -1.0; 365 // output->d *= -1.0; 354 366 355 empty = psSphereGetOffset(origin, output, PS_LINEAR, PS_RADIAN); 367 356 if ( fabs(offset->r - empty->r) > 0.0001 || fabs(offset->d - empty->d) > 0.0001 ) { -
trunk/psLib/test/astro/verified/tst_psSphereOps.stderr
r6226 r6309 49 49 \**********************************************************************************/ 50 50 51 <HOST>|I|testSphereOffset 52 Following should generate an error 51 53 <HOST>|E|psSphereSetOffset (FILE:LINENO) 52 54 Unallowable operation: position is NULL. 55 <HOST>|I|testSphereOffset 56 Following should generate an error 53 57 <HOST>|E|psSphereSetOffset (FILE:LINENO) 54 58 Unallowable operation: offset is NULL. 59 <HOST>|I|testSphereOffset 60 Following should generate an error 55 61 <HOST>|E|psSphereGetOffset (FILE:LINENO) 56 62 Unallowable operation: position1 is NULL. 63 <HOST>|I|testSphereOffset 64 Following should generate an error 57 65 <HOST>|E|psSphereGetOffset (FILE:LINENO) 58 66 Unallowable operation: position2 is NULL.
Note:
See TracChangeset
for help on using the changeset viewer.
