2014-11-15 4 views
3

Я пытаюсь применить Torque к узлу в моей сцене. Документации говорится:SceneKit applyTorque

Каждый компонент вектора крутящего момента относится к вращению вокруг соответствующей оси в локальной системе координат SCNNode объекта, содержащего тело физики. Например, применение крутящего момента {0.0, 0.0, 1.0} приводит к тому, что узел вращается против часовой стрелки вокруг своей оси .

Однако в моих тестах кажется, что физическая анимация не влияет на фактическое положение объекта. Поэтому ось остается статической (хотя фактический узел, очевидно, перемещается). Это приводит к тому, что крутящий момент всегда применяется с одного направления (везде, где оси z были при запуске сцены).

Я хотел бы иметь возможность применять крутящий момент так, чтобы он всегда был постоянным по отношению к объекту (например, чтобы заставить узел вращаться против часовой стрелки вокруг оси z представления узла nodeNode, а не узел позиции имел (имеет?), Когда сцена была инициирована)

ответ

1

SceneKit использует две версию каждого узла: модели узла определяет статическое поведение и представления узла, что на самом деле участвует в динамическом поведении и использовать на экране. Это разделение отражает то, что используется в Core Animation, и включает такие функции, как неявная анимация (где вы можете делать такие вещи, как set node.position, и обновлять его до нового значения, без других частей вашего кода, которые запрашивают node.position, чтобы работать с промежуточными значениями во время анимация).

Физика работает на узле представления, но в некоторых случаях - как этот - принимает входные данные в пространстве сцены.

Однако единственное отличие между узлом презентации и сценой заключается в координатных пространствах, поэтому все, что вам нужно сделать, - это преобразовать вектор из пространства презентации в пространство сцены. (Корневой узел сцены не должен трансформироваться физикой, действиями или анимациями при свете, поэтому нет никакой практической разницы между пространством модели-сцены и пространством презентации-сцены.) Для этого используйте один из методов преобразования координат SceneKit обеспечивает, например, convertPosition:fromNode:.

Вот Свифт площадка, которая иллюстрирует дилемму:

import Cocoa 
import SceneKit 
import XCPlayground 

// Set up a scene for our tests 
let scene = SCNScene() 
let view = SCNView(frame: NSRect(x: 0, y: 0, width: 500, height: 500)) 
view.autoenablesDefaultLighting = true 
view.scene = scene 
let cameraNode = SCNNode() 
cameraNode.camera = SCNCamera() 
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5) 
scene.rootNode.addChildNode(cameraNode) 
XCPShowView("view", view) 

// Make a pyramid to test on 
let node = SCNNode(geometry: SCNPyramid(width: 1, height: 1, length: 1)) 
scene.rootNode.addChildNode(node) 
node.physicsBody = SCNPhysicsBody.dynamicBody() 
scene.physicsWorld.gravity = SCNVector3Zero // Don't fall off screen 

// Rotate around the axis that looks into the screen 
node.physicsBody?.applyTorque(SCNVector4(x: 0, y: 0, z: 1, w: 0.1), impulse: true) 

// Wait a bit, then try to rotate around the y-axis 
node.runAction(SCNAction.waitForDuration(10), completionHandler: { 
    var axis = SCNVector3(x: 0, y: 1, z: 0) 
    node.physicsBody?.applyTorque(SCNVector4(x: axis.x, y: axis.y, z: axis.z, w: 1), impulse: true) 
}) 

Второй поворот эффективно закручивает пирамиды по всем экрана оси у, а не у оси пирамиды - тот, который идет через вершина пирамиды. Как вы заметили, он вращается вокруг того, что было осью y пирамиды до первого вращения; т. е. ось y сцены (которая не зависит от физики), а не у узла представления (который вращается по физике).

Чтобы исправить это, вставьте следующую строку (после того, как тот, который начинается с var axis):

axis = scene.rootNode.convertPosition(axis, fromNode: node.presentationNode()) 

Вызов convertPosition:fromNode: говорит «дайте мне вектор в сцене координатного пространства, что эквивалентно этому на презентации -node space ". Когда вы применяете крутящий момент вокруг преобразованной оси, он эффективно преобразуется обратно в пространство узла презентации, чтобы имитировать физику, поэтому вы видите, что она вращается вокруг нужной оси.

Обновление: Если некоторые координаты не совпадают, но конечный результат почти такой же.

+0

большое спасибо за ответ. К сожалению, для меня это не работает. Я подозреваю, что это потому, что мои объекты не находятся в центре моей сцены и не соответствуют миру. У меня есть решение, которое работает (опубликовано), но я не думаю, что оно красивое, точное или оптимизированное. – nekiTamoTip

+0

Это работало как шарм для меня, спасибо! (Если бы обновить некоторый код до Swift 2, но не biggie.) – BFeher

1

К сожалению, решение, предоставляемое rickster не работает для меня :(

Попытки решить эту головоломку, я создал (то, что я считаю, чтобы быть) очень некондиционным решением (скорее доказательства концепции). Это связано с созданием (нулевыми) объектами на оси, которую я пытаюсь найти, затем я использую их положение, чтобы найти вектор, выровненный по осям.

Поскольку у меня довольно сложная сцена, я загружаю ее из файла COLLADA В этом файле я смоделировал простой координатный штатив: три ортогональных цилиндра с конусами сверху (облегчает визуализацию происходящего).

Затем я сдерживаю этот объект треноги на объект, к которому я пытаюсь применить крутящий момент. Таким образом, у меня есть объекты, которые позволяют мне извлекать две точки на осях презентацииNode объекта, к которому я пытаюсь применить крутящий момент. Затем я смогу использовать эти две точки, чтобы определить вектор для применения крутящего момента.

// calculate orientation vector in the most unimaginative way possible 

// retrieve axis tripod objects. We will be using these as guide objects. 
// The tripod is constructed as a cylinder called "Xaxis" with a cone at the top. 
// All loaded from an external COLLADA file. 

SCNNode *XaxisRoot = [scene.rootNode childNodeWithName:@"XAxis" recursively:YES]; 
SCNNode *XaxisTip = [XaxisRoot childNodeWithName:@"Cone" recursively:NO]; 


// To devise the vector we will need two points. One is the root of our tripod, 
// the other is at the tip. First, we get their positions. As they are constrained 
// to the _rotatingNode, presentationNode.position is always the same .position 
// because presentationNode returns position in relation to the parent node. 

SCNVector3 XaxisRootPos = XaxisRoot.position; 
SCNVector3 XaxisTipPos = XaxisTip.position; 


// We then convert these two points into _rotatingNode coordinate space. This is 
// the coordinate space applyTorque seems to be using. 

XaxisRootPos = [_rotatingNode convertPosition:XaxisRootPos fromNode:_rotatingNode.presentationNode]; 
XaxisTipPos = [_rotatingNode convertPosition:XaxisTipPos fromNode:_rotatingNode.presentationNode]; 

// Now, we have two *points* in _rotatingNode coordinate space. One is at the center 
// of our _rotatingNode, the other is somewhere along it's Xaxis. Subtracting them 
// will give us the *vector* aligned to the x axis of our _rotatingNode 

GLKVector3 rawXRotationAxes = GLKVector3Subtract(SCNVector3ToGLKVector3(XaxisRootPos), SCNVector3ToGLKVector3(XaxisTipPos)); 

// we now normalise this vector 
GLKVector3 normalisedXRotationAxes = GLKVector3Normalize(rawXRotationAxes); 

//finally we are able to apply toque reliably 
[_rotatingNode.physicsBody applyTorque:SCNVector4Make(normalisedXRotationAxis.x,normalisedXRotationAxis.y,normalisedXRotationAxis.z, 500) impulse:YES]; 

Как вы можете видеть, я совершенно неопытным в SceneKit, но даже я вижу, что гораздо проще/оптимизированное решение делает выходы, но я не могу найти его :(

0

Недавно я имел эта же проблема, как преобразовать крутящий момент из локального пространства объекта в мировое пространство, требуемый методом applyTorque. Проблема с использованием методов узла convertPosition:toNode и fromNode s заключается в том, что они также применяют перевод узла к крутящий момент, так что это будет работать только тогда, когда узел равен 0,0,0. Что эти методы делают, так это рассматривать SCNVector3, как если бы это vec4 с aw компонентом 1.0. Мы просто хотим применить вращение, другими словами, мы хотим, чтобы вес компонента vec4 равным 0. В отличие от SceneKit, GLKit дает нам 2 варианта для того, как мы хотим, чтобы наши vec3s быть умножены:

GLKMatrix4MultiplyVector3 где

входной вектор обрабатывается как 4-компонентный вектор с w-компонентой 0.0.

и GLKMatrix4MultiplyVector3WithTranslation, где

Входной вектор обрабатывают, как это было 4-компонентный вектор с ш-составляющей 1,0.

То, что мы хотим здесь, это первое, а не ротация, а не перевод.

Итак, мы можем совершить кругосветное путешествие до GLKit. Для преобразования, например, локальную ось х (1,0,0), например, поворот, шаг к глобальной оси, необходимой для приложения крутящего момента, будет выглядеть следующим образом:

let local = GLKMatrix4MultiplyVector3(SCNMatrix4ToGLKMatrix4(node.presentationNode.worldTransform), GLKVector3(v: (1,0,0))) 
    node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false) 

Однако более Swiftian подход будет для добавления оператора * для mat4 * vec3, который рассматривает vec3 как vec4 с компонентом 0,0 w.Как это:

func * (left: SCNMatrix4, right: SCNVector3) -> SCNVector3 { //multiply mat4 by vec3 as if w is 0.0 
    return SCNVector3(
     left.m11 * right.x + left.m21 * right.y + left.m31 * right.z, 
     left.m12 * right.x + left.m22 * right.y + left.m32 * right.z, 
     left.m13 * right.x + left.m23 * right.y + left.m33 * right.z 
    ) 
} 

Хотя этот оператор делает предположение о том, как мы хотим, чтобы наши vec3s быть умножены, мои рассуждения в том, что, как convertPosition методы уже рассматривать ш как 1, было бы излишним иметь * оператора что тоже сделал это.

Вы также можете добавить mat4 * SCNVector4 оператор, который позволит пользователю выбрать в явной или не хотят они ж быть 0 или 1.

Таким образом, вместо того, чтобы обе стороны от SceneKit к GLKit, мы можем просто написать:

let local = node.presentationNode.worldTransform * SCNVector3(1,0,0) 
    node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false) 

Вы можете использовать этот метод, чтобы применить вращение по нескольким осям с одной applyTorque вызова. Так скажите, если у вас есть вход для ввода, где вы хотите, чтобы x на палке был рысканием (локальная ось yUp), а y на палке - шаг (локальная ось x), но с типом полета-sim «вниз, чтобы отступить/вверх ", то вы можете установить его на SCNVector3(input.y, -input.x, 0)