2014-04-08 17 views
13

Ho impostato una scena in SceneKit e ho emesso un hit-test per selezionare un oggetto. Tuttavia, voglio essere in grado di spostare quell'elemento lungo un piano nella mia scena. Continuo a ricevere eventi di trascinamento del mouse, ma non so come trasformare quelle coordinate 2D in coordinate 3D nella scena.Come si trova il puntatore del mouse in una scena usando SceneKit?

Il mio caso è molto semplice. La fotocamera si trova a 0, 0, 50 e punta a 0, 0, 0. Voglio solo trascinare il mio oggetto lungo il piano z con un valore z di 0.

Il hit-test funziona come un charm, ma come faccio a tradurre il punto del mouse da un evento di trascinamento in una nuova posizione nella scena per l'oggetto 3D che sto trascinando?

risposta

20

Non è necessario utilizzare la geometria invisibile - Scene Kit può eseguire tutte le conversioni di coordinate necessarie senza dover colpire oggetti invisibili di prova. Fondamentalmente è necessario fare la stessa cosa che si desidera in a 2D drawing app per lo spostamento di un oggetto: trovare lo scostamento tra la posizione mouseDown: e la posizione dell'oggetto, quindi per ogni mouseMoved: aggiungere l'offset alla nuova posizione del mouse per impostare la nuova posizione dell'oggetto.

Ecco un approccio si potrebbe usare ...

  1. Hit-test la posizione iniziale di clic, come si sta già facendo. Questo ti dà un oggetto SCNHitTestResult che identifica il nodo che vuoi spostare, giusto?

  2. Verificare la proprietà worldCoordinates del risultato di tale risultato. Se il nodo che vuoi spostare è figlio di uno scenario della scena rootNode, questo è il vettore che desideri per trovare l'offset. (Altrimenti dovrai convertirlo nel sistema di coordinate del genitore del nodo che vuoi spostare - vedi convertPosition:toNode: o convertPosition:fromNode:.)

  3. Avrai bisogno di una profondità di riferimento per questo punto in modo che tu possa confronta mouseMoved: località ad esso. Usa projectPoint: per convertire il vettore che hai ottenuto nel passaggio 2 (un punto nella scena 3D) nello spazio dello schermo: questo ti dà un vettore 3D le cui coordinate xey sono un punto dello spazio dello schermo e la cui coordinata z ti dice la profondità di quel punto rispetto ai piani di ritaglio (0.0 si trova sul piano vicino, 1.0 si trova sul piano più lontano). Mantenere questa coordinata z per l'uso durante mouseMoved:.

  4. Sottrarre il position del nodo che si desidera spostare dal vettore posizione del mouse ottenuto nel passaggio 2. Ciò consente di ottenere l'offset del clic del mouse dalla posizione dell'oggetto. Tieni questo vettore - ne avrai bisogno fino al termine del trascinamento.

  5. Su mouseMoved:, costruire un nuovo vettore 3D dalle coordinate dello schermo della nuova posizione del mouse e il valore di profondità che hai nel passaggio 3. Quindi, convertire questo vettore in scena le coordinate utilizzando unprojectPoint: - questa è la posizione del mouse nel vostro lo spazio 3D della scena (equivalente a quello ottenuto dal test di successo, ma senza la necessità di "colpire" la geometria della scena).

  6. Aggiungi l'offset che hai ottenuto nel passaggio 3 nella nuova posizione che hai ottenuto nel passaggio 5: questo è il nuovo position per spostare il nodo su. (Nota: per il trascinamento in tempo reale, dovresti assicurarti che questo cambio di posizione non sia animato. Di default la durata dell'attuale SCNTransaction è zero, quindi non devi preoccuparti di questo a meno che non lo hai già cambiato .)

(Si tratta di una sorta di fuori della parte superiore della mia testa, così si dovrebbe probabilmente doppio controllare la documentazione e le intestazioni importanti. E si potrebbe essere in grado di semplificare questo un po 'con un po' di matematica.)

+1

Quella è una soluzione molto più pulito. Dovresti andare con quello. –

3

Come esperimento ho implementato la risposta utile dell'onorevole Bishop. Il trascinamento non funziona (l'oggetto - un pezzo di scacchi - salta fuori dallo schermo) a causa delle differenze nelle grandezze di coordinate tra il clic del mouse e il mondo 3D. Ho inserito le uscite log qui e là tra il codice.

Ho chiesto sui forum Apple se qualcuno conosceva la salsa segreta per omogeneizzare le coordinate ma non ha ottenuto una risposta decisiva. Una cosa, ho apportato alcune modifiche sperimentali al metodo di Bishop e i membri del forum mi hanno consigliato di tornare alla sua tecnica.

Nonostante i difetti del mio codice, ho pensato che qualcuno potesse trovarlo un utile punto di partenza. Sospetto che ci siano solo uno o due piccoli problemi con il codice.

Si noti che il log del mondo trasforma la matrice dell'oggetto (pezzo degli scacchi) non fa parte del processo ma un membro del forum Apple mi ha consigliato che la matrice spesso offre un utile 'controllo della sanità' - che effettivamente ha fatto.

- (NSPoint) 
viewPointForEvent: (NSEvent *) event_ 
{ 
    NSPoint windowPoint = [event_ locationInWindow]; 
    NSPoint viewPoint  = [self.view convertPoint: windowPoint 
              fromView: nil]; 
    return viewPoint; 
} 

- (SCNHitTestResult *) 
hitTestResultForEvent: (NSEvent *) event_ 
{ 
    NSPoint  viewPoint  = [self viewPointForEvent: event_]; 
    CGPoint  cgPoint  = CGPointMake (viewPoint.x, viewPoint.y); 
    NSArray * points  = [(SCNView *) self.view hitTest: cgPoint 
                options: @{}]; 
    return points.firstObject; 
} 

- (void) 
mouseDown: (NSEvent *) theEvent 
{ 
    SCNHitTestResult * result = [self hitTestResultForEvent: theEvent]; 

    SCNVector3 clickWorldCoordinates = result.worldCoordinates; 
    log output: clickWorldCoordinates x 208.124578, y -12827.223365, z 3163.659073 
    SCNVector3 screenCoordinates = [(SCNView *) self.view projectPoint: clickWorldCoordinates]; 
    log output: screenCoordinates x 245.128906, y 149.335938, z 0.985565 
    // save the z coordinate for use in mouseDragged 
    mouseDownClickOnObjectZCoordinate = screenCoordinates.z; 

    selectedPiece = result.node; // save selected piece for use in mouseDragged 

    SCNVector3 piecePosition = selectedPiece.position; 
    log output: piecePosition x -18.200000, y 6.483060, z 2.350000 

    offsetOfMouseClickFromPiece.x = clickWorldCoordinates.x - piecePosition.x; 
    offsetOfMouseClickFromPiece.y = clickWorldCoordinates.y - piecePosition.y; 
    offsetOfMouseClickFromPiece.z = clickWorldCoordinates.z - piecePosition.z; 
    log output: offsetOfMouseClickFromPiece x 226.324578, y -12833.706425, z 3161.309073 
} 

- (void) 
mouseDragged: (NSEvent *) theEvent; 
{ 
    NSPoint viewClickPoint  = [self viewPointForEvent: theEvent]; 

    SCNVector3 clickCoordinates; 
    clickCoordinates.x = viewClickPoint.x; 
    clickCoordinates.y = viewClickPoint.y; 
    clickCoordinates.z = mouseDownClickOnObjectZCoordinate; 
    log output: clickCoordinates x 246.128906, y 0.000000, z 0.985565 

    log output: pieceWorldTransform: 
     m11 = 242.15889219510001, m12 = -0.000045609300002524833, m13 = -0.00000721691076126, m14 = 0, 
     m21 = 0.0000072168760805499971, m22 = -0.000039452697396149999, m23 = 242.15890446329999, m24 = 0, 
     m31 = -0.000045609300002524833, m32 = -242.15889219510001, m33 = -0.000039452676995750002, m34 = 0, 
     m41 = -4268.2349924762348, m42 = -12724.050221935429, m43 = 4852.6652710104272, m44 = 1) 

    SCNVector3 newPiecePosition; 
    newPiecePosition.x = offsetOfMouseClickFromPiece.x + clickCoordinates.x; 
    newPiecePosition.y = offsetOfMouseClickFromPiece.y + clickCoordinates.y; 
    newPiecePosition.z = offsetOfMouseClickFromPiece.z + clickCoordinates.z; 
    log output: newPiecePosition x 472.453484, y -12833.706425, z 3162.294639 

    selectedPiece.position = newPiecePosition; 
} 
1

Ho usato il codice scritto da Steve e con poche modifiche ha funzionato per me.

Su mouseDown, salgo clickWorldCoordinates su una proprietà denominata startClickWorldCoordinates.

Su mouseDragged ho calcolare la posizione selectedPiece in questo modo:

SCNVector3 worldClickCoordinate = [(SCNView *) self.view unprojectPoint:clickCoordinates.x]; 

newPiecePosition.x = selectedPiece.position.x + worldClickCoordinate.x - startClickWorldCoordinates.x; 
newPiecePosition.y = selectedPiece.position.y + worldClickCoordinate.y - startClickWorldCoordinates.y; 
newPiecePosition.z = selectedPiece.position.z + worldClickCoordinate.z - startClickWorldCoordinates.z; 

selectedPiece.position = newPiecePosition; 

startClickWorldCoordinates = worldClickCoordinate;