Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example: use SceneKit in custom layer #154

Merged
merged 4 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
0333B84F25ED942600D667C9 /* SceneKitExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0333B84E25ED942600D667C9 /* SceneKitExample.swift */; };
03BB33F925EDA19200109B28 /* 34M_17.dae in Resources */ = {isa = PBXBuildFile; fileRef = 03BB33F825EDA19200109B28 /* 34M_17.dae */; };
0706C4A625B1181A008733C0 /* TerrainExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0706C4A525B1181A008733C0 /* TerrainExample.swift */; };
073475D725AFAE520049B0B8 /* CustomLayerExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 073475D625AFAE520049B0B8 /* CustomLayerExample.swift */; };
077C4EFA252F7E89007636F1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 077C4EF8252F7E89007636F1 /* LaunchScreen.storyboard */; };
Expand Down Expand Up @@ -103,6 +105,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
0333B84E25ED942600D667C9 /* SceneKitExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneKitExample.swift; sourceTree = "<group>"; };
03BB33F825EDA19200109B28 /* 34M_17.dae */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml.dae; name = 34M_17.dae; path = ../../../../../Downloads/34M_17.dae; sourceTree = "<group>"; };
0706C4A525B1181A008733C0 /* TerrainExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerrainExample.swift; sourceTree = "<group>"; };
072C79EA25685D23006E47A7 /* SnapshotterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotterExample.swift; sourceTree = "<group>"; };
073475D625AFAE520049B0B8 /* CustomLayerExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLayerExample.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -249,6 +253,7 @@
A4211CE52592549900D215B4 /* RestrictCoordinateBoundsExample.swift */,
0CE8ED8B2589285C0066E56C /* ModelExample.swift */,
073475D625AFAE520049B0B8 /* CustomLayerExample.swift */,
0333B84E25ED942600D667C9 /* SceneKitExample.swift */,
0C52BA9725AF8C880054ECA8 /* PuckModelLayerExample.swift */,
0706C4A525B1181A008733C0 /* TerrainExample.swift */,
0CC4ECE925B8AD3000F998B8 /* CustomLocationIndicatorLayerExample.swift */,
Expand Down Expand Up @@ -338,6 +343,7 @@
07B071D12547CF50007F2865 /* Sample Data */ = {
isa = PBXGroup;
children = (
03BB33F825EDA19200109B28 /* 34M_17.dae */,
07B071D22547CFC3007F2865 /* GeoJSONSourceExample.geojson */,
0CE8ED87258928200066E56C /* race_car_model.gltf */,
0C52BA9B25AFB5940054ECA8 /* arrow.gltf */,
Expand Down Expand Up @@ -465,6 +471,7 @@
buildActionMask = 2147483647;
files = (
07B071D32547CFC3007F2865 /* GeoJSONSourceExample.geojson in Resources */,
03BB33F925EDA19200109B28 /* 34M_17.dae in Resources */,
0C78AC2F25BF72C70057F570 /* GradientLine.geojson in Resources */,
0C52BA9C25AFB5940054ECA8 /* arrow.gltf in Resources */,
07DC84422538B1F100F4AF14 /* Assets.xcassets in Resources */,
Expand Down Expand Up @@ -560,6 +567,7 @@
CAC195B725AC098A00F69FEA /* CameraUIViewAnimationExample.swift in Sources */,
0C78AC2925BF70E40057F570 /* LineGradientExample.swift in Sources */,
CADCF72B2584990E0065C51B /* FlyToExample.swift in Sources */,
0333B84F25ED942600D667C9 /* SceneKitExample.swift in Sources */,
CADCF72D2584990E0065C51B /* GeoJSONSourceExample.swift in Sources */,
CADCF72E2584990E0065C51B /* MapViewExample.swift in Sources */,
58A3C0C925C4B93600CAE5F0 /* AnimateGeoJSONLineExample.swift in Sources */,
Expand Down Expand Up @@ -773,6 +781,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_WORKSPACE = YES;
};
name = Release;
};
Expand Down
882 changes: 882 additions & 0 deletions Examples/Examples/All Examples/Sample Data/34M_17.dae

Large diffs are not rendered by default.

230 changes: 230 additions & 0 deletions Examples/Examples/All Examples/SceneKitExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import UIKit
import SceneKit
import MapboxMaps

@objc(SceneKitExample)

public class SceneKitExample: UIViewController, ExampleProtocol, CustomLayerHost {

internal var mapView: MapView!
public var peer: MBXPeerWrapper?

let modelOrigin = CLLocationCoordinate2D(latitude: -35.39847, longitude: 148.9819)
var renderer: SCNRenderer!
var scene: SCNScene!
var modelNode: SCNNode!
var cameraNode: SCNNode!
var textNode: SCNNode!
var useCPUOcclusion = false

override public func viewDidLoad() {
super.viewDidLoad()

self.mapView = MapView(with: view.bounds, resourceOptions: resourceOptions())
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapView)
mapView.cameraManager.setCamera(
centerCoordinate: self.modelOrigin,
zoom: 18,
bearing: 180,
pitch: 60
)
self.mapView.update { (mapOptions) in
mapOptions.render.presentsWithTransaction = true
}

self.mapView.on(.styleLoadingFinished) { [weak self] _ in
self?.addModelAndTerrain()
}
}

override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}

func addModelAndTerrain() {
try! mapView.__map.addStyleCustomLayer(forLayerId: "Custom",
layerHost: self,
layerPosition: LayerPosition(above: nil, below: "waterway-label", at: nil))

var demSource = RasterDemSource()
demSource.url = "mapbox://mapbox.mapbox-terrain-dem-v1"
demSource.tileSize = 512
demSource.maxzoom = 14.0
mapView.style.addSource(source: demSource, identifier: "mapbox-dem")
let terrain = Terrain(sourceId: "mapbox-dem")
_ = self.mapView.style.setTerrain(terrain)

var skyLayer = SkyLayer(id: "sky-layer")
skyLayer.paint?.skyType = .constant(.atmosphere)
skyLayer.paint?.skyAtmosphereSun = .constant([0, 0])
skyLayer.paint?.skyAtmosphereSunIntensity = .constant(15.0)

_ = self.mapView.style.addLayer(layer: skyLayer)

// Re-use terrain source for hillshade
let map = self.mapView.__map!
let properties = [
"id": "terrain_hillshade",
"type": "hillshade",
"source": "mapbox-dem",
"hillshade-illumination-anchor": "map"
] as [ String: Any ]

try! map.addStyleLayer(forProperties: properties,
layerPosition: LayerPosition(above: nil, below: "water", at: nil))
}

public func renderingWillStart(_ metalDevice: MTLDevice, colorPixelFormat: UInt, depthStencilPixelFormat: UInt) {
renderer = SCNRenderer(device: metalDevice)
scene = SCNScene()
renderer.scene = scene

modelNode = SCNScene(named: "34M_17")?.rootNode.clone()
scene.rootNode.addChildNode(modelNode)

cameraNode = SCNNode()
let camera = SCNCamera()
cameraNode.camera = camera
camera.usesOrthographicProjection = false
scene.rootNode.addChildNode(cameraNode)
renderer.pointOfView = cameraNode
self.setupLight()
// In order to use depth occlusion, align with gl-native Z handling (doesn't use reverse Z).
if #available(iOS 13.0, *) {
renderer.usesReverseZ = false
} else {
// Fallback on earlier versions, disable depth in render()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens in these cases? Should we disable this example completely if < iOS 13?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should enable it, just there is a need to use CPU side occlusion (the same approach that could be used when geo-referencing UI elements to map). The idea is to disable depth buffer (now disabled in demo) and show or hide model with animated fade in / fade out (similar to behavior of markers in gl-js).

90252440-2cde5000-de48-11ea-8f3f-6bffa52e07e1

As we cannot have occlusion done yet, current behavior on iOS < 13 is like here (model always visible):

Screen.Recording.2021-03-03.at.19.28.43.mov

self.useCPUOcclusion = true
}
}

func setupLight() {
// Ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.light?.color = UIColor(white: 0.4, alpha: 1.0)
modelNode.addChildNode(ambientLight)

let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = SCNLight.LightType.directional
lightNode.light?.orthographicScale = 30
lightNode.light?.color = UIColor(white: 0.8, alpha: 1.0)
lightNode.position = SCNVector3Make(-50, 100, 100)
lightNode.light?.zNear = 1
lightNode.light?.zFar = 1000
lightNode.light?.intensity = 2000
lightNode.look(at: modelNode.worldPosition)
modelNode.addChildNode(lightNode)
let pointNode = SCNNode()
pointNode.light = SCNLight()
pointNode.light?.type = SCNLight.LightType.omni
pointNode.light?.intensity = 3000
pointNode.position = SCNVector3Make(0, 25, 0)
modelNode.addChildNode(pointNode)
}

internal func makeTranslationMatrix(tx: Double, ty: Double, tz: Double) -> simd_double4x4 {
var matrix = matrix_identity_double4x4

matrix[3, 0] = tx
matrix[3, 1] = ty
matrix[3, 2] = tz

return matrix
}

internal func makeScaleMatrix(xScale: Double, yScale: Double, zScale: Double) -> simd_double4x4 {
var matrix = matrix_identity_double4x4

matrix[0, 0] = xScale
matrix[1, 1] = yScale
matrix[2, 2] = zScale

return matrix
}

public func render(_ parameters: CustomLayerRenderParameters, mtlCommandBuffer: MTLCommandBuffer, mtlRenderPassDescriptor: MTLRenderPassDescriptor) {
guard let colorTexture = mtlRenderPassDescriptor.colorAttachments[0].texture else {
return
}
let m = parameters.projectionMatrix

// It is essential to use double precision for computation below: using simd instead
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you say why - presumably because of jittering with floats?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, mercator coordinate addresses whole world and there's just not enough precision if using 32-bit floating point computation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add that to the comment please?

// of SceneKit matrix operations.
var transformSimd = matrix_identity_double4x4
transformSimd[0, 0] = m[0].doubleValue
transformSimd[0, 1] = m[1].doubleValue
transformSimd[0, 2] = m[2].doubleValue
transformSimd[0, 3] = m[3].doubleValue
transformSimd[1, 0] = m[4].doubleValue
transformSimd[1, 1] = m[5].doubleValue
transformSimd[1, 2] = m[6].doubleValue
transformSimd[1, 3] = m[7].doubleValue
transformSimd[2, 0] = m[8].doubleValue
transformSimd[2, 1] = m[9].doubleValue
transformSimd[2, 2] = m[10].doubleValue
transformSimd[2, 3] = m[11].doubleValue
transformSimd[3, 0] = m[12].doubleValue
transformSimd[3, 1] = m[13].doubleValue
transformSimd[3, 2] = m[14].doubleValue
transformSimd[3, 3] = m[15].doubleValue

// Model is using metric unit system: scale x and y from meters to mercator and keep z is in meters.
let meterInMercatorCoordinateUnits = try! 1.0 / (Projection.getMetersPerPixelAtLatitude(forLatitude: modelOrigin.latitude, zoom: parameters.zoom))
let modelScale = makeScaleMatrix(xScale: meterInMercatorCoordinateUnits, yScale: -meterInMercatorCoordinateUnits, zScale: 1)

// Translate scaled model to model origin (in web mercator coordinates) and elevate to model origin's altitude (in meters).
let origin = try! Projection.project(for: modelOrigin, zoomScale: pow(2, parameters.zoom))
var elevation = 0.0
if let elevationData = parameters.elevationData, let elevationValue = elevationData.getElevationFor(self.modelOrigin) {
elevation = elevationValue.doubleValue
}
let translateModel = makeTranslationMatrix(tx: origin.x, ty: origin.y, tz: elevation)

let transform = transformSimd * translateModel * modelScale

let scnMat = SCNMatrix4(
m11: Float(transform[0, 0]),
astojilj marked this conversation as resolved.
Show resolved Hide resolved
m12: Float(transform[0, 1]),
m13: Float(transform[0, 2]),
m14: Float(transform[0, 3]),
m21: Float(transform[1, 0]),
m22: Float(transform[1, 1]),
m23: Float(transform[1, 2]),
m24: Float(transform[1, 3]),
m31: Float(transform[2, 0]),
m32: Float(transform[2, 1]),
m33: Float(transform[2, 2]),
m34: Float(transform[2, 3]),
m41: Float(transform[3, 0]),
m42: Float(transform[3, 1]),
m43: Float(transform[3, 2]),
m44: Float(transform[3, 3])
)
cameraNode.camera!.projectionTransform = scnMat

// flush automatic SceneKit transaction as SceneKit animation is not running and
// there's need to use transform matrix in this frame (not to have it used with delay).
SCNTransaction.flush()

if self.useCPUOcclusion {
mtlRenderPassDescriptor.depthAttachment = nil
mtlRenderPassDescriptor.stencilAttachment = nil
// Example uses depth buffer to occlude model when e.g. behind the hill.
// If depth buffer (SCNRenderer.usesReverseZ = false) is not available, or if wished to
// to indicate that model is occluded or e.g. implement fade out / fade in model occlusion,
// the example here needs to provide CPU side occlusion implementation, too.
// TODO: this is blocked on https://github.com/mapbox/mapbox-maps-ios/issues/155
}
renderer.render(withViewport: CGRect(x: 0, y: 0, width: CGFloat(colorTexture.width), height: CGFloat(colorTexture.height)), commandBuffer: mtlCommandBuffer, passDescriptor: mtlRenderPassDescriptor)

}

public func renderingWillEnd() {
// The below line is used for internal testing purposes only.
self.finish()
}
}
3 changes: 3 additions & 0 deletions Examples/Examples/Models/Examples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ public struct Examples {
Example(title: "Show 3D terrain",
description: "Show realistic elevation by enabling terrain.",
type: TerrainExample.self),
Example(title: "SceneKit rendering on map",
description: "Use custom layer to render SceneKit model over terrain.",
type: SceneKitExample.self),
Example(title: "Customize the location puck",
description: "Use a different asset to represent the puck.",
type: CustomLocationIndicatorLayerExample.self),
Expand Down