back out some off-brand changes. add parentChanged call

This commit is contained in:
Seth Alves 2015-11-03 16:07:28 -08:00
parent f60b1d9bd7
commit 60824a1fb0
9 changed files with 178 additions and 133 deletions

View file

@ -1128,77 +1128,75 @@ void Application::paintGL() {
{ {
PerformanceTimer perfTimer("CameraUpdates"); PerformanceTimer perfTimer("CameraUpdates");
auto myAvatar = getMyAvatar(); auto myAvatar = getMyAvatar();
myAvatar->withReadLock([&] {
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { myAvatar->startCapture();
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN); Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
!(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN)); cameraMenuChanged();
cameraMenuChanged(); }
}
// The render mode is default or mirror if the camera is in mirror mode, assigned further below // The render mode is default or mirror if the camera is in mirror mode, assigned further below
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
// Always use the default eye position, not the actual head eye position. // Always use the default eye position, not the actual head eye position.
// Using the latter will cause the camera to wobble with idle animations, // Using the latter will cause the camera to wobble with idle animations,
// or with changes from the face tracker // or with changes from the face tracker
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
if (isHMDMode()) { if (isHMDMode()) {
mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
_myCamera.setPosition(extractTranslation(camMat)); _myCamera.setPosition(extractTranslation(camMat));
_myCamera.setRotation(glm::quat_cast(camMat)); _myCamera.setRotation(glm::quat_cast(camMat));
} else { } else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()); _myCamera.setPosition(myAvatar->getDefaultEyePosition());
_myCamera.setRotation(myAvatar->getHead()->getCameraOrientation()); _myCamera.setRotation(myAvatar->getHead()->getCameraOrientation());
}
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
if (isHMDMode()) {
auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
_myCamera.setRotation(glm::normalize(glm::quat_cast(hmdWorldMat)));
auto worldBoomOffset = myAvatar->getOrientation() *
(myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f));
_myCamera.setPosition(extractTranslation(hmdWorldMat) + worldBoomOffset);
} else {
_myCamera.setRotation(myAvatar->getHead()->getOrientation());
if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ _myCamera.getRotation() *
(myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
} else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getOrientation() *
(myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
}
}
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
+ (myAvatar->getOrientation() *
glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset);
} else {
_myCamera.setRotation(myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
} }
// Update camera position } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
if (!isHMDMode()) { if (isHMDMode()) {
_myCamera.update(1.0f / _fps); auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
_myCamera.setRotation(glm::normalize(glm::quat_cast(hmdWorldMat)));
auto worldBoomOffset = myAvatar->getOrientation() * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f));
_myCamera.setPosition(extractTranslation(hmdWorldMat) + worldBoomOffset);
} else {
_myCamera.setRotation(myAvatar->getHead()->getOrientation());
if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ _myCamera.getRotation()
* (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
} else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getOrientation()
* (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
}
} }
}); } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset);
} else {
_myCamera.setRotation(myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
}
// Update camera position
if (!isHMDMode()) {
_myCamera.update(1.0f / _fps);
}
myAvatar->endCapture();
} }
// Primary rendering pass // Primary rendering pass
@ -3454,9 +3452,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
// FIXME: This preRender call is temporary until we create a separate render::scene for the mirror rendering. // FIXME: This preRender call is temporary until we create a separate render::scene for the mirror rendering.
// Then we can move this logic into the Avatar::simulate call. // Then we can move this logic into the Avatar::simulate call.
auto myAvatar = getMyAvatar(); auto myAvatar = getMyAvatar();
myAvatar->withReadLock([&] { myAvatar->startRender();
myAvatar->preRender(renderArgs); myAvatar->preRender(renderArgs);
}); myAvatar->endRender();
activeRenderingThread = QThread::currentThread(); activeRenderingThread = QThread::currentThread();
@ -3570,9 +3568,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
_renderEngine->setRenderContext(renderContext); _renderEngine->setRenderContext(renderContext);
// Before the deferred pass, let's try to use the render engine // Before the deferred pass, let's try to use the render engine
myAvatar->withReadLock([&] { myAvatar->startRenderRun();
_renderEngine->run(); _renderEngine->run();
}); myAvatar->endRenderRun();
auto engineRC = _renderEngine->getRenderContext(); auto engineRC = _renderEngine->getRenderContext();
sceneInterface->setEngineFeedOpaqueItems(engineRC->_numFeedOpaqueItems); sceneInterface->setEngineFeedOpaqueItems(engineRC->_numFeedOpaqueItems);

View file

@ -377,6 +377,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
} }
if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) {
endRender();
return; return;
} }
@ -535,6 +536,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
renderDisplayName(batch, frustum, textPosition); renderDisplayName(batch, frustum, textPosition);
} }
} }
endRender();
} }
glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
@ -1011,25 +1013,23 @@ void Avatar::setBillboard(const QByteArray& billboard) {
} }
int Avatar::parseDataFromBuffer(const QByteArray& buffer) { int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
int bytesRead; startUpdate();
if (!_initialized) {
// now that we have data for this Avatar we are go for init
init();
}
withWriteLock([&] { // change in position implies movement
if (!_initialized) { glm::vec3 oldPosition = getPosition();
// now that we have data for this Avatar we are go for init
init();
}
// change in position implies movement int bytesRead = AvatarData::parseDataFromBuffer(buffer);
glm::vec3 oldPosition = getPosition();
bytesRead = AvatarData::parseDataFromBuffer(buffer); const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD;
const float MOVE_DISTANCE_THRESHOLD = 0.001f; if (_moving && _motionState) {
_moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD; _motionState->addDirtyFlags(Simulation::DIRTY_POSITION);
if (_moving && _motionState) { }
_motionState->addDirtyFlags(Simulation::DIRTY_POSITION); endUpdate();
}
});
return bytesRead; return bytesRead;
} }

View file

@ -133,9 +133,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
QWriteLocker locker(&_hashLock); QWriteLocker locker(&_hashLock);
avatarIterator = _avatarHash.erase(avatarIterator); avatarIterator = _avatarHash.erase(avatarIterator);
} else { } else {
avatar->withWriteLock([&] { avatar->startUpdate();
avatar->simulate(deltaTime); avatar->simulate(deltaTime);
}); avatar->endUpdate();
++avatarIterator; ++avatarIterator;
} }
} }
@ -154,16 +154,16 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
render::PendingChanges pendingChanges; render::PendingChanges pendingChanges;
while (fadingIterator != _avatarFades.end()) { while (fadingIterator != _avatarFades.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*fadingIterator); auto avatar = std::static_pointer_cast<Avatar>(*fadingIterator);
avatar->withWriteLock([&] { avatar->startUpdate();
avatar->setTargetScale(avatar->getAvatarScale() * SHRINK_RATE); avatar->setTargetScale(avatar->getAvatarScale() * SHRINK_RATE);
if (avatar->getTargetScale() < MIN_FADE_SCALE) { if (avatar->getTargetScale() < MIN_FADE_SCALE) {
avatar->removeFromScene(*fadingIterator, scene, pendingChanges); avatar->removeFromScene(*fadingIterator, scene, pendingChanges);
fadingIterator = _avatarFades.erase(fadingIterator); fadingIterator = _avatarFades.erase(fadingIterator);
} else { } else {
avatar->simulate(deltaTime); avatar->simulate(deltaTime);
++fadingIterator; ++fadingIterator;
} }
}); avatar->endUpdate();
} }
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
} }

View file

@ -56,11 +56,12 @@ bool AvatarUpdate::process() {
//gets current lookat data, removes missing avatars, etc. //gets current lookat data, removes missing avatars, etc.
manager->updateOtherAvatars(deltaSeconds); manager->updateOtherAvatars(deltaSeconds);
myAvatar->withWriteLock([&] { myAvatar->startUpdate();
qApp->updateMyAvatarLookAtPosition(); qApp->updateMyAvatarLookAtPosition();
// Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
manager->updateMyAvatar(deltaSeconds); manager->updateMyAvatar(deltaSeconds);
}); myAvatar->endUpdate();
if (!isThreaded()) { if (!isThreaded()) {
return true; return true;

View file

@ -111,25 +111,60 @@ void AvatarData::setBodyRoll(float bodyRoll) {
} }
void AvatarData::setPosition(const glm::vec3& position) { void AvatarData::setPosition(const glm::vec3& position) {
withWriteLock([&] { SpatiallyNestable::setPosition(position);
SpatiallyNestable::setPosition(position);
});
} }
void AvatarData::setOrientation(const glm::quat& orientation) { void AvatarData::setOrientation(const glm::quat& orientation) {
withWriteLock([&] { SpatiallyNestable::setOrientation(orientation);
SpatiallyNestable::setOrientation(orientation);
});
} }
// There are a number of possible strategies for this set of tools through endRender, below. // There are a number of possible strategies for this set of tools through endRender, below.
void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) {
withWriteLock([&] { avatarLock.lock();
SpatiallyNestable::setPosition(position); SpatiallyNestable::setPosition(position);
SpatiallyNestable::setOrientation(orientation); SpatiallyNestable::setOrientation(orientation);
}); avatarLock.unlock();
updateAttitude(); updateAttitude();
} }
void AvatarData::startCapture() {
avatarLock.lock();
assert(_nextAllowed);
_nextAllowed = false;
_nextPosition = getPosition();
_nextOrientation = getOrientation();
}
void AvatarData::endCapture() {
avatarLock.unlock();
}
void AvatarData::startUpdate() {
avatarLock.lock();
}
void AvatarData::endUpdate() {
avatarLock.unlock();
}
void AvatarData::startRenderRun() {
// I'd like to get rid of this and just (un)lock at (end-)startRender.
// But somehow that causes judder in rotations.
avatarLock.lock();
}
void AvatarData::endRenderRun() {
avatarLock.unlock();
}
void AvatarData::startRender() {
glm::vec3 pos = getPosition();
glm::quat rot = getOrientation();
setPosition(_nextPosition);
setOrientation(_nextOrientation);
updateAttitude();
_nextPosition = pos;
_nextOrientation = rot;
}
void AvatarData::endRender() {
setPosition(_nextPosition);
setOrientation(_nextOrientation);
updateAttitude();
_nextAllowed = true;
}
float AvatarData::getTargetScale() const { float AvatarData::getTargetScale() const {
return _targetScale; return _targetScale;

View file

@ -44,13 +44,13 @@ typedef unsigned long long quint64;
#include <QVariantMap> #include <QVariantMap>
#include <QVector> #include <QVector>
#include <QtScript/QScriptable> #include <QtScript/QScriptable>
#include <QReadWriteLock>
#include <NLPacket.h> #include <NLPacket.h>
#include <Node.h> #include <Node.h>
#include <RegisteredMetaTypes.h> #include <RegisteredMetaTypes.h>
#include <SimpleMovingAverage.h> #include <SimpleMovingAverage.h>
#include <SpatiallyNestable.h> #include <SpatiallyNestable.h>
#include <shared/ReadWriteLockable.h>
#include "AABox.h" #include "AABox.h"
#include "HandData.h" #include "HandData.h"
@ -136,7 +136,7 @@ class QDataStream;
class AttachmentData; class AttachmentData;
class JointData; class JointData;
class AvatarData : public QObject, public ReadWriteLockable, public SpatiallyNestable { class AvatarData : public QObject, public SpatiallyNestable {
Q_OBJECT Q_OBJECT
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
@ -200,6 +200,14 @@ public:
virtual void setOrientation(const glm::quat& orientation); virtual void setOrientation(const glm::quat& orientation);
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
void startCapture(); // start/end of the period in which the latest values are about to be captured for camera, etc.
void endCapture();
void startUpdate(); // start/end of update iteration
void endUpdate();
void startRender(); // start/end of rendering of this object
void startRenderRun(); // start/end of entire scene.
void endRenderRun();
void endRender();
virtual void updateAttitude() {} // Tell skeleton mesh about changes virtual void updateAttitude() {} // Tell skeleton mesh about changes
glm::quat getHeadOrientation() const { return _headData->getOrientation(); } glm::quat getHeadOrientation() const { return _headData->getOrientation(); }
@ -355,6 +363,10 @@ public slots:
protected: protected:
glm::vec3 _handPosition; glm::vec3 _handPosition;
glm::vec3 _nextPosition {};
glm::quat _nextOrientation {};
bool _nextAllowed {true};
// Body scale // Body scale
float _targetScale; float _targetScale;
@ -404,6 +416,8 @@ protected:
SimpleMovingAverage _averageBytesReceived; SimpleMovingAverage _averageBytesReceived;
QMutex avatarLock; // Name is redundant, but it aids searches.
private: private:
static QUrl _defaultFullAvatarModelUrl; static QUrl _defaultFullAvatarModelUrl;
// privatize the copy constructor and assignment operator so they cannot be called // privatize the copy constructor and assignment operator so they cannot be called

View file

@ -1795,9 +1795,6 @@ void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) {
} }
void EntityItem::setActionData(QByteArray actionData) { void EntityItem::setActionData(QByteArray actionData) {
if (_id == QUuid("32147a6e-976d-44ea-8c33-056c820b4dbd")) {
qDebug() << "EntityItem::setActionData to " << actionData.size() << "bytes.";
}
withWriteLock([&] { withWriteLock([&] {
setActionDataInternal(actionData); setActionDataInternal(actionData);
}); });
@ -1805,16 +1802,8 @@ void EntityItem::setActionData(QByteArray actionData) {
void EntityItem::setActionDataInternal(QByteArray actionData) { void EntityItem::setActionDataInternal(QByteArray actionData) {
if (_allActionsDataCache != actionData) { if (_allActionsDataCache != actionData) {
if (_id == QUuid("32147a6e-976d-44ea-8c33-056c820b4dbd")) {
qDebug() << "EntityItem::setActionDataInternal to " << actionData.size() << "bytes.";
}
_allActionsDataCache = actionData; _allActionsDataCache = actionData;
deserializeActionsInternal(); deserializeActionsInternal();
} else {
if (_id == QUuid("32147a6e-976d-44ea-8c33-056c820b4dbd")) {
qDebug() << "EntityItem::setActionDataInternal NOT setting to " << actionData.size() << "bytes.";
}
} }
checkWaitingToRemove(); checkWaitingToRemove();
} }

View file

@ -61,7 +61,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const {
_parent.reset(); _parent.reset();
} }
// we have a _parentID but no parent pointer, or our parent pointer is to the wrong thing // we have a _parentID but no parent pointer, or our parent pointer was to the wrong thing
QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>(); QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>();
_parent = parentFinder->find(_parentID); _parent = parentFinder->find(_parentID);
parent = _parent.lock(); parent = _parent.lock();
@ -69,6 +69,11 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const {
parent->beParentOfChild(thisPointer); parent->beParentOfChild(thisPointer);
_parentKnowsMe = true; _parentKnowsMe = true;
} }
if (parent || _parentID.isNull()) {
parentChanged();
}
return parent; return parent;
} }
@ -80,10 +85,11 @@ void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const {
_children.remove(newChild->getID()); _children.remove(newChild->getID());
} }
void SpatiallyNestable::setParentID(const QUuid& parentID) { void SpatiallyNestable::setParentID(const QUuid& parentID) {
_parentID = parentID; if (_parentID != parentID) {
_parentKnowsMe = false; _parentID = parentID;
_parentKnowsMe = false;
}
} }
glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position) { glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position) {

View file

@ -92,6 +92,8 @@ protected:
virtual void forgetChild(SpatiallyNestablePointer newChild) const; virtual void forgetChild(SpatiallyNestablePointer newChild) const;
mutable QHash<QUuid, SpatiallyNestableWeakPointer> _children; mutable QHash<QUuid, SpatiallyNestableWeakPointer> _children;
virtual void parentChanged() const {} // called when parent pointer is updated
private: private:
Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform. Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform.