Bagaimana cara membuat objek SCNSkinner SceneKit dalam kode?

88

Saya memiliki aplikasi Swift menggunakan SceneKit untuk iOS 8. Saya memuat adegan dari file .dae yang berisi mesh yang dikendalikan oleh kerangka. Saat runtime, saya perlu mengubah koordinat tekstur. Menggunakan transformasi bukanlah pilihan - saya perlu menghitung UV yang berbeda dan benar-benar baru untuk setiap simpul di mesh.

Saya tahu geometri tidak dapat diubah di SceneKit, dan saya telah membaca bahwa pendekatan yang disarankan adalah membuat salinan secara manual. Saya mencoba melakukan itu, tetapi saya selalu mengalami error saat mencoba membuat ulang SCNSkinnerkode in. Kecelakaan itu ada EXC_BAD_ACCESSdi dalam C3DSourceAccessorGetMutableValuePtrAtIndex. Sayangnya, tidak ada kode sumber untuk ini, jadi saya tidak yakin mengapa sebenarnya itu macet. Saya telah mempersempitnya ke SCNSkinnerobjek yang menempel pada node mesh. Jika saya tidak menyetelnya, saya tidak mengalami crash dan segala sesuatunya tampak berfungsi.

EDIT: Berikut adalah tumpukan panggilan crash yang lebih lengkap:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Saya belum menemukan dokumentasi atau kode contoh tentang cara membuat SCNSkinnerobjek secara manual. Karena saya hanya membuatnya berdasarkan mesh yang berfungsi sebelumnya, seharusnya tidak terlalu sulit. Saya membuat SCNSkinnersesuai dengan dokumentasi Swift, meneruskan semua hal yang benar ke init. Namun, ada properti kerangka di dalamnya SCNSkinneryang saya tidak yakin cara menyetelnya. Saya mengaturnya ke kerangka yang ada di SCNSkinnermesh asli yang saya salin, yang menurut saya harus berfungsi ... tetapi ternyata tidak. Saat menyetel properti kerangka, tampaknya properti tersebut tidak menetapkan. Memeriksa segera setelah penugasan menunjukkan bahwa masih nihil. Sebagai ujian, saya mencoba untuk mengatur properti kerangka mesh asli ke sesuatu yang lain, dan setelah penugasan itu dibiarkan tidak tersentuh juga.

Adakah yang bisa menjelaskan apa yang terjadi? Atau bagaimana cara membuat dan mengatur SCNSkinnerobjek secara manual?

Berikut adalah kode yang saya gunakan untuk mengkloning mesh secara manual dan menggantinya dengan yang baru (Saya belum mengubah sumber data apa pun di sini - Saya hanya mencoba memastikan saya dapat membuat salinan pada saat ini) :

// 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
sumber
Rencana saya saat ini untuk mengatasinya adalah dengan hanya menghitung koordinat tekstur baru, menimpa file .dae, membersihkan layar, dan memuat ulang file .dae. Ini seharusnya benar-benar tidak perlu, tetapi dari dokumentasi Apple, saya tidak tahu apa masalahnya. Saya melakukan sesuatu yang salah, meninggalkan beberapa langkah penting, atau SceneKit menggunakan beberapa API pribadi untuk mengatur SCNSkinner dengan benar saat memuat dari file .dae. Sangat menarik bahwa properti kerangka setelah SceneKit dimuat dari .dae adalah node tanpa turunan , namun animasi kerangka berfungsi dengan jelas.
jcr
Sayangnya, menimpa file .dae bukanlah suatu pilihan. Terdapat langkah pengoptimalan dan kompresi yang terjadi saat Xcode menyalin file .dae Anda ke perangkat. Menyimpan file .dae baru di perangkat dalam aplikasi tidak berfungsi karena SceneKit di iOS tidak akan memuat file .dae yang belum dijalankan melalui skrip Xcode.
jcr
Apakah Anda mendapatkan pekerjaan ini?
μολὼν.λαβέ
Tidak, saya akhirnya membuat mesin saya sendiri dan tidak menggunakan SceneKit sejak itu.
jcr
Impresif. Apakah Anda memilih opengl atau metal?
μολὼν.λαβέ

Jawaban:

1

Ketiga metode ini dapat membantu Anda menemukan solusinya:

  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. Lihat tautan ini juga.

Kartik Raja S.
sumber
0

Saya tidak tahu secara spesifik apa yang menyebabkan kode Anda macet, tetapi berikut adalah cara menghasilkan mesh, tulang, dan skinning mesh itu - semuanya dari kode. Swift4 dan iOS 12.

Dalam contoh, terdapat jaring yang merepresentasikan rangkaian dua silinder, dengan salah satu silinder bercabang pada sudut 45 derajat, seperti:

 \
 |

Silinder hanyalah segitiga yang diekstrusi, yaitu radialSegmentCount = 3.(Perhatikan bahwa ada 12 simpul, bukan 9, karena kedua silinder tidak benar-benar terhubung. Segitiga tersebut disusun seperti ini:

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

Ada 3 tulang, sesuai dengan kepala dan kaki silinder, dimana tulang tengah sesuai dengan kepala silinder bawah dan sekaligus kaki silinder atas. Jadi misalnya, simpul v0, v2dan v4sesuai dengan bone0; v1, v3, v5Sesuai dengan bone1, dan sebagainya. Itu menjelaskan mengapa boneIndices(lihat di bawah) memiliki nilai yang dimilikinya.

Posisi istirahat tulang sesuai dengan posisi diam silinder dalam geometri ( bone2bertunas pada sudut 45 derajat dari bone1, seperti geometri silinder).

Dengan itu sebagai konteks, kode berikut membuat semua yang dibutuhkan untuk menguliti geometri:

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

Catatan: Dalam banyak kasus, Anda sebaiknya UInt16tidak menggunakan UInt8.

Nick Kallen
sumber