History

improved isStuck detection and MyAvatar::safeLanding() trigger

Andrew Meadows committed 7 years ago

Commit 32400a61 parent 9382fb88 files · +311 −189Browse files

Showing the changes to libraries/physics/src/CharacterController.cpp (1 of 8 files). Show all

Unified · Split

libraries/physics/src/CharacterController.cpp+250−74
@@ -11,14 +11,136 @@ · 10 unmodified lines
1111
1212 #include "CharacterController.h"
1313
14−#include <NumericalConstants.h>
1514 #include <AvatarConstants.h>
15+#include <NumericalConstants.h>
16+#include <PhysicsCollisionGroups.h>
1617
1718 #include "ObjectMotionState.h"
1819 #include "PhysicsHelpers.h"
1920 #include "PhysicsLogging.h"
2021
2122 const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
23+static bool _flippedThisFrame = false;
24+
25+// Note: flipBackfaceTriangleNormals is registered as a sub-callback to Bullet's gContactAddedCallback feature
26+// when we detect MyAvatar is "stuck". It will reverse the triangles on the backface of mesh shapes, unless it thinks
27+// the old normal would be better at extracting MyAvatar out along its UP direction.
28+bool flipBackfaceTriangleNormals(btManifoldPoint& cp,
29+ const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0,
30+ const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) {
31+ static int32_t numCalls = 0;
32+ ++numCalls;
33+ // This callback is ONLY called on objects with btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK flag
34+ // and the flagged object will always be sorted to Obj0. Hence the "other" is always Obj1.
35+ const btCollisionObject* other = colObj1Wrap->m_collisionObject;
36+
37+ if (other->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) {
38+ // access the meshInterface
39+ auto meshShape = static_cast<const btBvhTriangleMeshShape*>(other->getCollisionShape());
40+ const btStridingMeshInterface* meshInterface = meshShape->getMeshInterface();
41+
42+ // figure out about how to navigate meshInterface
43+ const uint8_t* vertexBase;
44+ int32_t numverts;
45+ PHY_ScalarType vertexType;
46+ int32_t vertexStride;
47+ const uint8_t* indexBase;
48+ int32_t indexStride;
49+ int32_t numFaces;
50+ PHY_ScalarType indicesType;
51+ int32_t subPart = colObj1Wrap->m_partId;
52+ // NOTE: all arguments are being passed by reference except the bases (passed by pointer) and subPart (true input)
53+ meshInterface->getLockedReadOnlyVertexIndexBase(&vertexBase, numverts, vertexType, vertexStride, &indexBase, indexStride, numFaces, indicesType, subPart);
54+
55+ // fetch the triangle vertices
56+ int32_t triangleIndex = colObj1Wrap->m_index;
57+ assert(vertexType == PHY_FLOAT); // all mesh vertex data is float...
58+ // ...but indicesType can vary
59+ btVector3 triangleVertex[3];
60+ switch (indicesType) {
61+ case PHY_INTEGER: {
62+ uint32_t* triangleIndices = (uint32_t*)(indexBase + triangleIndex * indexStride);
63+ float* triangleBase;
64+ triangleBase = (float*)(vertexBase + triangleIndices[0] * vertexStride);
65+ triangleVertex[0].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
66+ triangleBase = (float*)(vertexBase + triangleIndices[1] * vertexStride);
67+ triangleVertex[1].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
68+ triangleBase = (float*)(vertexBase + triangleIndices[2] * vertexStride);
69+ triangleVertex[2].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
70+ }
71+ break;
72+ case PHY_SHORT: {
73+ uint16_t* triangleIndices = (uint16_t*)(indexBase + triangleIndex * indexStride);
74+ float* triangleBase;
75+ triangleBase = (float*)(vertexBase + triangleIndices[0] * vertexStride);
76+ triangleVertex[0].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
77+ triangleBase = (float*)(vertexBase + triangleIndices[1] * vertexStride);
78+ triangleVertex[1].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
79+ triangleBase = (float*)(vertexBase + triangleIndices[2] * vertexStride);
80+ triangleVertex[2].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
81+ }
82+ break;
83+ case PHY_UCHAR: {
84+ uint8_t* triangleIndices = (uint8_t*)(indexBase + triangleIndex * indexStride);
85+ float* triangleBase;
86+ triangleBase = (float*)(vertexBase + triangleIndices[0] * vertexStride);
87+ triangleVertex[0].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
88+ triangleBase = (float*)(vertexBase + triangleIndices[1] * vertexStride);
89+ triangleVertex[1].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
90+ triangleBase = (float*)(vertexBase + triangleIndices[2] * vertexStride);
91+ triangleVertex[2].setValue(triangleBase[0], triangleBase[1], triangleBase[2]);
92+ }
93+ break;
94+ default:
95+ return false;
96+ }
97+
98+ // compute faceNormal
99+ btVector3 meshScaling = meshInterface->getScaling();
100+ triangleVertex[0] *= meshScaling;
101+ triangleVertex[1] *= meshScaling;
102+ triangleVertex[2] *= meshScaling;
103+ btVector3 faceNormal = other->getWorldTransform().getBasis() * btCross(triangleVertex[1] - triangleVertex[0], triangleVertex[2] - triangleVertex[0]);
104+ float nDotF = btDot(faceNormal, cp.m_normalWorldOnB);
105+ if (nDotF <= 0.0f && faceNormal.length2() > EPSILON) {
106+ faceNormal.normalize();
107+ // flip the contact normal to be aligned with the face normal...
108+ // ...but only if old normal does NOT point along obj0's UP
109+ // (because we're "stuck" and UP is the likely path out)
110+ btVector3 up = colObj0Wrap->m_collisionObject->getWorldTransform().getBasis() * LOCAL_UP_AXIS;
111+ if (cp.m_normalWorldOnB.dot(up) <= 0.0f) {
112+ nDotF = btDot(faceNormal, cp.m_normalWorldOnB);
113+ cp.m_normalWorldOnB -= 2.0f * nDotF * faceNormal;
114+ _flippedThisFrame = true;
115+ }
116+ // Note: if we're flipping normals it means the "Are we stuck?" logic is concluding "Yes, we are".
117+ // But when we flip the normals it typically causes the ContactManifold to discard the modified ManifoldPoint
118+ // which in turn causes the "Are we stuck?" logic to incorrectly conclude "No, we are not".
119+ // So we set '_flippedThisFrame = true' here and use it later to stay in "stuck" state until flipping stops
120+ }
121+ }
122+
123+ // KEEP THIS: in case we add support for concave shapes which delegate to temporary btTriangleShapes (such as btHeightfieldTerrainShape)
124+ //else if (other->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE) {
125+ // auto triShape = static_cast<const btTriangleShape*>(other->getCollisionShape());
126+ // const btVector3* v = triShape->m_vertices1;
127+ // btVector3 faceNormal = other->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]);
128+ // float nDotF = btDot(faceNormal, cp.m_normalWorldOnB);
129+ // if (nDotF <= 0.0f && faceNormal.length2() > EPSILON) {
130+ // faceNormal.normalize();
131+ // // flip the contact normal to be aligned with the face normal
132+ // cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal;
133+ // }
134+ //}
135+ // KEEP THIS
136+
137+ // NOTE: this ManifoldPoint is a candidate and hasn't been accepted yet into the final ContactManifold yet.
138+ // So when we modify its parameters we can convince Bullet to discard it.
139+ //
140+ // by our own convention:
141+ // return true when this ManifoldPoint has been modified in a way that would disable it
142+ return false;
143+};
22144
23145 #ifdef DEBUG_STATE_CHANGE
24146 #define SET_STATE(desiredState, reason) setState(desiredState, reason)
@@ -92,64 +214,70 @@ CharacterController::~CharacterController() { · 67 unmodified lines
92214 }
93215
94216 bool CharacterController::needsRemoval() const {
95− return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);
217+ return (_physicsEngine && (_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);
96218 }
97219
98220 bool CharacterController::needsAddition() const {
99− return ((_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION) == PENDING_FLAG_ADD_TO_SIMULATION);
221+ return (_physicsEngine && (_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION) == PENDING_FLAG_ADD_TO_SIMULATION);
100222 }
101223
102−void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
103− if (_dynamicsWorld != world) {
104− // remove from old world
105− if (_dynamicsWorld) {
106− if (_rigidBody) {
107− _dynamicsWorld->removeRigidBody(_rigidBody);
108− _dynamicsWorld->removeAction(this);
109− }
110− _dynamicsWorld = nullptr;
111− }
112− int32_t collisionMask = computeCollisionMask();
113− int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR;
114− if (_rigidBody) {
115− updateMassProperties();
116− }
117− if (world && _rigidBody) {
118− // add to new world
119− _dynamicsWorld = world;
120− _pendingFlags &= ~PENDING_FLAG_JUMP;
121− _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, collisionMask);
122− _dynamicsWorld->addAction(this);
123− // restore gravity settings because adding an object to the world overwrites its gravity setting
124− _rigidBody->setGravity(_currentGravity * _currentUp);
125− // set flag to enable custom contactAddedCallback
126− _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
127−
128− // enable CCD
129− _rigidBody->setCcdSweptSphereRadius(_radius);
130− _rigidBody->setCcdMotionThreshold(_radius);
131−
132− btCollisionShape* shape = _rigidBody->getCollisionShape();
133− assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
134− _ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
135− }
136− _ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup));
137− _ghost.setCollisionWorld(_dynamicsWorld);
138− _ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
224+void CharacterController::removeFromWorld() {
225+ if (_inWorld) {
139226 if (_rigidBody) {
140− _ghost.setWorldTransform(_rigidBody->getWorldTransform());
227+ _physicsEngine->getDynamicsWorld()->removeRigidBody(_rigidBody);
228+ _physicsEngine->getDynamicsWorld()->removeAction(this);
141229 }
230+ _inWorld = false;
142231 }
143− if (_dynamicsWorld) {
144− if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
145− // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
146− _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION |
147− PENDING_FLAG_ADD_DETAILED_TO_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
148− } else {
149− _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
150− }
232+ _pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION;
233+}
234+
235+void CharacterController::addToWorld() {
236+ if (!_rigidBody) {
237+ return;
238+ }
239+ if (_inWorld) {
240+ _pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
241+ return;
242+ }
243+ btDiscreteDynamicsWorld* world = _physicsEngine->getDynamicsWorld();
244+ int32_t collisionMask = computeCollisionMask();
245+ int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR;
246+
247+ updateMassProperties();
248+ _pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
249+
250+ // add to new world
251+ _pendingFlags &= ~PENDING_FLAG_JUMP;
252+ world->addRigidBody(_rigidBody, collisionGroup, collisionMask);
253+ world->addAction(this);
254+ _inWorld = true;
255+
256+ // restore gravity settings because adding an object to the world overwrites its gravity setting
257+ _rigidBody->setGravity(_currentGravity * _currentUp);
258+ // set flag to enable custom contactAddedCallback
259+ _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
260+
261+ // enable CCD
262+ _rigidBody->setCcdSweptSphereRadius(_radius);
263+ _rigidBody->setCcdMotionThreshold(_radius);
264+
265+ btCollisionShape* shape = _rigidBody->getCollisionShape();
266+ assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
267+ _ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
268+
269+ _ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup));
270+ _ghost.setCollisionWorld(world);
271+ _ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
272+ if (_rigidBody) {
273+ _ghost.setWorldTransform(_rigidBody->getWorldTransform());
274+ }
275+ if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
276+ // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
277+ _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION |
278+ PENDING_FLAG_ADD_DETAILED_TO_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
151279 } else {
152− _pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION;
280+ _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
153281 }
154282 }
155283
@@ -159,17 +287,21 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 3 unmodified lines
159287 btDispatcher* dispatcher = collisionWorld->getDispatcher();
160288 int numManifolds = dispatcher->getNumManifolds();
161289 bool hasFloor = false;
162− bool isStuck = false;
290+ bool probablyStuck = _isStuck && _flippedThisFrame;
163291
164292 btTransform rotation = _rigidBody->getWorldTransform();
165293 rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part
166294
295+ float deepestDistance = 0.0f;
296+ float strongestImpulse = 0.0f;
297+
167298 for (int i = 0; i < numManifolds; i++) {
168299 btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i);
169300 if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) {
170301 bool characterIsFirst = _rigidBody == contactManifold->getBody0();
171302 int numContacts = contactManifold->getNumContacts();
172303 int stepContactIndex = -1;
304+ bool stepValid = true;
173305 float highestStep = _minStepHeight;
174306 for (int j = 0; j < numContacts; j++) {
175307 // check for "floor"
@@ -177,28 +309,24 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 1 unmodified line
177309 btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
178310 btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
179311 btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp);
180− // If there's non-trivial penetration with a big impulse for several steps, we're probably stuck.
181− // Note it here in the controller, and let MyAvatar figure out what to do about it.
182− const float STUCK_PENETRATION = -0.05f; // always negative into the object.
183− const float STUCK_IMPULSE = 500.0f;
184− const int STUCK_LIFETIME = 3;
185− if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) {
186− isStuck = true; // latch on
312+
313+ float distance = contact.getDistance();
314+ if (distance < deepestDistance) {
315+ deepestDistance = distance;
316+ }
317+ float impulse = contact.getAppliedImpulse();
318+ if (impulse > strongestImpulse) {
319+ strongestImpulse = impulse;
187320 }
321+
188322 if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) {
189323 hasFloor = true;
190− if (!pushing && isStuck) {
191− // we're not pushing against anything and we're stuck so we can early exit
192− // (all we need to know is that there is a floor)
193− break;
194− }
195324 }
196− if (pushing && _targetVelocity.dot(normal) < 0.0f) {
325+ if (stepValid && pushing && _targetVelocity.dot(normal) < 0.0f) {
197326 // remember highest step obstacle
198327 if (!_stepUpEnabled || hitHeight > _maxStepHeight) {
199328 // this manifold is invalidated by point that is too high
200− stepContactIndex = -1;
201− break;
329+ stepValid = false;
202330 } else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) {
203331 highestStep = hitHeight;
204332 stepContactIndex = j;
@@ -206,7 +334,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 1 unmodified line
206334 }
207335 }
208336 }
209− if (stepContactIndex > -1 && highestStep > _stepHeight) {
337+ if (stepValid && stepContactIndex > -1 && highestStep > _stepHeight) {
210338 // remember step info for later
211339 btManifoldPoint& contact = contactManifold->getContactPoint(stepContactIndex);
212340 btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
@@ -214,13 +342,37 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { · 1 unmodified line
214342 _stepHeight = highestStep;
215343 _stepPoint = rotation * pointOnCharacter; // rotate into world-frame
216344 }
217− if (hasFloor && isStuck && !(pushing && _stepUpEnabled)) {
218− // early exit since all we need to know is that we're on a floor
219− break;
345+ }
346+ }
347+
348+ // If there's deep penetration and big impulse we're probably stuck.
349+ const float STUCK_PENETRATION = -0.05f; // always negative into the object.
350+ const float STUCK_IMPULSE = 500.0f;
351+ probablyStuck = probablyStuck || (deepestDistance < STUCK_PENETRATION && strongestImpulse > STUCK_IMPULSE);
352+
353+ if (_isStuck != probablyStuck) {
354+ ++_stuckTransitionCount;
355+ if (_stuckTransitionCount == NUM_FRAMES_FOR_STUCK_TRANSITION) {
356+ // we've been in this "probablyStuck" state for several consecutive frames
357+ // --> make it official by changing state
358+ _isStuck = probablyStuck;
359+ // start _numStuckFrames at NUM_FRAMES_FOR_SAFE_LANDING_RETRY so SafeLanding tries to help immediately
360+ _numStuckFrames = NUM_FRAMES_FOR_SAFE_LANDING_RETRY;
361+ _stuckTransitionCount = 0;
362+ if (_isStuck) {
363+ _physicsEngine->addContactAddedCallback(flipBackfaceTriangleNormals);
364+ } else {
365+ _physicsEngine->removeContactAddedCallback(flipBackfaceTriangleNormals);
366+ _flippedThisFrame = false;
220367 }
221368 }
369+ } else {
370+ _stuckTransitionCount = 0;
371+ if (_isStuck) {
372+ ++_numStuckFrames;
373+ _flippedThisFrame = false;
374+ }
222375 }
223− _isStuck = isStuck;
224376 return hasFloor;
225377 }
226378
@@ -452,7 +604,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const · 225 unmodified lines
452604 _minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
453605 _maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
454606
455− if (_dynamicsWorld) {
607+ if (_physicsEngine) {
456608 // must REMOVE from world prior to shape update
457609 _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
458610 }
@@ -470,6 +622,15 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const · 11 unmodified lines
470622 }
471623 }
472624
625+void CharacterController::setPhysicsEngine(const PhysicsEnginePointer& engine) {
626+ if (!_physicsEngine && engine) {
627+ // ATM there is only one PhysicsEngine: it is a singleton, and we are taking advantage
628+ // of that assumption here. If we ever introduce more and allow for this backpointer
629+ // to change then we'll have to overhaul this method.
630+ _physicsEngine = engine;
631+ }
632+}
633+
473634 void CharacterController::setCollisionless(bool collisionless) {
474635 if (collisionless != _collisionless) {
475636 _collisionless = collisionless;
@@ -681,7 +842,7 @@ void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) { · 205 unmodified lines
681842 }
682843
683844 void CharacterController::updateState() {
684− if (!_dynamicsWorld) {
845+ if (!_physicsEngine) {
685846 return;
686847 }
687848 if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) {
@@ -712,7 +873,7 @@ void CharacterController::updateState() { · 24 unmodified lines
712873
713874 ClosestNotMe rayCallback(_rigidBody);
714875 rayCallback.m_closestHitFraction = 1.0f;
715− _dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
876+ _physicsEngine->getDynamicsWorld()->rayTest(rayStart, rayEnd, rayCallback);
716877 bool rayHasHit = rayCallback.hasHit();
717878 quint64 now = usecTimestampNow();
718879 if (rayHasHit) {
@@ -829,6 +990,21 @@ void CharacterController::updateState() { · 110 unmodified lines
829990 }
830991
831992 void CharacterController::preSimulation() {
993+ if (needsRemoval()) {
994+ removeFromWorld();
995+
996+ // We must remove any existing contacts for the avatar so that any new contacts will have
997+ // valid data. MyAvatar's RigidBody is the ONLY one in the simulation that does not yet
998+ // have a MotionState so we pass nullptr to removeContacts().
999+ if (_physicsEngine) {
1000+ _physicsEngine->removeContacts(nullptr);
1001+ }
1002+ }
1003+ updateShapeIfNecessary();
1004+ if (needsAddition()) {
1005+ addToWorld();
1006+ }
1007+
8321008 if (_rigidBody) {
8331009 // slam body transform and remember velocity
8341010 _rigidBody->setWorldTransform(btTransform(btTransform(_rotation, _position)));