Jak stworzyć obiekt SceneKit SCNSkinner w kodzie?

89

Mam aplikację Swift używającą SceneKit dla iOS 8. Ładuję scenę z pliku .dae, który zawiera siatkę kontrolowaną przez szkielet. W czasie wykonywania muszę zmodyfikować współrzędne tekstury. Korzystanie z transformacji nie wchodzi w grę - muszę obliczyć inne, całkowicie nowe UV dla każdego wierzchołka siatki.

Wiem, że geometria jest niezmienna w SceneKit i przeczytałem, że sugerowanym podejściem jest ręczne wykonanie kopii. Próbuję to zrobić, ale zawsze kończy mi się awaria, gdy próbuję odtworzyć SCNSkinnerkod. Katastrofa jest w EXC_BAD_ACCESSśrodku C3DSourceAccessorGetMutableValuePtrAtIndex. Niestety nie ma do tego kodu źródłowego, więc nie jestem pewien, dlaczego dokładnie się zawiesza. Zawęziłem to do SCNSkinnerobiektu przymocowanego do węzła siatki. Jeśli tego nie ustawię, nie ma awarii i wydaje się, że wszystko działa.

EDYCJA: Oto bardziej kompletny stos wywołań awarii:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Nie znalazłem żadnej dokumentacji ani przykładowego kodu o tym, jak SCNSkinnerręcznie utworzyć obiekt. Ponieważ tworzę go tylko w oparciu o wcześniej działającą siatkę, nie powinno to być zbyt trudne. Tworzę SCNSkinnerzgodnie z dokumentacją Swift, przekazując wszystkie poprawne rzeczy do init. Jednak istnieje właściwość szkieletu SCNSkinner, której nie jestem pewien, jak ustawić. Ustawiłem go na szkielecie, który był na oryginale SCNSkinnersiatki, którą kopiuję, co moim zdaniem powinno działać ... ale tak nie jest. Podczas ustawiania właściwości szkieletu nie wygląda na to, że jest przypisywana. Sprawdzenie go natychmiast po przypisaniu pokazuje, że nadal jest zerowy. W ramach testu próbowałem ustawić właściwość szkieletu oryginalnej siatki na coś innego i po przypisaniu również ona została nietknięta.

Czy ktoś może rzucić światło na to, co się dzieje? Albo jak poprawnie SCNSkinnerręcznie stworzyć i skonfigurować obiekt?

Oto kod, którego używam do ręcznego sklonowania siatki i zastąpienia jej nową (nie modyfikowałem tutaj żadnych danych źródłowych - po prostu próbuję się upewnić, że mogę w tym miejscu utworzyć kopię) :

// 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
źródło
Mój obecny plan obejścia to po prostu obliczenie nowych współrzędnych tekstury, nadpisanie pliku .dae, opróżnienie sceny i ponowne załadowanie pliku .dae. Powinno to być całkowicie niepotrzebne, ale z dokumentacji Apple nie mogę dowiedzieć się, na czym polega problem. Albo robię coś niepoprawnie, pomijając jakiś kluczowy krok, albo SceneKit używa prywatnych API do poprawnej konfiguracji SCNSkinner podczas ładowania z pliku .dae. Interesujące jest to, że właściwość szkieletu po załadowaniu SceneKit z .dae jest węzłem bez dzieci , ale animacja szkieletu wyraźnie działa.
jcr
Niestety nadpisanie pliku .dae nie wchodzi w grę. Istnieje etap optymalizacji i kompresji, który ma miejsce, gdy Xcode kopiuje plik .dae na urządzenie. Zapisanie nowego pliku .dae na urządzeniu w aplikacji nie działa, ponieważ SceneKit na iOS nie załaduje pliku .dae, który nie został uruchomiony przez skrypt Xcode.
jcr,
Czy dostałeś tę pracę?
μολὼν.λαβέ
Nie, napisałem własny silnik i od tamtej pory nie korzystałem ze SceneKit.
jcr
Imponujący. Poszedłeś z openglem czy metalem?
μολὼν.λαβέ

Odpowiedzi:

1

Te trzy metody mogą pomóc w znalezieniu rozwiązania:

  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. Zobacz również ten link .

Kartik Raja S
źródło
0

Nie wiem konkretnie, co powoduje awarię kodu, ale oto sposób generowania siatki, kości i skórowania tej siatki - wszystko z kodu. Swift4 oraz iOS 12.

W tym przykładzie jest siatka reprezentująca połączenie dwóch cylindrów, przy czym jeden z cylindrów rozgałęzia się pod kątem 45 stopni, na przykład:

 \
 |

Cylindry są po prostu wytłoczonymi trójkątami, tj. radialSegmentCount = 3.(Zauważ, że jest 12 wierzchołków, a nie 9, ponieważ oba cylindry nie są tak naprawdę połączone. Trójkąty są uporządkowane w następujący sposób:

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

Istnieją 3 kości, odpowiadające głowom i stopom cylindrów, gdzie środkowa kość odpowiada główce dolnego cylindra i jednocześnie stopie górnego cylindra. Tak na przykład wierzchołki v0, v2i v4odpowiadają bone0; v1, v3, v5Odpowiadają bone1, i tak dalej. To wyjaśnia, dlaczego boneIndices(patrz poniżej) ma taką wartość.

Położenia spoczynkowe kości odpowiadają położeniom spoczynkowym cylindrów w geometrii ( bone2wyrastają pod kątem 45 stopni od bone1, podobnie jak geometria cylindra).

W tym kontekście poniższy kod tworzy wszystko, co jest potrzebne do skórowania geometrii:

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

Uwaga: W większości przypadków należy użyć UInt16nie UInt8.

Nick Kallen
źródło