Come creare un oggetto SCNSkinner di SceneKit nel codice?


89

Ho un'app Swift che utilizza SceneKit per iOS 8. Carico una scena da un file .dae che contiene una mesh controllata da uno scheletro. In fase di esecuzione, ho bisogno di modificare le coordinate della trama. L'utilizzo di una trasformazione non è un'opzione: devo calcolare un UV diverso e completamente nuovo per ogni vertice nella mesh.

So che la geometria è immutabile in SceneKit e ho letto che l'approccio suggerito è quello di creare una copia manualmente. Sto provando a farlo, ma finisco sempre per bloccarmi quando provo a ricreare il SCNSkinnercodice in. Lo schianto è un EXC_BAD_ACCESSinterno C3DSourceAccessorGetMutableValuePtrAtIndex. Sfortunatamente, non esiste un codice sorgente per questo, quindi non sono sicuro del motivo per cui si blocca esattamente. L'ho ristretto SCNSkinnerall'oggetto collegato al nodo mesh. Se non lo imposto, non ottengo un arresto anomalo e le cose sembrano funzionare.

EDIT: ecco uno stack di chiamate più completo del crash:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Non ho trovato alcuna documentazione o codice di esempio su come creare un SCNSkinneroggetto manualmente. Dato che lo sto solo creando sulla base di una mesh precedentemente funzionante, non dovrebbe essere troppo difficile. Sto creando il SCNSkinnersecondo la documentazione di Swift, passando tutte le cose corrette nell'init. Tuttavia, c'è una proprietà scheletro in SCNSkinnerche non sono sicuro di come impostare. L'ho impostato sullo scheletro che era sull'originale SCNSkinnerdella mesh che sto copiando, che penso dovrebbe funzionare ... ma non è così. Quando si imposta la proprietà dello scheletro, non sembra essere assegnata. Controllandolo subito dopo l'assegnazione mostra che è ancora nullo. Come test, ho provato a impostare la proprietà dello scheletro della mesh originale su qualcos'altro e anche dopo l'assegnazione è stata lasciata inalterata.

Qualcuno può far luce su ciò che sta accadendo? O come creare e impostare correttamente un SCNSkinneroggetto manualmente?

Ecco il codice che sto usando per clonare manualmente una mesh e sostituirla con una nuova (non ho modificato nessuno dei dati di origine qui - sto semplicemente cercando di assicurarmi di poter creare una copia a questo punto) :

// This is at the start of the app, just so you can see how the scene is set up.
// I add the .dae contents into its own node in the scene. This seems to be the
// standard way to put multiple .dae models into the same scene. This doesn't seem to
// have any impact on the problem I'm having -- I've tried without this indirection
// and the same problem exists.
let scene = SCNScene()

let modelNode = SCNNode()
modelNode.name = "ModelNode"

scene.rootNode.addChildNode(modelNode)

let modelScene = SCNScene(named: "model.dae")

if modelScene != nil {
    if let childNodes = modelScene?.rootNode.childNodes {
        for childNode in childNodes {
            modelNode.addChildNode(childNode as SCNNode)
        }
    }
}


// This happens later in the app after a tap from the user.

let modelNode = scnView.scene!.rootNode.childNodeWithName("ModelNode", recursively: true)

let modelMesh = modelNode?.childNodeWithName("MeshName", recursively: true)

let verts = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticVertex)
let normals = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticNormal)
let texcoords = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticTexcoord)
let boneWeights = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticBoneWeights)
let boneIndices = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticBoneIndices)
let geometry = modelMesh?.geometry!.geometryElementAtIndex(0)

// Note: the vertex and normal data is shared.
let vertsData = NSData(data: verts![0].data)
let texcoordsData = NSData(data: texcoords![0].data)
let boneWeightsData = NSData(data: boneWeights![0].data)
let boneIndicesData = NSData(data: boneIndices![0].data)
let geometryData = NSData(data: geometry!.data!)

let newVerts = SCNGeometrySource(data: vertsData, semantic: SCNGeometrySourceSemanticVertex, vectorCount: verts![0].vectorCount, floatComponents: verts![0].floatComponents, componentsPerVector: verts![0].componentsPerVector, bytesPerComponent: verts![0].bytesPerComponent, dataOffset: verts![0].dataOffset, dataStride: verts![0].dataStride)

let newNormals = SCNGeometrySource(data: vertsData, semantic: SCNGeometrySourceSemanticNormal, vectorCount: normals![0].vectorCount, floatComponents: normals![0].floatComponents, componentsPerVector: normals![0].componentsPerVector, bytesPerComponent: normals![0].bytesPerComponent, dataOffset: normals![0].dataOffset, dataStride: normals![0].dataStride)

let newTexcoords = SCNGeometrySource(data: texcoordsData, semantic: SCNGeometrySourceSemanticTexcoord, vectorCount: texcoords![0].vectorCount, floatComponents: texcoords![0].floatComponents, componentsPerVector: texcoords![0].componentsPerVector, bytesPerComponent: texcoords![0].bytesPerComponent, dataOffset: texcoords![0].dataOffset, dataStride: texcoords![0].dataStride)

let newBoneWeights = SCNGeometrySource(data: boneWeightsData, semantic: SCNGeometrySourceSemanticBoneWeights, vectorCount: boneWeights![0].vectorCount, floatComponents: boneWeights![0].floatComponents, componentsPerVector: boneWeights![0].componentsPerVector, bytesPerComponent: boneWeights![0].bytesPerComponent, dataOffset: boneWeights![0].dataOffset, dataStride: boneWeights![0].dataStride)

let newBoneIndices = SCNGeometrySource(data: boneIndicesData, semantic: SCNGeometrySourceSemanticBoneIndices, vectorCount: boneIndices![0].vectorCount, floatComponents: boneIndices![0].floatComponents, componentsPerVector: boneIndices![0].componentsPerVector, bytesPerComponent: boneIndices![0].bytesPerComponent, dataOffset: boneIndices![0].dataOffset, dataStride: boneIndices![0].dataStride)

let newGeometry = SCNGeometryElement(data: geometryData, primitiveType: geometry!.primitiveType, primitiveCount: geometry!.primitiveCount, bytesPerIndex: geometry!.bytesPerIndex)

let newMeshGeometry = SCNGeometry(sources: [newVerts, newNormals, newTexcoords, newBoneWeights, newBoneIndices], elements: [newGeometry])

newMeshGeometry.firstMaterial = modelMesh?.geometry!.firstMaterial

let newModelMesh = SCNNode(geometry: newMeshGeometry)

let bones = modelMesh?.skinner?.bones
let boneInverseBindTransforms = modelMesh?.skinner?.boneInverseBindTransforms
let skeleton = modelMesh!.skinner!.skeleton!
let baseGeometryBindTransform = modelMesh!.skinner!.baseGeometryBindTransform

newModelMesh.skinner = SCNSkinner(baseGeometry: newMeshGeometry, bones: bones, boneInverseBindTransforms: boneInverseBindTransforms, boneWeights: newBoneWeights, boneIndices: newBoneIndices)

newModelMesh.skinner?.baseGeometryBindTransform = baseGeometryBindTransform

// Before this assignment, newModelMesh.skinner?.skeleton is nil.
newModelMesh.skinner?.skeleton = skeleton
// After, it is still nil... however, skeleton itself is completely valid.

modelMesh?.removeFromParentNode()

newModelMesh.name = "MeshName"

let meshParentNode = modelNode?.childNodeWithName("MeshParentNode", recursively: true)

meshParentNode?.addChildNode(newModelMesh)

Il mio piano attuale per una soluzione alternativa è semplicemente calcolare le nuove coordinate della trama, sovrascrivere il file .dae, svuotare la scena e ricaricare il file .dae. Questo dovrebbe essere completamente inutile, ma dalla documentazione di Apple, non riesco a capire quale sia il problema. O sto facendo qualcosa in modo errato, tralasciando alcuni passaggi cruciali, oppure SceneKit utilizza alcune API private per configurare correttamente SCNSkinner durante il caricamento dal file .dae. È interessante notare che la proprietà scheletro dopo che SceneKit è stato caricato da .dae è un nodo senza figli , tuttavia l'animazione scheletrica funziona chiaramente.
jcr

La sovrascrittura del file .dae non è un'opzione, sfortunatamente. Esiste un passaggio di ottimizzazione e compressione che si verifica quando Xcode copia il file .dae sul dispositivo. Il salvataggio di un nuovo file .dae sul dispositivo all'interno dell'app non funziona perché SceneKit su iOS non caricherà un file .dae che non è stato eseguito tramite lo script di Xcode.
jcr

Hai ottenuto questo lavoro?
μολὼν.λαβέ

No, ho finito per scrivere il mio motore e da allora non ho più usato SceneKit.
jcr

Degno di nota. Sei andato con l'opengl o il metallo?
μολὼν.λαβέ

Risposte:


1

Questi tre metodi possono aiutarti a trovare la soluzione:

  1. SCNNode *hero = [SCNScene sceneNamed:@"Hero"].rootNode;
    SCNNode *hat = [SCNScene sceneNamed:@"FancyFedora"].rootNode;
    hat.skinner.skeleton = hero.skinner.skeleton;
  2. [Export ("initWithFrame:")]
    public UIView (System.Drawing.RectangleF frame) : base (NSObjectFlag.Empty)
    {
    // Invoke the init method now.
        var initWithFrame = new Selector ("initWithFrame:").Handle;
        if (IsDirectBinding)
            Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSend_RectangleF (this.Handle, initWithFrame, frame);
        else
            Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_RectangleF (this.SuperHandle, initWithFrame, frame);
    }
  3. Vedi anche questo link .


0

Non so specificamente cosa causi l'arresto anomalo del codice, ma ecco un modo per generare una mesh, ossa e scuoiare quella mesh, tutto dal codice. Swift4 e iOS 12.

Nell'esempio, c'è una maglia che rappresenta la concatenazione di due cilindri, con uno dei cilindri che si dirama con un angolo di 45 gradi, in questo modo:

 \
 |

I cilindri sono solo triangoli estrusi, cioè radialSegmentCount = 3.(Nota che ci sono 12 vertici, non 9, poiché i due cilindri non sono realmente congiunti. I triangoli sono ordinati in questo modo:

      v5
      ^
v3 /__|__\ v1
   |  |  |
   |  v4 |
v2 |/___\| v0

Ci sono 3 ossa, corrispondenti alle teste e ai piedi dei cilindri, dove l'osso medio corrisponde alla testa del cilindro inferiore e contemporaneamente al piede del cilindro superiore. Così, per esempio, i vertici v0, v2e v4corrispondono a bone0; v1, v3, v5Conforme a bone1, e così via. Questo spiega perché boneIndices(vedi sotto) ha il valore che ha.

Le posizioni di riposo delle ossa corrispondono alle posizioni di riposo dei cilindri nella geometria ( bone2spuntano con un angolo di 45 gradi da bone1, proprio come la geometria del cilindro).

Con quello come contesto, il codice seguente crea tutto il necessario per skin la geometria:

let vertices = [float3(0.17841241, 0.0, 0.0), float3(0.17841241, 1.0, 0.0), float3(-0.089206174, 0.0, 0.1545097), float3(-0.089206174, 1.0, 0.1545097), float3(-0.089206256, 0.0, -0.15450965), float3(-0.089206256, 1.0, -0.15450965), float3(0.12615661, 1.1261566, 0.0), float3(-0.58094996, 1.8332633, 0.0), float3(-0.063078284, 0.9369217, 0.1545097), float3(-0.7701849, 1.6440284, 0.1545097), float3(-0.063078344, 0.93692166, -0.15450965), float3(-0.77018493, 1.6440284, -0.15450965)]
let indices: [UInt8] = [0, 1, 2, 3, 4, 5, 0, 1, 1, 6, 6, 7, 8, 9, 10, 11, 6, 7]
let geometrySource = SCNGeometrySource(vertices: vertices.map { SCNVector3($0) })
let geometryElement = SCNGeometryElement(indices: indices, primitiveType: .triangleStrip)
let geometry = SCNGeometry(sources: [geometrySource], elements: [geometryElement])

let bone0 = SCNNode()
bone0.simdPosition = float3(0,0,0)
let bone1 = SCNNode()
bone1.simdPosition = float3(0,1,0)
let bone2 = SCNNode()
bone2.simdPosition = float3(0,1,0) + normalize(float3(-1,1,0))
let bones = [bone0, bone1, bone2]

let boneInverseBindTransforms: [NSValue]? = bones.map { NSValue(scnMatrix4: SCNMatrix4Invert($0.transform)) }
var boneWeights: [Float] = vertices.map { _ in 1.0 }
var boneIndices: [UInt8] = [
    0, 1, 0, 1, 0, 1,
    1, 2, 1, 2, 1, 2,
]

let boneWeightsData = Data(bytesNoCopy: &boneWeights, count: boneWeights.count * MemoryLayout<Float>.size, deallocator: .none)
let boneIndicesData = Data(bytesNoCopy: &boneIndices, count: boneWeights.count * MemoryLayout<UInt8>.size, deallocator: .none)

let boneWeightsGeometrySource = SCNGeometrySource(data: boneWeightsData, semantic: .boneWeights, vectorCount: boneWeights.count, usesFloatComponents: true, componentsPerVector: 1, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: MemoryLayout<Float>.size)
let boneIndicesGeometrySource = SCNGeometrySource(data: boneIndicesData, semantic: .boneIndices, vectorCount: boneIndices.count, usesFloatComponents: false, componentsPerVector: 1, bytesPerComponent: MemoryLayout<UInt8>.size, dataOffset: 0, dataStride: MemoryLayout<UInt8>.size)

let skinner = SCNSkinner(baseGeometry: geometry, bones: bones, boneInverseBindTransforms: boneInverseBindTransforms, boneWeights: boneWeightsGeometrySource, boneIndices: boneIndicesGeometrySource)

let node = SCNNode(geometry: geometry)
node.skinner = skinner

Nota: nella maggior parte dei casi, dovresti usare UInt16not UInt8.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.