Wie erstelle ich ein SceneKit SCNSkinner-Objekt im Code?

89

Ich habe eine Swift-App mit SceneKit für iOS 8. Ich lade eine Szene aus einer .dae-Datei, die ein von einem Skelett gesteuertes Netz enthält. Zur Laufzeit muss ich die Texturkoordinaten ändern. Die Verwendung einer Transformation ist keine Option. Ich muss für jeden Scheitelpunkt im Netz eine andere, völlig neue UV-Strahlung berechnen.

Ich weiß, dass Geometrie in SceneKit unveränderlich ist, und ich habe gelesen, dass der vorgeschlagene Ansatz darin besteht, eine Kopie manuell zu erstellen. Ich versuche das zu tun, aber ich stürze immer ab, wenn ich versuche, den SCNSkinnerIn-Code neu zu erstellen . Der Absturz ist ein EXC_BAD_ACCESSInneres C3DSourceAccessorGetMutableValuePtrAtIndex. Leider gibt es dafür keinen Quellcode, daher bin ich mir nicht sicher, warum genau er abstürzt. Ich habe es auf das SCNSkinnerObjekt eingegrenzt, das an den Netzknoten angehängt ist. Wenn ich das nicht einstelle, bekomme ich keinen Absturz und die Dinge scheinen zu funktionieren.

EDIT: Hier ist ein vollständigerer Aufrufstapel des Absturzes:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Ich habe keine Dokumentation oder Beispielcode zum SCNSkinnermanuellen Erstellen eines Objekts gefunden. Da ich es nur auf der Grundlage eines zuvor funktionierenden Netzes erstelle, sollte es nicht zu schwierig sein. Ich erstelle das SCNSkinnergemäß der Swift-Dokumentation und übergebe alle richtigen Dinge an den Init. Es gibt jedoch eine Skeletteigenschaft in der SCNSkinner, deren Einstellung ich nicht sicher bin. Ich habe es auf das Skelett eingestellt, das sich auf dem Original SCNSkinnerdes zu kopierenden Netzes befand, was meiner Meinung nach funktionieren sollte ... aber es funktioniert nicht. Wenn Sie die Skeleton-Eigenschaft festlegen, scheint sie nicht zugewiesen zu sein. Wenn Sie es unmittelbar nach der Zuweisung überprüfen, wird angezeigt, dass es immer noch Null ist. Als Test habe ich versucht, die Skeletteigenschaft des ursprünglichen Netzes auf etwas anderes zu setzen, und nach der Zuweisung blieb sie ebenfalls unberührt.

Kann jemand Licht ins Dunkel bringen, was passiert? Oder wie man ein SCNSkinnerObjekt manuell korrekt erstellt und einrichtet ?

Hier ist der Code, den ich verwende, um ein Netz manuell zu klonen und durch ein neues zu ersetzen (ich habe hier keine Quelldaten geändert - ich versuche lediglich sicherzustellen, dass ich an dieser Stelle eine Kopie erstellen kann). ::

// 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)
jcr
quelle
Mein aktueller Plan für eine Problemumgehung besteht darin, einfach die neuen Texturkoordinaten zu berechnen, die .dae-Datei zu überschreiben, die Szene zu leeren und die .dae-Datei neu zu laden. Dies sollte völlig unnötig sein, aber aus der Dokumentation von Apple kann ich nicht herausfinden, wo das Problem liegt. Entweder mache ich etwas falsch und lasse einen entscheidenden Schritt aus, oder SceneKit verwendet einige private APIs, um den SCNSkinner beim Laden aus der .dae-Datei korrekt einzurichten. Es ist interessant, dass die Skeleton-Eigenschaft nach dem Laden von SceneKit aus der .dae ein Knoten ohne untergeordnete Elemente ist , die Skelettanimation jedoch eindeutig funktioniert.
jcr
Das Überschreiben der .dae-Datei ist leider keine Option. Es gibt einen Optimierungs- und Komprimierungsschritt, der ausgeführt wird, wenn Xcode Ihre .dae-Datei auf das Gerät kopiert. Das Speichern einer neuen .dae-Datei auf dem Gerät in der App funktioniert nicht, da SceneKit unter iOS keine .dae-Datei lädt, die nicht über das Xcode-Skript ausgeführt wurde.
jcr
Hast du diese Arbeit bekommen?
μολὼν.λαβέ
Nein, am Ende habe ich meine eigene Engine geschrieben und seitdem kein SceneKit mehr verwendet.
jcr
Beeindruckend. Bist du mit OpenGL oder Metal gegangen?
μολὼν.λαβέ

Antworten:

1

Diese drei Methoden können Ihnen helfen, die Lösung zu finden:

  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. Siehe auch diesen Link .

Kartik Raja S.
quelle
0

Ich weiß nicht genau, was Ihren Code zum Absturz bringt, aber hier ist eine Möglichkeit, ein Netz, Knochen und das Enthäuten dieses Netzes zu generieren - alles aus Code. Swift4 und iOS 12.

In dem Beispiel gibt es ein Netz, das die Verkettung von zwei Zylindern darstellt, wobei einer der Zylinder in einem Winkel von 45 Grad abzweigt, wie folgt:

 \
 |

Die Zylinder sind nur extrudierte Dreiecke, dh radialSegmentCount = 3.(Beachten Sie, dass es 12 Eckpunkte gibt, nicht 9, da die beiden Zylinder nicht wirklich miteinander verbunden sind. Die Dreiecke sind wie folgt angeordnet:

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

Es gibt 3 Knochen, die den Köpfen und Füßen der Zylinder entsprechen, wobei der mittlere Knochen dem Kopf des unteren Zylinders und gleichzeitig dem Fuß des oberen Zylinders entspricht. So zum Beispiel, Ecken v0, v2und v4entsprechen bone0; v1, v3, v5Entspricht bone1, und so weiter. Das erklärt, warum boneIndices(siehe unten) den Wert hat, den es hat.

Die Ruhepositionen der Knochen entsprechen den Ruhepositionen der Zylinder in der Geometrie ( bone2sprießt in einem Winkel von 45 Grad von bone1, genau wie die Zylindergeometrie).

Mit diesem Kontext erstellt der folgende Code alles, was zum Skinieren der Geometrie erforderlich ist:

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

Hinweis: In den meisten Fällen sollten Sie UInt16nicht verwenden UInt8.

Nick Kallen
quelle