axiom-scenekit-ref

Pass

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

@CharlesWiltgen
MIT2/22/2026
(0)
460stars
0downloads
1views

Install Skill

Skills are third-party code from public GitHub repositories. SkillHub scans for known malicious patterns but cannot guarantee safety. Review the source code before installing.

Install globally (user-level):

npx skillhub install CharlesWiltgen/Axiom/axiom-scenekit-ref

Install in current project:

npx skillhub install CharlesWiltgen/Axiom/axiom-scenekit-ref --project

Suggested path: ~/.claude/skills/axiom-scenekit-ref/

SKILL.md Content

---
name: axiom-scenekit-ref
description: SceneKit → RealityKit concept mapping, complete API cross-reference for migration, scene graph API, materials, lighting, camera, physics, animation, constraints
license: MIT
compatibility: [iOS 8+, macOS 10.8+, tvOS 9+]
metadata:
  version: "1.0.0"
---

# 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

```swift
// 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

```swift
// 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

```swift
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

```swift
// 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

```swift
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 |

```swift
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
axiom-scenekit-ref | SkillHub | SkillHub