2
votes

I've got a row dimensional array of values that I want to visualize in 3D and I'm using scene kit under OS X for it. I've done it in a clumsy manner by using each column as a point on the X axis, each row as a point on the Z axis, and each value as a normalized point on the Y axis -- I place a sphere at the vector defined by each data point. It works but it doesn't look too good.

I've also done this by building a mesh of lines based on @Matthew's function in Drawing a line between two points using SceneKit (the answer he posted, not the original question). For each point I use his function to draw two lines - one between my current point and the next point to the right and another between my current point and the next point towards the front (except when there is no additional column/row, of course).

Using the second method, my results look much better... however the performance is quite hideous! It takes quite a long time to complete the initial rendering, and if I use a trackpad/mouse to rotate or translate the scene, I might as well get a cup of coffee to wait until my system is usable again (and this is not much hyperbole). Using the sphere method, things render and update very quickly.

Any advice on how to improve the performance when using the lines method? (Note that I am not trying to add both lines and spheres at the same time.) Code-wise, the only difference between approach is which of the following methods gets called (and that for each point, addPixelAt... is called once, but addLineAt... is called twice for most points).

enter image description here

- (SCNNode *)addPixelAtRow:(CGFloat)row Column:(CGFloat)column size:(CGFloat)size color:(NSColor *)color
{
    CGFloat radius = 0.5;

    SCNSphere *ball = [SCNSphere sphereWithRadius:radius*1.5];
    SCNMaterial *material = [SCNMaterial material];
    [[material diffuse] setContents:color];
    [[material specular] setContents:color];
    [ball setMaterials:@[material]];

    SCNNode *ballNode = [SCNNode nodeWithGeometry:ball];
    [ballNode setPosition:SCNVector3Make(column, size, row)];

    [_baseNode addChildNode:ballNode];
    return ballNode;
}

enter image description here

- (SCNNode *)addLineFromRow:(CGFloat)row1  Column:(CGFloat)column1  size:(CGFloat)size1
                       toRow2:(CGFloat)row2 Column2:(CGFloat)column2 size2:(CGFloat)size2 color:(NSColor *)color
{
    SCNVector3 positions[] = {
        SCNVector3Make(column1, size1, row1),
        SCNVector3Make(column2, size2, row2)
    };

    int indices[] = {0, 1};

    SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:2];

    NSData *indexData = [NSData dataWithBytes:indices length:sizeof(indices)];

    SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData
                                                                primitiveType:SCNGeometryPrimitiveTypeLine
                                                               primitiveCount:1
                                                                bytesPerIndex:sizeof(int)];

    SCNGeometry *line = [SCNGeometry geometryWithSources:@[vertexSource] elements:@[element]];
    SCNMaterial *material = [SCNMaterial material];
    [[material diffuse] setContents:color];
    [[material specular] setContents:color];
    [line setMaterials:@[material]];

    SCNNode *lineNode = [SCNNode nodeWithGeometry:line];

    [_baseNode addChildNode:lineNode];
    return lineNode;
}
1
We would need to know more about the performance problems and the measurements that you have? Is the problem too many draw calls? Too many triangles? Is the CPU doing too much work? etc.David Rönnqvist
Also, are you using the lines individually or are they just making up a mesh? (A screenshot could be useful to know what you are doing but it's not related to the performance problems)David Rönnqvist
@DavidRönnqvist I'm not positive of the answers here, but I can say it's not only my application that suffers when this happens, it's the full system. What I can say is that even when using the sphere method, it's approximately a second before I can see the scene after my last node gets created.mah
My lines are individually created; I have 10,000 data points, so 20k lines.mah
If you turn on showsStatistics on the view, what does that tell you?David Rönnqvist

1 Answers

5
votes

From the data that you've shown in your question I would say that your main problem is the number of draw calls. Your's is in the tens of thousands, which is way too much. It should probably be a lot closer to ~100.

The reason why you have so many draw calls is that you have so many distinct objects in your scene (each line). The better (but more advanced solution) would probably be to generate a single element for the entire mesh that consists of all the lines. If you want to achieve the same rendering with that mesh (with a color from cold to warm based on the height) then you could do that in a shader modifier.

However, in your case I would start by flattening all the lines (since that would be the smallest code change and should still have a significant performance improvement in your case).

(Optimizing performance is always an iterative process. Once you fix one thing there will be another thing which is the most expensive operation. Without your code I can only say what would help with the current performance problem)

Create an empty node (without adding it to your scene) and generate all the lines, adding them to this node. Then create a flattened copy of that node by calling flattenedClone on the node that contains all the lines

SCNNode *nodeWithAllTheLines = [SCNNode node];
// create all the lines and add them to it...
SCNNode *flattenedNode = [nodeWithAllTheLines flattenedClone];
[_baseNode addChildNode:flattenedNode];

When you do this you should see a significant drop in the number of draw calls (the number after the diamond in the statistics) and hopefully a big increase in performance.