Comment créer un objet SceneKit SCNSkinner dans le code?


89

J'ai une application Swift utilisant SceneKit pour iOS 8. Je charge une scène à partir d'un fichier .dae contenant un maillage contrôlé par un squelette. Au moment de l'exécution, je dois modifier les coordonnées de la texture. L'utilisation d'une transformation n'est pas une option - je dois calculer une UV différente et complètement nouvelle pour chaque sommet du maillage.

Je sais que la géométrie est immuable dans SceneKit, et j'ai lu que l'approche suggérée est de faire une copie manuellement. J'essaie de le faire, mais je finis toujours par planter en essayant de recréer le SCNSkinnercode in. Le crash est un EXC_BAD_ACCESSintérieur C3DSourceAccessorGetMutableValuePtrAtIndex. Malheureusement, il n'y a pas de code source pour cela, donc je ne sais pas pourquoi exactement cela plante. Je l'ai réduit à l' SCNSkinnerobjet attaché au nœud de maillage. Si je ne règle pas cela, je ne reçois pas de plantage et les choses semblent fonctionner.

EDIT: Voici une pile d'appels plus complète du crash:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Je n'ai trouvé aucune documentation ou exemple de code sur la création SCNSkinnermanuelle d' un objet. Puisque je le crée simplement sur la base d'un maillage précédemment fonctionnel, cela ne devrait pas être trop difficile. Je crée le SCNSkinnerconformément à la documentation Swift, en passant toutes les choses correctes dans le fichier init. Cependant, il existe une propriété squelette dans le SCNSkinnerque je ne sais pas comment définir. Je l'ai défini sur le squelette qui était sur l'original SCNSkinnerdu maillage que je copie, ce qui, je pense, devrait fonctionner ... mais ce n'est pas le cas. Lors de la définition de la propriété squelette, elle ne semble pas être affectée. Le vérifier immédiatement après l'affectation montre qu'il est toujours nul. À titre de test, j'ai essayé de définir la propriété squelette du maillage d'origine sur autre chose, et après l'affectation, elle a également été laissée intacte.

Quelqu'un peut-il faire la lumière sur ce qui se passe? Ou comment créer et configurer correctement un SCNSkinnerobjet manuellement?

Voici le code que j'utilise pour cloner manuellement un maillage et le remplacer par un nouveau (je n'ai modifié aucune des données source ici - j'essaie simplement de m'assurer de pouvoir créer une copie à ce stade) :

// 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)

Mon plan actuel pour une solution de contournement consiste simplement à calculer les nouvelles coordonnées de texture, à écraser le fichier .dae, à vider la scène et à recharger le fichier .dae. Cela devrait être complètement inutile, mais d'après la documentation d'Apple, je ne peux pas comprendre quel est le problème. Soit je fais quelque chose de manière incorrecte, en omettant une étape cruciale, soit SceneKit utilise des API privées pour configurer correctement SCNSkinner lors du chargement à partir du fichier .dae. Il est intéressant de noter que la propriété squelette après le chargement de SceneKit à partir du .dae est un nœud sans enfant , mais l'animation squelettique fonctionne clairement.
jcr

L'écrasement du fichier .dae n'est malheureusement pas une option. Une étape d'optimisation et de compression se produit lorsque Xcode copie votre fichier .dae sur l'appareil. L'enregistrement d'un nouveau fichier .dae sur l'appareil dans l'application ne fonctionne pas car SceneKit sur iOS ne chargera pas un fichier .dae qui n'a pas été exécuté via le script de Xcode.
jcr

Avez-vous obtenu ce travail?
μολὼν.λαβέ

Non, j'ai fini par écrire mon propre moteur et je n'ai plus utilisé SceneKit depuis.
jcr

Impressionnant. Avez-vous choisi opengl ou metal?
μολὼν.λαβέ

Réponses:


1

Ces trois méthodes peuvent vous aider à trouver la solution:

  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. Voir également ce lien .


0

Je ne sais pas précisément ce qui provoque le plantage de votre code, mais voici un moyen de générer un maillage, des os et de skinning ce maillage - le tout à partir du code. Swift4 et iOS 12.

Dans l'exemple, il y a un maillage représentant la concaténation de deux cylindres, avec l'un des cylindres bifurquant à un angle de 45 degrés, comme ceci:

 \
 |

Les cylindres ne sont que des triangles extrudés, c'est-à-dire radialSegmentCount = 3.(notez qu'il y a 12 sommets, pas 9, puisque les deux cylindres ne sont pas vraiment joints. Les triangles sont ordonnés comme ceci:

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

Il y a 3 os, correspondant aux têtes et aux pieds des cylindres, où l'os médian correspond à la tête du cylindre inférieur et simultanément au pied du cylindre supérieur. Ainsi , par exemple, les sommets v0, v2et v4correspondent à bone0; v1, v3, v5Correspondent à bone1, et ainsi de suite. Cela explique pourquoi boneIndices(voir ci-dessous) a la valeur que cela a.

Les positions de repos des os correspondent aux positions de repos des cylindres dans la géométrie ( bone2jaillit à un angle de 45 degrés de bone1, tout comme la géométrie du cylindre).

Avec cela comme contexte, le code suivant crée tout le nécessaire pour habiller la géométrie:

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

Remarque: dans la plupart des cas, vous ne devez UInt16pas utiliser UInt8.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.