Як створити в коді об’єкт SceneKit SCNSkinner?


89

У мене є програма Swift, яка використовує SceneKit для iOS 8. Я завантажую сцену з файлу .dae, що містить сітку, керовану скелетом. Під час виконання мені потрібно змінити координати текстури. Використання перетворення не є варіантом - мені потрібно обчислити інший, абсолютно новий УФ для кожної вершини сітки.

Я знаю, що геометрія незмінна у SceneKit, і я прочитав, що запропонований підхід полягає у створенні копії вручну. Я намагаюся це зробити, але завжди закінчую аварією, намагаючись відтворити вхідний SCNSkinnerкод. Аварія EXC_BAD_ACCESSвсередині C3DSourceAccessorGetMutableValuePtrAtIndex. На жаль, для цього немає вихідного коду, тому я не впевнений, чому саме він падає. Я звузив його до SCNSkinnerоб’єкта, приєднаного до вузла сітки. Якщо я не встановлюю це, я не отримую аварійного завершення роботи, і все, здається, працює.

EDIT: Ось більш повний стек викликів аварійного завершення роботи:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Я не знайшов жодної документації чи прикладу коду про те, як створити SCNSkinnerоб’єкт вручну. Оскільки я просто створюю його на основі раніше працюючої сітки, це не повинно бути надто складно. Я створюю SCNSkinnerвідповідно до документації Swift, передаючи всі правильні речі в init. Однак у цьому є властивість скелета, SCNSkinnerяке я не знаю, як встановити. Я встановив його на скелет, який знаходився на оригіналі SCNSkinnerсітки, яку я копіюю, що, на мою думку, мало б працювати ... але це не так. При встановленні властивості скелета воно, схоже, не призначається. Перевірка його відразу після призначення показує, що воно все ще нульове. В якості тесту я спробував встановити для властивості скелета оригінальної сітки щось інше, і після призначення це також залишилось недоторканим.

Хтось може пролити якесь світло на те, що відбувається? Або як правильно створити та налаштувати SCNSkinnerоб’єкт вручну?

Ось код, який я використовую для клонування сітки вручну та заміни її на нову (я не модифікував жодних вихідних даних тут - я просто намагаюся переконатися, що можу створити копію на цьому етапі) :

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

Мій поточний план обхідного шляху полягає в простому обчисленні нових координат текстури, перезапису файлу .dae, очищенні сцени та перезавантаженні файлу .dae. Це повинно бути абсолютно непотрібним, але з документації Apple я не можу зрозуміти, в чому проблема. Я або роблю щось неправильно, залишаючи важливий крок, або SceneKit використовує деякі приватні API для коректної настройки SCNSkinner під час завантаження з файлу .dae. Цікаво, що властивість скелета після завантаження SceneKit із .dae є вузлом без дітей , проте скелетна анімація явно працює.
jcr

На жаль, перезаписати файл .dae не можна. Існує крок оптимізації та стиснення, який відбувається, коли Xcode копіює ваш файл .dae на пристрій. Збереження нового файлу .dae на пристрої в програмі не працює, оскільки SceneKit на iOS не завантажує файл .dae, який не запускався за допомогою сценарію Xcode.
jcr

Ви отримали цю роботу?
μολὼν.λαβέ

Ні, я врешті-решт написав власний движок і з тих пір не використовував SceneKit.
jcr

Вражає. Ви ходили з opengl чи металом?
μολὼν.λαβέ

Відповіді:


1

Ці три методи можуть допомогти вам знайти рішення:

  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. Дивіться також це посилання .


0

Я конкретно не знаю, що спричиняє збій вашого коду, але ось спосіб генерувати сітку, кістки та обробляти цю сітку - все з коду. Swift4 та iOS 12.

У цьому прикладі є сітка, що представляє собою об'єднання двох циліндрів, причому один з циліндрів розгалужується під кутом 45 градусів, наприклад:

 \
 |

Циліндри - це просто екструдовані трикутники, тобто radialSegmentCount = 3.(Зверніть увагу, що існує 12 вершин, а не 9, оскільки два циліндри насправді не з’єднані. Трикутники впорядковані так:

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

Є 3 кістки, що відповідають головкам і ступням циліндрів, де середня кістка відповідає головці нижнього циліндра і одночасно стопі верхнього циліндра. Так, наприклад, вершини v0, v2і v4відповідають bone0; v1, v3, v5Відповідають bone1, і так далі. Це пояснює, чому boneIndices(див. Нижче) має значення, яке воно має.

Положення костей у стані спокою відповідають положенням циліндрів у спокої в геометрії ( bone2відростає під кутом 45 градусів від bone1, як і геометрія циліндра).

Враховуючи це як контекст, наступний код створює все необхідне для обробки геометрії:

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

Примітка: У більшості випадків вам слід використовувати UInt16ні UInt8.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.