Agent Skills: SceneKit API Reference & Migration Mapping

SceneKit → RealityKit concept mapping, complete API cross-reference for migration, scene graph API, materials, lighting, camera, physics, animation, constraints

UncategorizedID: charleswiltgen/axiom/axiom-scenekit-ref

Install this agent skill to your local

pnpm dlx add-skill https://github.com/CharlesWiltgen/Axiom/tree/HEAD/.claude-plugin/plugins/axiom/skills/axiom-scenekit-ref

Skill Files

Browse the full folder contents for axiom-scenekit-ref.

Download Skill

Loading file tree…

.claude-plugin/plugins/axiom/skills/axiom-scenekit-ref/SKILL.md

Skill Metadata

Name
axiom-scenekit-ref
Description
SceneKit → RealityKit concept mapping, complete API cross-reference for migration, scene graph API, materials, lighting, camera, physics, animation, constraints

SceneKit API Reference & Migration Mapping

Complete API reference for SceneKit with RealityKit equivalents for every major concept.

When to Use This Reference

Use this reference when:

  • Looking up SceneKit → RealityKit API equivalents during migration
  • Checking specific SceneKit class properties or methods
  • Planning which SceneKit features have direct RealityKit counterparts
  • Understanding architectural differences between scene graph and ECS

Part 1: SceneKit → RealityKit Concept Mapping

Core Architecture

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | SCNScene | RealityViewContent / Entity (root) | RealityKit scenes are entity hierarchies | | SCNNode | Entity | Lightweight container in both | | SCNView | RealityView (SwiftUI) | ARView for UIKit on iOS | | SceneView (SwiftUI) | RealityView | SceneView deprecated iOS 26 | | SCNRenderer | RealityRenderer | Low-level Metal rendering | | Node properties | Components | ECS separates data from hierarchy | | SCNSceneRendererDelegate | System / SceneEvents.Update | Frame-level updates | | .scn files | .usdz / .usda files | Convert with xcrun scntool |

Geometry & Rendering

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | SCNGeometry | MeshResource | RealityKit generates from code or loads USD | | SCNBox, SCNSphere, etc. | MeshResource.generateBox(), .generateSphere() | Similar built-in shapes | | SCNMaterial | SimpleMaterial, PhysicallyBasedMaterial | PBR-first in RealityKit | | SCNMaterial.lightingModel = .physicallyBased | PhysicallyBasedMaterial | Default in RealityKit | | SCNMaterial.diffuse | PhysicallyBasedMaterial.baseColor | Different property name | | SCNMaterial.metalness | PhysicallyBasedMaterial.metallic | Different property name | | SCNMaterial.roughness | PhysicallyBasedMaterial.roughness | Same concept | | SCNMaterial.normal | PhysicallyBasedMaterial.normal | Same concept | | Shader modifiers | ShaderGraphMaterial / CustomMaterial | No direct port — must rewrite | | SCNProgram (custom shaders) | CustomMaterial with Metal functions | Different API surface | | SCNGeometrySource | MeshResource.Contents | Low-level mesh data |

Transforms & Hierarchy

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | node.position | entity.position | Both SCNVector3 / SIMD3<Float> | | node.eulerAngles | entity.orientation (quaternion) | RealityKit prefers quaternions | | node.scale | entity.scale | Both SIMD3<Float> | | node.transform | entity.transform | 4×4 matrix | | node.worldTransform | entity.transform(relativeTo: nil) | World-space transform | | node.addChildNode(_:) | entity.addChild(_:) | Same hierarchy concept | | node.removeFromParentNode() | entity.removeFromParent() | Same concept | | node.childNodes | entity.children | Children collection | | node.parent | entity.parent | Parent reference | | node.childNode(withName:recursively:) | entity.findEntity(named:) | Named lookup |

Lighting

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | SCNLight (.omni) | PointLightComponent | Point light | | SCNLight (.directional) | DirectionalLightComponent | Sun/directional light | | SCNLight (.spot) | SpotLightComponent | Cone light | | SCNLight (.area) | No direct equivalent | Use multiple point lights | | SCNLight (.ambient) | EnvironmentResource (IBL) | Image-based lighting preferred | | SCNLight (.probe) | EnvironmentResource | Environment probes | | SCNLight (.IES) | No direct equivalent | Use light intensity profiles |

Camera

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | SCNCamera | PerspectiveCamera entity | Entity with camera component | | camera.fieldOfView | PerspectiveCameraComponent.fieldOfViewInDegrees | Same concept | | camera.zNear / camera.zFar | PerspectiveCameraComponent.near / .far | Clipping planes | | camera.wantsDepthOfField | Post-processing effects | Different mechanism | | camera.motionBlurIntensity | Post-processing effects | Different mechanism | | allowsCameraControl | Custom gesture handling | No built-in orbit camera |

Physics

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | SCNPhysicsBody | PhysicsBodyComponent | Component-based | | .dynamic | .dynamic | Same mode | | .static | .static | Same mode | | .kinematic | .kinematic | Same mode | | SCNPhysicsShape | CollisionComponent / ShapeResource | Separate from body in RealityKit | | categoryBitMask | CollisionGroup | Named groups vs raw bitmasks | | collisionBitMask | CollisionFilter | Filter-based | | contactTestBitMask | CollisionEvents.Began subscription | Event-based contacts | | SCNPhysicsContactDelegate | scene.subscribe(to: CollisionEvents.Began.self) | Combine-style events | | SCNPhysicsField | PhysicsBodyComponent forces | Apply forces directly | | SCNPhysicsJoint | PhysicsJoint | Similar joint types |

Animation

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | SCNAction | entity.move(to:relativeTo:duration:) | Transform animation | | SCNAction.sequence | Animation chaining | Less declarative in RealityKit | | SCNAction.group | Parallel animations | Apply to different entities | | SCNAction.repeatForever | AnimationPlaybackController repeat | Different API | | SCNTransaction (implicit) | No direct equivalent | Explicit animations only | | CAAnimation bridge | entity.playAnimation() | Load from USD | | SCNAnimationPlayer | AnimationPlaybackController | Playback control | | Morph targets | Blend shapes in USD | Load via USD files |

Interaction

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | hitTest(_:options:) | RealityViewContent.entities(at:) | Different API | | Gesture recognizers on SCNView | ManipulationComponent | Built-in drag/rotate/scale | | allowsCameraControl | Custom implementation | No built-in orbit |

AR Integration

| SceneKit | RealityKit | Notes | |----------|-----------|-------| | ARSCNView | RealityView + AnchorEntity | Legacy → modern | | ARSCNViewDelegate | AnchorEntity auto-tracking | Event-driven | | renderer(_:didAdd:for:) | AnchorEntity(.plane) | Declarative anchoring | | ARWorldTrackingConfiguration | SpatialTrackingSession | iOS 18+ |


Part 2: Scene Graph API

SCNScene

// Loading
let scene = SCNScene(named: "scene.usdz")!
let scene = try SCNScene(url: url, options: [
    .checkConsistency: true,
    .convertToYUp: true
])

// Properties
scene.rootNode                    // Root of node hierarchy
scene.background.contents        // Skybox (UIImage, UIColor, MDLSkyCubeTexture)
scene.lightingEnvironment.contents // IBL environment map
scene.fogStartDistance            // Fog near
scene.fogEndDistance              // Fog far
scene.fogColor                   // Fog color
scene.isPaused                   // Pause simulation

SCNNode

// Creation
let node = SCNNode()
let node = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))

// Transform
node.position = SCNVector3(x, y, z)
node.eulerAngles = SCNVector3(pitch, yaw, roll)
node.scale = SCNVector3(1, 1, 1)
node.simdPosition = SIMD3<Float>(x, y, z)  // SIMD variants available
node.pivot = SCNMatrix4MakeTranslation(0, -0.5, 0) // Offset pivot point

// Visibility
node.isHidden = false
node.opacity = 1.0
node.castsShadow = true
node.renderingOrder = 0   // Lower = rendered first

// Hierarchy
node.addChildNode(child)
node.removeFromParentNode()
node.childNodes
node.childNode(withName: "name", recursively: true)
node.enumerateChildNodes { child, stop in }

Part 3: Materials

Lighting Models

| Model | Description | Use Case | |-------|-------------|----------| | .physicallyBased | PBR metallic-roughness | Realistic rendering (recommended) | | .blinn | Blinn-Phong specular | Simple shiny surfaces | | .phong | Phong specular | Classic specular highlight | | .lambert | Diffuse only, no specular | Matte surfaces | | .constant | Unlit, flat color | UI elements, debug visualization | | .shadowOnly | Invisible, receives shadows | AR ground plane |

Material Properties

let mat = SCNMaterial()
mat.lightingModel = .physicallyBased

// Textures or scalar values
mat.diffuse.contents = UIImage(named: "albedo")    // Base color
mat.metalness.contents = 0.0                        // 0 = dielectric, 1 = metal
mat.roughness.contents = 0.5                        // 0 = mirror, 1 = rough
mat.normal.contents = UIImage(named: "normal")      // Normal map
mat.ambientOcclusion.contents = UIImage(named: "ao") // AO map
mat.emission.contents = UIColor.blue                // Glow
mat.displacement.contents = UIImage(named: "height") // Height map

// Options
mat.isDoubleSided = false        // Render both sides
mat.writesToDepthBuffer = true
mat.readsFromDepthBuffer = true
mat.blendMode = .alpha           // .add, .subtract, .multiply, .screen
mat.transparencyMode = .aOne     // .rgbZero for pre-multiplied alpha

Part 4: Physics

Body Types and Properties

// Dynamic body with custom shape
let shape = SCNPhysicsShape(geometry: SCNSphere(radius: 0.5), options: nil)
let body = SCNPhysicsBody(type: .dynamic, shape: shape)
body.mass = 1.0
body.friction = 0.5
body.restitution = 0.3       // Bounciness
body.damping = 0.1            // Linear damping
body.angularDamping = 0.1     // Angular damping
body.isAffectedByGravity = true
body.allowsResting = true     // Sleep optimization
node.physicsBody = body

// Compound shapes
let compound = SCNPhysicsShape(shapes: [shape1, shape2],
    transforms: [transform1, transform2])

// Concave (static only)
let concave = SCNPhysicsShape(geometry: mesh, options: [
    .type: SCNPhysicsShape.ShapeType.concavePolyhedron
])

Joint Types

| Joint | Description | |-------|-------------| | SCNPhysicsHingeJoint | Single-axis rotation (door) | | SCNPhysicsBallSocketJoint | Free rotation around point (pendulum) | | SCNPhysicsSliderJoint | Linear movement along axis (drawer) | | SCNPhysicsConeTwistJoint | Limited rotation (ragdoll limb) |


Part 5: Animation API

SCNAction Catalog

| Category | Actions | |----------|---------| | Movement | move(by:duration:), move(to:duration:) | | Rotation | rotate(by:around:duration:), rotateTo(x:y:z:duration:) | | Scale | scale(by:duration:), scale(to:duration:) | | Fade | fadeIn(duration:), fadeOut(duration:), fadeOpacity(to:duration:) | | Visibility | hide(), unhide() | | Audio | playAudio(source:waitForCompletion:) | | Custom | run { node in }, customAction(duration:action:) | | Composition | sequence([]), group([]), repeat(_:count:), repeatForever(_:) | | Control | wait(duration:), removeFromParentNode() |

Timing Functions

action.timingMode = .linear        // Default
action.timingMode = .easeIn        // Slow start
action.timingMode = .easeOut       // Slow end
action.timingMode = .easeInEaseOut // Slow start and end
action.timingFunction = { t in     // Custom curve
    return t * t  // Quadratic ease-in
}

Part 6: Constraints

| Constraint | Purpose | |------------|---------| | SCNLookAtConstraint | Node always faces target | | SCNBillboardConstraint | Node always faces camera | | SCNDistanceConstraint | Maintains min/max distance | | SCNReplicatorConstraint | Copies transform of target | | SCNAccelerationConstraint | Smooths transform changes | | SCNSliderConstraint | Locks to axis | | SCNIKConstraint | Inverse kinematics chain |

let lookAt = SCNLookAtConstraint(target: targetNode)
lookAt.isGimbalLockEnabled = true  // Prevent roll
lookAt.influenceFactor = 0.8       // Partial constraint
node.constraints = [lookAt]

In RealityKit: No direct constraint system. Implement with System update logic or entity.look(at:from:relativeTo:).


Part 7: Scene Configuration

SCNView Configuration

| Property | Default | Description | |----------|---------|-------------| | antialiasingMode | .multisampling4X | MSAA level | | preferredFramesPerSecond | 60 | Target frame rate | | allowsCameraControl | false | Built-in orbit/pan/zoom | | autoenablesDefaultLighting | false | Add default light if none | | showsStatistics | false | FPS/node/draw count overlay | | isTemporalAntialiasingEnabled | false | TAA smoothing | | isJitteringEnabled | false | Temporal jitter for TAA | | debugOptions | [] | .showPhysicsShapes, .showBoundingBoxes, .renderAsWireframe |


Resources

WWDC: 2014-609, 2014-610, 2017-604, 2019-612

Docs: /scenekit, /scenekit/scnscene, /scenekit/scnnode, /scenekit/scnmaterial, /scenekit/scnphysicsbody, /scenekit/scnaction

Skills: axiom-scenekit, axiom-realitykit, axiom-realitykit-ref