Reputation: 81
I created a fully custom rigged SCNNode programmatically. This node is complete with custom geometry, a skeletal rig as a hierarchy of child nodes and an SCNSkinner object to bind skeleton and geometry together. The problem is this: As soon as I clone my custom node, it looses the binding between geometry and skeleton.
To get to the root of this problem, I swapped my rather complex character node with the simplest geometry-rig combination I could think of: A geometry block of 12 vertices and a skeletal rig of 3 joints.
I'd have loved to share an image at this point, but can't do this as I don't have enough reputation, yet...
-(SCNNode *)createCustomRigBlock {
// baseGeometry
SCNVector3 positions[] = {
SCNVector3Make(0, 0, 0),
SCNVector3Make(0, 0, 1),
SCNVector3Make(1, 0, 1),
SCNVector3Make(1, 0, 0),
SCNVector3Make(0, 1, 0),
SCNVector3Make(0, 1, 1),
SCNVector3Make(1, 1, 1),
SCNVector3Make(1, 1, 0),
SCNVector3Make(0, 2, 0),
SCNVector3Make(0, 2, 1),
SCNVector3Make(1, 2, 1),
SCNVector3Make(1, 2, 0)
};
SCNGeometrySource * baseGeometrySource = [SCNGeometrySource geometrySourceWithVertices:positions count:12];
typedef struct {
uint16_t a, b, c;
} Triangles;
Triangles tVectors[20] = {
0,1,2,
0,2,3,
0,1,5,
0,4,5,
4,5,9,
4,8,9,
1,2,6,
1,5,6,
5,6,10,
5,9,10,
2,3,7,
2,6,7,
6,7,11,
6,10,11,
3,0,4,
3,4,7,
7,4,8,
7,8,11,
8,9,10,
8,10,11
};
NSData *triangleData = [NSData dataWithBytes:tVectors length:sizeof(tVectors)];
SCNGeometryElement * baseGeometryElement = [SCNGeometryElement geometryElementWithData:triangleData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:20 bytesPerIndex:sizeof(uint16_t)];
SCNGeometry * baseGeometry = [SCNGeometry geometryWithSources:[NSArray arrayWithObject:baseGeometrySource] elements:[NSArray arrayWithObject:baseGeometryElement]];
baseGeometry.firstMaterial.emission.contents = [UIColor greenColor];
baseGeometry.firstMaterial.doubleSided = YES;
baseGeometry.firstMaterial.transparency = 0.5;
SCNNode *customBlock = [SCNNode nodeWithGeometry:baseGeometry];
int stageSize = 30;
customBlock.position = SCNVector3Make(stageSize/2, 0, stageSize/2-6);
int vectorCount = (int)[(SCNGeometrySource *)[customBlock.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex].firstObject vectorCount];
//bones ... the bones of the rig
NSMutableArray * bonesArray = [NSMutableArray new];
for (int i = 0; i < 3; i++) {
SCNNode * boneNode = [SCNNode new];
boneNode.name = [NSString stringWithFormat:@"bone_%i",i];
if (bonesArray.count > 0) {
[bonesArray.lastObject addChildNode:boneNode];
}
boneNode.position = SCNVector3Make((i>0 ? 0 : 0.5), (i>0 ? 0.75 : 0.25), (i>0 ? 0 : 0.5));
//add a sphere to each bone, to visually check its position etc.
SCNSphere *boneSphereGeom = [SCNSphere sphereWithRadius:0.1];
boneSphereGeom.firstMaterial.emission.contents = [UIColor redColor];
SCNNode * boneSphere = [SCNNode nodeWithGeometry:boneSphereGeom];
[boneNode addChildNode:boneSphere];
[bonesArray addObject:boneNode];
}
[customBlock addChildNode:bonesArray.firstObject];
//boneInverseBindTransforms ... this defines the geometries transformation in the default pose!
NSMutableArray * bibtArray = [NSMutableArray new];
for (int i = 0; i < 3; i++) {
SCNMatrix4 initialPositionMatrix = SCNMatrix4MakeTranslation(0.5, (i*0.75)+0.25, 0.5);
SCNMatrix4 inverseFinalMatrix = SCNMatrix4Invert(initialPositionMatrix);
NSValue * bibtValue = [NSValue valueWithSCNMatrix4:inverseFinalMatrix];
[bibtArray addObject:bibtValue];
}
//boneWeights ... the weights, at which each vertex is influenced by certain bones (which bones is defined by "boneIndices")
typedef struct {
float a, b, c;
} WeightVectors;
WeightVectors vectors[vectorCount];
for (int i = 0; i < vectorCount; i++) {
// set the same boneWeights for every vertex
vectors[i].a = 1;
vectors[i].b = 0;
vectors[i].c = 0;
}
NSData *weightData = [NSData dataWithBytes:vectors length:sizeof(vectors)];
SCNGeometrySource * boneWeightsGeometrySource = [SCNGeometrySource geometrySourceWithData:weightData
semantic:SCNGeometrySourceSemanticBoneWeights
vectorCount:vectorCount
floatComponents:YES
componentsPerVector:3
bytesPerComponent:sizeof(float)
dataOffset:offsetof(WeightVectors, a)
dataStride:sizeof(WeightVectors)];
//boneIndices
typedef struct {
short k, l, m; // boneWeight
} IndexVectors;
IndexVectors iVectors[vectorCount];
for (int i = 0; i < vectorCount; i++) {
if (i > 7) {
iVectors[i].k = 1;
iVectors[i].l = 0;
iVectors[i].m = 0;
} else {
iVectors[i].k = 0;
iVectors[i].l = 0;
iVectors[i].m = 0;
}
}
NSData *indexData = [NSData dataWithBytes:iVectors length:sizeof(iVectors)];
SCNGeometrySource * boneIndicesGeometrySource = [SCNGeometrySource geometrySourceWithData:indexData
semantic:SCNGeometrySourceSemanticBoneIndices
vectorCount:vectorCount
floatComponents:NO
componentsPerVector:3
bytesPerComponent:sizeof(short)
dataOffset:offsetof(IndexVectors, k)
dataStride:sizeof(IndexVectors)];
SCNSkinner * customBlockSkinner = [SCNSkinner skinnerWithBaseGeometry:baseGeometry
bones:bonesArray
boneInverseBindTransforms:bibtArray
boneWeights:boneWeightsGeometrySource
boneIndices:boneIndicesGeometrySource];
customBlock.skinner = customBlockSkinner;
[[bonesArray objectAtIndex:1] runAction:[SCNAction repeatActionForever:[SCNAction rotateByX:0 y:0 z:2 duration:2]]];
return customBlock;
}
This custom SCNNode works perfectly, as long as I add it to my scene directly. Now, in my current application I need to add this node to the scene several times. That's why I need to clone the node and add the resulting clone to the scene instead. But something is very wrong with the result of cloning the custom node. As I mentioned above, the binding between geometry and skeleton appears to be lost. The skeleton in the cloned node can still be positioned in my scene, but the geometry doesn't follow the skeletons position or transformation anymore.
What I tried already:
1.) I created a fresh node and added a copy of the original geometry to it. Then I cloned the skeleton root node and added this to the fresh node as well. And finally I set the originals skinner as the fresh nodes skinner. Unfortunately to no avail. The result behaves exactly the same as a directly cloned node.
SCNNode * newNode = [SCNNode node];
newNode.geometry = [node.geometry copy];
for (SCNNode * childNode in node.childNodes) {
[newNode addChildNode:[childNode clone]];
}
newNode.skinner = node.skinner;
2.) In the attempt to recreate the binding between the geometry and the skeleton I created a deep copy of the SCNSkinner and made it the skinner of the fresh node. But this only caused a crash in C3DSourceAccessorGetMutableValuePtrAtIndex, which I couldn't fix.
NSArray * newBones = [newNode childNodesPassingTest:^(SCNNode *child, BOOL *stop)
{
if (![child.name isEqualToString:@"manipulator"] ) {
return YES;
}
return NO;
}];
NSArray * newBoneInverseBindTransforms = [node.skinner.boneInverseBindTransforms copy];
SCNGeometrySource * newBoneWeights = [SCNGeometrySource geometrySourceWithData:[node.skinner.boneWeights.data copy]
semantic:SCNGeometrySourceSemanticBoneWeights
vectorCount:node.skinner.boneWeights.vectorCount
floatComponents:node.skinner.boneWeights.floatComponents
componentsPerVector:node.skinner.boneWeights.componentsPerVector
bytesPerComponent:node.skinner.boneWeights.bytesPerComponent
dataOffset:node.skinner.boneWeights.dataOffset
dataStride:node.skinner.boneWeights.dataStride];
SCNGeometrySource * newBoneIndices = [SCNGeometrySource geometrySourceWithData:[node.skinner.boneIndices.data copy]
semantic:SCNGeometrySourceSemanticBoneIndices
vectorCount:node.skinner.boneIndices.vectorCount
floatComponents:node.skinner.boneIndices.floatComponents
componentsPerVector:node.skinner.boneIndices.componentsPerVector
bytesPerComponent:node.skinner.boneIndices.bytesPerComponent
dataOffset:node.skinner.boneIndices.dataOffset
dataStride:node.skinner.boneIndices.dataStride];
SCNSkinner * newSkinner = [SCNSkinner skinnerWithBaseGeometry:newNode.geometry
bones:newBones
boneInverseBindTransforms:newBoneInverseBindTransforms
boneWeights:newBoneWeights
boneIndices:newBoneIndices];
newNode.skinner = newSkinner;
Upvotes: 1
Views: 1166
Reputation: 553
The answer is in apple SCNNode documentation.
Try using flattenedClone()
method instead of clone()
. And one more thing, it's not needed any for statement. Here is a little example in Swift:
nodeB = nodeA.flattenedClone()
Now, nodeB contains all children nodes of nodeA
Upvotes: 0