ahmed needs to attract extra consideration to this query:
I need assistance locking the rotation of the globe to solely horizontal with +- 30 levels of vertical freedom.
Supply: https://github.com/DominicHolmes/dot-globe
Hi there I’m attempting to make the 3d globe from the repo above lock concerning the z axis. I would like the globe to solely rotate horizontally and ignore undesirable rotations. Whether it is attainable Id like to permit as much as -30 levels of rotation to the underside of the globe, and 30 levels of rotation to the highest of the globe. Im not very expert with SCNScene or SCNCamera so Id admire the assistance. At present horizontal swipes additionally rotate the entire globe as a substitute of spinning it.
Within the repo the code under was added within the perform setupCamera to stop undesirable globe rotations. However this doesn’t work.
constraint.isGimbalLockEnabled = true
cameraNode.constraints = [constraint]
sceneView.scene?.rootNode.addChildNode(cameraNode)
I additionally tried doing this however it additionally did not work.
let constraint = SCNTransformConstraint.orientationConstraint(inWorldSpace: true) { (_, orientation) -> SCNQuaternion in
// Maintain the identical orientation round x and z axes, permit rotation round y-axis
return SCNQuaternion(x: 0, y: orientation.y, z: 0, w: orientation.w)
}
Right here is the code to arrange the digicam (the place these constraints must be added). The remainder of the code is within the repository above. All the pieces related to this query and code is within the file linked above.
import SwiftUI
import SceneKit
typealias GenericControllerRepresentable = UIViewControllerRepresentable
@obtainable(iOS 13.0, *)
non-public struct GlobeViewControllerRepresentable: GenericControllerRepresentable {
var particles: SCNParticleSystem? = nil
//@Binding public var showProf: Bool
func makeUIViewController(context: Context) -> GlobeViewController {
let globeController = GlobeViewController(earthRadius: 1.0)//, showProf: $showProf
updateGlobeController(globeController)
return globeController
}
func updateUIViewController(_ uiViewController: GlobeViewController, context: Context) {
updateGlobeController(uiViewController)
}
non-public func updateGlobeController(_ globeController: GlobeViewController) {
globeController.dotSize = CGFloat(0.005)
globeController.enablesParticles = true
if let particles = particles {
globeController.particles = particles
}
}
}
@obtainable(iOS 13.0, *)
public struct GlobeView: View {
//@Binding public var showProf: Bool
public var physique: some View {
GlobeViewControllerRepresentable()//showProf: $showProf
}
}
import Basis
import SceneKit
import CoreImage
import SwiftUI
import MapKit
public typealias GenericController = UIViewController
public typealias GenericColor = UIColor
public typealias GenericImage = UIImage
public class GlobeViewController: GenericController {
public var earthNode: SCNNode!
non-public var sceneView : SCNView!
non-public var cameraNode: SCNNode!
non-public var worldMapImage : CGImage {
guard let path = Bundle.module.path(forResource: “earth-dark”, ofType: “jpg”) else { fatalError(“Couldn’t find world map picture.”) }
guard let picture = GenericImage(contentsOfFile: path)?.cgImage else { fatalError() }
return picture
}
non-public lazy var imgData: CFData = {
guard let imgData = worldMapImage.dataProvider?.knowledge else { fatalError(“Couldn’t fetch knowledge from world map picture.”) }
return imgData
}()
public var particles: SCNParticleSystem? {
didSet {
if let particles = particles {
sceneView.scene?.rootNode.removeAllParticleSystems()
sceneView.scene?.rootNode.addParticleSystem(particles)
}
}
}
public init(earthRadius: Double) {
self.earthRadius = earthRadius
tremendous.init(nibName: nil, bundle: nil)
}
public init(earthRadius: Double, dotCount: Int) {
self.earthRadius = earthRadius
self.dotCount = dotCount
tremendous.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError(“init(coder:) has not been applied”)
}
public override func viewDidLoad() {
tremendous.viewDidLoad()
setupScene()
setupParticles()
setupCamera()
setupGlobe()
setupDotGeometry()
}
non-public func setupScene() {
var scene = SCNScene()
sceneView = SCNView(body: view.body)
sceneView.scene = scene
sceneView.showsStatistics = true
sceneView.backgroundColor = .black
sceneView.allowsCameraControl = true
self.view.addSubview(sceneView)
}
non-public func setupParticles() {
guard let stars = SCNParticleSystem(named: “StarsParticles.scnp”, inDirectory: nil) else { return }
stars.isLightingEnabled = false
if sceneView != nil {
sceneView.scene?.rootNode.addParticleSystem(stars)
}
}
non-public func setupCamera() {
self.cameraNode = SCNNode()
cameraNode.digicam = SCNCamera()
cameraNode.place = SCNVector3(x: 0, y: 0, z: 5)
sceneView.scene?.rootNode.addChildNode(cameraNode)
}
non-public func setupGlobe() {
self.earthNode = EarthNode(radius: earthRadius, earthColor: earthColor, earthGlow: glowColor, earthReflection: reflectionColor)
sceneView.scene?.rootNode.addChildNode(earthNode)
}
non-public func setupDotGeometry() {
let textureMap = generateTextureMap(dots: dotCount, sphereRadius: CGFloat(earthRadius))
let newYork = CLLocationCoordinate2D(latitude: 44.0682, longitude: -121.3153)
let newYorkDot = closestDotPosition(to: newYork, in: textureMap)
let dotColor = GenericColor(white: 1, alpha: 1)
let oceanColor = GenericColor(cgColor: UIColor.systemRed.cgColor)
let highlightColor = GenericColor(cgColor: UIColor.systemRed.cgColor)
let threshold: CGFloat = 0.03
let dotGeometry = SCNSphere(radius: dotRadius)
dotGeometry.firstMaterial?.diffuse.contents = dotColor
dotGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.fixed
let highlightGeometry = SCNSphere(radius: dotRadius * 5)
highlightGeometry.firstMaterial?.diffuse.contents = highlightColor
highlightGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.fixed
let oceanGeometry = SCNSphere(radius: dotRadius)
oceanGeometry.firstMaterial?.diffuse.contents = oceanColor
oceanGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.fixed
var positions = [SCNVector3]()
var dotNodes = [SCNNode]()
var highlightedNode: SCNNode? = nil
for i in 0…textureMap.depend – 1 {
let u = textureMap[i].x
let v = textureMap[i].y
let pixelColor = self.getPixelColor(x: Int(u), y: Int(v))
let isHighlight = u == newYorkDot.x && v == newYorkDot.y
if (isHighlight) {
let dotNode = SCNNode(geometry: highlightGeometry)
dotNode.identify = “NewYorkDot”
dotNode.place = textureMap[i].place
positions.append(dotNode.place)
dotNodes.append(dotNode)
highlightedNode = dotNode
} else if (pixelColor.crimson < threshold && pixelColor.inexperienced < threshold && pixelColor.blue < threshold) {
let dotNode = SCNNode(geometry: dotGeometry)
dotNode.place = textureMap[i].place
positions.append(dotNode.place)
dotNodes.append(dotNode)
}
}
DispatchQueue.essential.async {
let dotPositions = positions as NSArray
let dotIndices = NSArray()
let supply = SCNGeometrySource(vertices: dotPositions as! [SCNVector3])
let ingredient = SCNGeometryElement(indices: dotIndices as! [Int32], primitiveType: .level)
let pointCloud = SCNGeometry(sources: [source], components: [element])
let pointCloudNode = SCNNode(geometry: pointCloud)
for dotNode in dotNodes {
pointCloudNode.addChildNode(dotNode)
}
self.sceneView.scene?.rootNode.addChildNode(pointCloudNode)
//this strikes the digicam to indicate the highest of the earth
DispatchQueue.essential.asyncAfter(deadline: .now() + 3) {
if let highlightedNode = highlightedNode {
self.alignPointToPositiveZ(for: pointCloudNode, targetPoint: highlightedNode.place)
}
}
}
}
func alignPointToPositiveZ(for sphereNode: SCNNode, targetPoint: SCNVector3) {
// Compute normalized vector from Earth’s heart to the goal level
let targetDirection = targetPoint.normalized()
// Compute quaternion rotation
let up = SCNVector3(0, 0, 1)
let rotationQuaternion = SCNQuaternion.fromVectorRotate(from: up, to: targetDirection)
sphereNode.orientation = rotationQuaternion
}
typealias MapDot = (place: SCNVector3, x: Int, y: Int)
non-public func generateTextureMap(dots: Int, sphereRadius: CGFloat) -> [MapDot] {
let phi = Double.pi * (sqrt(5) – 1)
var positions = [MapDot]()
for i in 0..<dots {
let y = 1.0 – (Double(i) / Double(dots – 1)) * 2.0 // y is 1 to -1
let radiusY = sqrt(1 – y * y)
let theta = phi * Double(i) // Golden angle increment
let x = cos(theta) * radiusY
let z = sin(theta) * radiusY
let vector = SCNVector3(x: Float(sphereRadius * x),
y: Float(sphereRadius * y),
z: Float(sphereRadius * z))
let pixel = equirectangularProjection(level: Point3D(x: x, y: y, z: z),
imageWidth: 2048,
imageHeight: 1024)
let place = MapDot(place: vector, x: pixel.u, y: pixel.v)
positions.append(place)
}
return positions
}
struct Point3D {
let x: Double
let y: Double
let z: Double
}
struct Pixel {
let u: Int
let v: Int
}
func equirectangularProjection(level: Point3D, imageWidth: Int, imageHeight: Int) -> Pixel {
let theta = asin(level.y)
let phi = atan2(level.x, level.z)
let u = Double(imageWidth) / (2.0 * .pi) * (phi + .pi)
let v = Double(imageHeight) / .pi * (.pi / 2.0 – theta)
return Pixel(u: Int(u), v: Int(v))
}
non-public func distanceBetweenPoints(x1: Int, y1: Int, x2: Int, y2: Int) -> Double {
let dx = Double(x2 – x1)
let dy = Double(y2 – y1)
return sqrt(dx * dx + dy * dy)
}
non-public func closestDotPosition(to coordinate: CLLocationCoordinate2D, in positions: [(position: SCNVector3, x: Int, y: Int)]) -> (x: Int, y: Int) {
let pixelPositionDouble = getEquirectangularProjectionPosition(for: coordinate)
let pixelPosition = (x: Int(pixelPositionDouble.x), y: Int(pixelPositionDouble.y))
let nearestDotPosition = positions.min { p1, p2 in
distanceBetweenPoints(x1: pixelPosition.x, y1: pixelPosition.y, x2: p1.x, y2: p1.y) <
distanceBetweenPoints(x1: pixelPosition.x, y1: pixelPosition.y, x2: p2.x, y2: p2.y)
}
return (x: nearestDotPosition?.x ?? 0, y: nearestDotPosition?.y ?? 0)
}
/// Convert a coordinate to an (x, y) coordinate on the world map picture
non-public func getEquirectangularProjectionPosition(
for coordinate: CLLocationCoordinate2D
) -> CGPoint {
let imageHeight = CGFloat(worldMapImage.peak)
let imageWidth = CGFloat(worldMapImage.width)
// Normalize longitude to [0, 360). Longitude in MapKit is [-180, 180)
let normalizedLong = coordinate.longitude + 180
// Calculate x and y positions
let xPosition = (normalizedLong / 360) * imageWidth
// Note: Latitude starts from top, hence the `-` sign
let yPosition = (-(coordinate.latitude – 90) / 180) * imageHeight
return CGPoint(x: xPosition, y: yPosition)
}
private func getPixelColor(x: Int, y: Int) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(imgData)
let pixelInfo: Int = ((worldMapWidth * y) + x) * 4
let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
let g = CGFloat(knowledge[pixelInfo + 1]) / CGFloat(255.0)
let b = CGFloat(knowledge[pixelInfo + 2]) / CGFloat(255.0)
let a = CGFloat(knowledge[pixelInfo + 3]) / CGFloat(255.0)
return (r, g, b, a)
}
}