Lab 066 – First look at Mesh Instances Component

Useful for creating efficient copies of meshes in vast quantities.

Overview

The first thing to keep in mind when using this component is the name. This component creates instance of meshes, not entities. If you need to create a complete copy of an entity with all components and children, then use Entity.clone().

MeshInstancesComponent takes a bit of work to set up and use. We can specify a number of instances using LowLevelInstanceData.

let instances = try LowLevelInstanceData(instanceCount: 10)

Then we need to get the mesh / part that we want to copy. This can take some trial and error depending on the structure of the entity. For primitive entities like spheres and cubes we can get the first part index.

meshInstancesComponent[partIndex: 0] = .init(data: instances)

We can process the instances, making changes to the transform of each one as needed based on the requirements of our app.

instances.withMutableTransforms { transforms in
  for i in 0..<instanceCount {
    // Do something with the transform of each instance
  }
}

Here is a minimal usage:

let instanceCount = 10
var meshInstancesComponent = MeshInstancesComponent()
do {
    // 1. specify a number of instances
    let instances = try LowLevelInstanceData(instanceCount: instanceCount)
    
    // 2. get the mesh / part to copy
    meshInstancesComponent[partIndex: 0] = .init(data: instances)
    
    // Loop over each instance and update the transform
    instances.withMutableTransforms { transforms in
        for i in 0..<instanceCount {
            let offset: Float = 0.05 * Float(i)
            var transform = Transform()
            transform.translation = [offset, offset, offset]
            transforms[i] = transform.matrix
        }
    }
    
    // Add the component to the entity
    entity.components.set(meshInstancesComponent)
    
} catch {
    print("error creating instances = \(error)")
}

Video Demo

Example Code

The code for this example uses a tag gesture to create and attach a new instance of the component. Each time we tap the main entity, we add to the number of instances, then attach a new copy of MeshInstancesComponent. We could also capture the current MeshInstancesComponent from an entity, modify it, and assign it back. Perhaps we’ll dive deeper into that in another lab.

struct Lab066: View {
    @State var instanceCount = 1
    @State var offset: Float = 0.05

    var body: some View {
        RealityView { content in

            // Load a scene
            guard let scene = try? await Entity(named: "InstanceLab", in: realityKitContentBundle) else { return }
            content.add(scene)

            // Load an entity and set it up for input
            guard let subject = scene.findEntity(named: "Subject") else { return }
            subject.components.set(InputTargetComponent())
            subject.components.set(HoverEffectComponent())
            subject.components
                .set(CollisionComponent(shapes: [.generateBox(width: 0.1, height: 0.1, depth: 0.1)], isStatic: false))

            // When we tap on the subject, we'll create create new instances
            let tapGesture = TapGesture()
                .onEnded({ [weak subject] _ in

                    guard let subject = subject else { return }
                    createInstances(entity: subject)

                })
            
            let gestureComponent = GestureComponent(tapGesture)
            subject.components.set(gestureComponent)

            subject.position = .init(x: 0, y: -0.3, z: 0)

        }
        .toolbar {
            ToolbarItem(placement: .bottomOrnament, content: {
                Text("Instances: \(instanceCount)")
            })
        }
    }

    func createInstances(entity: Entity) {

        instanceCount += 1 // Add another instance each time this is run

        // Create and set up the component
        var meshInstancesComponent = MeshInstancesComponent()
        do {
            let instances = try LowLevelInstanceData(instanceCount: instanceCount)
            // The tricky part can be getting the correct part index
            meshInstancesComponent[partIndex: 0] = .init(data: instances)

            // Loop over each instance and update the transform
            instances.withMutableTransforms { transforms in
                for i in 0..<instanceCount {

                    // For this example, we'll only edit the position / translation. We can also edit the scale and rotation if needed.
                    let offset: Float = 0.05 * Float(i)
                    var transform = Transform()

                    transform.translation = [offset, offset, offset]
                    transforms[i] = transform.matrix

                }
            }

            entity.components.set(meshInstancesComponent)

        } catch {
            print("error creating instances = \(error)")
        }
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more labs from Step Into Vision.

Questions or feedback?