Làm thế nào để tạo một đối tượng SceneKit SCNSkinner trong mã?


89

Tôi có một ứng dụng Swift bằng SceneKit cho iOS 8. Tôi tải một cảnh từ tệp .dae có chứa lưới được điều khiển bởi khung. Trong thời gian chạy, tôi cần sửa đổi tọa độ kết cấu. Sử dụng một phép biến đổi không phải là một lựa chọn - tôi cần tính toán một tia UV khác, hoàn toàn mới cho mỗi đỉnh trong lưới.

Tôi biết hình học là bất biến trong SceneKit và tôi đã đọc rằng cách tiếp cận được đề xuất là tạo một bản sao theo cách thủ công. Tôi đang cố gắng làm điều đó, nhưng cuối cùng tôi luôn gặp sự cố khi cố gắng tạo lại SCNSkinnermã trong mã. Vụ tai nạn là một EXC_BAD_ACCESSbên trong C3DSourceAccessorGetMutableValuePtrAtIndex. Thật không may, không có mã nguồn nào cho việc này, vì vậy tôi không chắc chính xác tại sao nó lại gặp sự cố. Tôi đã thu hẹp nó xuống SCNSkinnerđối tượng được gắn vào nút lưới. Nếu tôi không đặt điều đó, tôi sẽ không gặp sự cố và mọi thứ dường như đang hoạt động.

CHỈNH SỬA: Đây là một ngăn xếp cuộc gọi đầy đủ hơn về sự cố:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

Tôi không tìm thấy bất kỳ tài liệu hoặc mã ví dụ nào về cách tạo một SCNSkinnerđối tượng theo cách thủ công. Vì tôi chỉ tạo nó dựa trên một lưới đã làm việc trước đó, nên không quá khó. Tôi đang tạo SCNSkinnertheo tài liệu Swift, chuyển tất cả những thứ chính xác vào init. Tuy nhiên, có một thuộc tính khung SCNSkinnermà tôi không chắc chắn về cách đặt. Tôi đặt nó vào khung trên bản gốc SCNSkinnercủa lưới mà tôi đang sao chép, mà tôi nghĩ sẽ hoạt động ... nhưng nó không. Khi đặt thuộc tính bộ xương, nó dường như không được chỉ định. Kiểm tra nó ngay sau khi được giao cho thấy rằng nó vẫn là con số không. Như một bài kiểm tra, tôi đã cố gắng đặt thuộc tính khung của lưới gốc thành một thứ khác và sau khi gán nó cũng được giữ nguyên.

Bất cứ ai có thể làm sáng tỏ những gì đang xảy ra? Hoặc làm thế nào để tạo và thiết lập một SCNSkinnerđối tượng theo cách thủ công một cách chính xác ?

Đây là mã tôi đang sử dụng để sao chép thủ công một lưới và thay thế nó bằng một lưới mới (Tôi chưa sửa đổi bất kỳ dữ liệu nguồn nào ở đây - tôi chỉ đang cố gắng đảm bảo rằng tôi có thể tạo một bản sao tại thời điểm này) :

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

Kế hoạch hiện tại của tôi cho một cách giải quyết là chỉ cần tính toán tọa độ kết cấu mới, ghi đè tệp .dae, xóa hiện trường và tải lại tệp .dae. Điều này hoàn toàn không cần thiết, nhưng từ tài liệu của Apple, tôi không thể tìm ra vấn đề là gì. Tôi đang làm sai điều gì đó, bỏ qua một số bước quan trọng hoặc SceneKit sử dụng một số API riêng tư để thiết lập chính xác SCNSkinner khi tải từ tệp .dae. Thật thú vị khi thuộc tính bộ xương sau khi SceneKit tải từ .dae là một nút không có nút con , nhưng hoạt ảnh bộ xương rõ ràng hoạt động.
jcr

Rất tiếc, ghi đè tệp .dae không phải là một tùy chọn. Có một bước tối ưu hóa và nén xảy ra khi Xcode sao chép tệp .dae của bạn vào thiết bị. Lưu tệp .dae mới trên thiết bị trong ứng dụng không hoạt động vì SceneKit trên iOS sẽ không tải tệp .dae chưa được chạy qua tập lệnh của Xcode.
jcr

Bạn đã nhận được tác phẩm này?
μολὼν.λαβέ

Không, cuối cùng tôi đã viết công cụ của riêng mình và không sử dụng SceneKit kể từ đó.
jcr

Thật ấn tượng. Bạn đã đi với opengl hay metal?
μολὼν.λαβέ

Câu trả lời:


1

Ba phương pháp này có thể giúp bạn tìm ra giải pháp:

  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. Xem liên kết này .


0

Tôi không biết cụ thể nguyên nhân khiến mã của bạn gặp sự cố nhưng đây là một cách tạo lưới, xương và lột da lưới đó - tất cả đều từ mã. Swift4 và iOS 12.

Trong ví dụ, có lưới đại diện cho sự ghép nối của hai hình trụ, với một trong các hình trụ phân nhánh theo góc 45 độ, như sau:

 \
 |

Các hình trụ chỉ là hình tam giác đùn, tức là, radialSegmentCount = 3.(Lưu ý rằng có 12 đỉnh, không phải 9, vì hai hình trụ không thực sự dính liền với nhau. Các hình tam giác được sắp xếp như sau:

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

Có 3 xương, tương ứng với đầu và chân của trụ, trong đó xương ở giữa tương ứng với đầu của trụ dưới và đồng thời là chân của trụ trên. Vì vậy, ví dụ, đỉnh v0, v2v4tương ứng với bone0; v1, v3, v5Tương ứng với bone1, và vân vân. Điều đó giải thích tại sao boneIndices(xem bên dưới) có giá trị như nó.

Vị trí nghỉ của xương tương ứng với vị trí nghỉ của hình trụ trong hình học ( bone2mọc lệch ra một góc 45 độ bone1, giống như hình trụ).

Với ngữ cảnh đó, đoạn mã sau tạo ra mọi thứ cần thiết để làm nổi bật hình học:

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

Lưu ý: Trong hầu hết các trường hợp, bạn nên sử dụng UInt16không UInt8.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.