I3DMacroscopic.cpp revision a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697
1a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten/* 2a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * Copyright (C) 2010 The Android Open Source Project 3a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * 4a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * Licensed under the Apache License, Version 2.0 (the "License"); 5a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * you may not use this file except in compliance with the License. 6a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * You may obtain a copy of the License at 7a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * 8a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * http://www.apache.org/licenses/LICENSE-2.0 9a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * 10a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * Unless required by applicable law or agreed to in writing, software 11a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * distributed under the License is distributed on an "AS IS" BASIS, 12a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * See the License for the specific language governing permissions and 14a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten * limitations under the License. 15a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten */ 16a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 17a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten/* 3DMacroscopic implementation */ 18a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 19a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten#include "sles_allinclusive.h" 20a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 21a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic SLresult I3DMacroscopic_SetSize(SL3DMacroscopicItf self, 22a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillimeter width, SLmillimeter height, SLmillimeter depth) 23a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 24a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 25a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_lock_exclusive(this); 26a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mSize.mWidth = width; 27a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mSize.mHeight = height; 28a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mSize.mDepth = depth; 29a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_exclusive(this); 30a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_SUCCESS; 31a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 32a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 33a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic SLresult I3DMacroscopic_GetSize(SL3DMacroscopicItf self, 34a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillimeter *pWidth, SLmillimeter *pHeight, SLmillimeter *pDepth) 35a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 36a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten if (NULL == pWidth || NULL == pHeight || NULL == pDepth) 37a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_PARAMETER_INVALID; 38a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 39a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_lock_shared(this); 40a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillimeter width = this->mSize.mWidth; 41a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillimeter height = this->mSize.mHeight; 42a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillimeter depth = this->mSize.mDepth; 43a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_shared(this); 44a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten *pWidth = width; 45a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten *pHeight = height; 46a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten *pDepth = depth; 47a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_SUCCESS; 48a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 49a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 50a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic SLresult I3DMacroscopic_SetOrientationAngles(SL3DMacroscopicItf self, 51a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillidegree heading, SLmillidegree pitch, SLmillidegree roll) 52a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 53a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 54a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_lock_exclusive(this); 55a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationAngles.mHeading = heading; 56a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationAngles.mPitch = pitch; 57a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationAngles.mRoll = roll; 58a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationActive = ANGLES_SET_VECTORS_UNKNOWN; 59a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mRotatePending = SL_BOOLEAN_FALSE; 60a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // ++this->mGeneration; 61a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_exclusive(this); 62a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_SUCCESS; 63a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 64a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 65a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic SLresult I3DMacroscopic_SetOrientationVectors(SL3DMacroscopicItf self, 66a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten const SLVec3D *pFront, const SLVec3D *pAbove) 67a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 68a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten if (NULL == pFront || NULL == pAbove) 69a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_PARAMETER_INVALID; 70a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 71a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLVec3D front = *pFront; 72a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLVec3D above = *pAbove; 73a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_lock_exclusive(this); 74a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mFront = front; 75a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mUp = above; 76a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationActive = ANGLES_UNKNOWN_VECTORS_SET; 77a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mRotatePending = SL_BOOLEAN_FALSE; 78a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_exclusive(this); 79a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_SUCCESS; 80a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 81a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 82a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic SLresult I3DMacroscopic_Rotate(SL3DMacroscopicItf self, 83a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLmillidegree theta, const SLVec3D *pAxis) 84a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 85a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten if (NULL == pAxis) 86a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_PARAMETER_INVALID; 87a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLVec3D axis = *pAxis; 88a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 89a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // FIXME Do the rotate here: 90a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // interface_lock_shared(this); 91a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // read old values and generation 92a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // interface_unlock_shared(this); 93a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // compute new position 94a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_lock_exclusive(this); 95a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten while (this->mRotatePending) 96a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_cond_wait(this); 97a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mTheta = theta; 98a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mAxis = axis; 99a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mRotatePending = SL_BOOLEAN_TRUE; 100a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // compare generation with saved value 101a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // if equal, store new position and increment generation 102a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // if unequal, discard new position 103a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_exclusive(this); 104a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_SUCCESS; 105a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 106a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 107a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic SLresult I3DMacroscopic_GetOrientationVectors(SL3DMacroscopicItf self, 108a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLVec3D *pFront, SLVec3D *pUp) 109a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 110a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten if (NULL == pFront || NULL == pUp) 111a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_PARAMETER_INVALID; 112a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 113a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_lock_exclusive(this); 114a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten for (;;) { 115a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten enum AnglesVectorsActive orientationActive = this->mOrientationActive; 116a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten switch (orientationActive) { 117a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten case ANGLES_COMPUTED_VECTORS_SET: // not in 1.0.1 118a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten case ANGLES_REQUESTED_VECTORS_SET: // not in 1.0.1 119a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten case ANGLES_UNKNOWN_VECTORS_SET: 120a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten case ANGLES_SET_VECTORS_COMPUTED: 121a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten { 122a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLVec3D front = this->mOrientationVectors.mFront; 123a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten SLVec3D up = this->mOrientationVectors.mUp; 124a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_exclusive(this); 125a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten *pFront = front; 126a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten *pUp = up; 127a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten } 128a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten break; 129a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten case ANGLES_SET_VECTORS_UNKNOWN: 130a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationActive = ANGLES_SET_VECTORS_REQUESTED; 131a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // fall through 132a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten case ANGLES_SET_VECTORS_REQUESTED: 133a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // matched by cond_broadcast in case multiple requesters 134a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_cond_wait(this); 135a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten continue; 136a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten default: 137a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten interface_unlock_exclusive(this); 138a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten assert(SL_BOOLEAN_FALSE); 139a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten pFront->x = 0; 140a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten pFront->y = 0; 141a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten pFront->z = 0; 142a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten pUp->x = 0; 143a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten pUp->y = 0; 144a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten pUp->z = 0; 145a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten break; 146a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten } 147a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten break; 148a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten } 149a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten return SL_RESULT_SUCCESS; 150a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 151a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 152a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenstatic const struct SL3DMacroscopicItf_ I3DMacroscopic_Itf = { 153a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic_SetSize, 154a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic_GetSize, 155a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic_SetOrientationAngles, 156a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic_SetOrientationVectors, 157a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic_Rotate, 158a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic_GetOrientationVectors 159a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten}; 160a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten 161a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kastenvoid I3DMacroscopic_init(void *self) 162a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten{ 163a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten I3DMacroscopic *this = (I3DMacroscopic *) self; 164a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mItf = &I3DMacroscopic_Itf; 165a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten#ifndef NDEBUG 166a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mSize.mWidth = 0; 167a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mSize.mHeight = 0; 168a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mSize.mDepth = 0; 169a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationAngles.mHeading = 0; 170a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationAngles.mPitch = 0; 171a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationAngles.mRoll = 0; 172a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mFront.x = 0; 173a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mFront.y = 0; 174a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mUp.x = 0; 175a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mUp.z = 0; 176a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten // this->mGeneration = 0; 177a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mTheta = 0x55555555; 178a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mAxis.x = 0x55555555; 179a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mAxis.y = 0x55555555; 180a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mAxis.z = 0x55555555; 181a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mRotatePending = SL_BOOLEAN_FALSE; 182a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten#endif 183a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mFront.z = -1000; 184a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationVectors.mUp.y = 1000; 185a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten this->mOrientationActive = ANGLES_SET_VECTORS_UNKNOWN; 186a6d984c32855a239f7f79a3d3b7f2ddfb8cb9697Glenn Kasten} 187