moogfest18-waveform/Waveform/GameViewController.swift

165 lines
6 KiB
Swift
Raw Normal View History

2018-09-16 22:04:00 -07:00
//
// GameViewController.swift
// Waveform
//
// Created by Eryn Wells on 9/16/18.
// Copyright © 2018 Eryn Wells. All rights reserved.
//
import SceneKit
import QuartzCore
2018-09-18 06:45:49 -07:00
class GameViewController: NSViewController, SCNSceneRendererDelegate {
static let numberOfBallsPerSide = 41
private var balls = [SCNNode]()
2018-09-16 22:04:00 -07:00
override func viewDidLoad() {
super.viewDidLoad()
2018-09-18 06:45:49 -07:00
guard let sceneView = view as? SCNView else {
fatalError("My view isn't a SCNView!")
}
sceneView.delegate = self
sceneView.rendersContinuously = true
sceneView.preferredFramesPerSecond = 30
2018-09-16 22:04:00 -07:00
// create a new scene
2018-09-18 06:45:49 -07:00
let scene = SCNScene(named: "art.scnassets/blank.scn")!
2018-09-16 22:04:00 -07:00
// create and add a camera to the scene
let cameraNode = SCNNode()
2018-09-18 06:45:49 -07:00
let camera = SCNCamera()
cameraNode.camera = camera
2018-09-16 22:04:00 -07:00
scene.rootNode.addChildNode(cameraNode)
// place the camera
2018-09-18 06:45:49 -07:00
cameraNode.position = SCNVector3(x: CGFloat(GameViewController.numberOfBallsPerSide / 7),
y: 1.5,
z: CGFloat(GameViewController.numberOfBallsPerSide / 3))
cameraNode.look(at: SCNVector3())
2018-09-16 22:04:00 -07:00
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
2018-09-18 06:45:49 -07:00
let sphereContainer = SCNNode()
scene.rootNode.addChildNode(sphereContainer)
if let sphere = scene.rootNode.childNode(withName: "sphere", recursively: false) {
// Remove it. We're going to copy it all over the place.
sphere.removeFromParentNode()
for i in 0..<GameViewController.numberOfBallsPerSide {
for j in 0..<GameViewController.numberOfBallsPerSide {
let ijSphere = sphere.clone()
balls.append(ijSphere)
ijSphere.name = "sphere\(i),\(j)"
2018-09-18 06:45:49 -07:00
ijSphere.worldPosition = SCNVector3(x: CGFloat(-GameViewController.numberOfBallsPerSide / 2) + CGFloat(i),
y: 0.0,
z: CGFloat(-GameViewController.numberOfBallsPerSide / 2) + CGFloat(j))
sphereContainer.addChildNode(ijSphere)
}
}
}
sphereContainer.runAction(SCNAction.repeatForever(SCNAction.rotate(by: CGFloat.pi, around: SCNVector3(0, 1, 0), duration: 40.0)))
2018-09-16 22:04:00 -07:00
// set the scene to the view
2018-09-18 06:45:49 -07:00
sceneView.scene = scene
2018-09-16 22:04:00 -07:00
// allows the user to manipulate the camera
2018-09-18 06:45:49 -07:00
sceneView.allowsCameraControl = false
2018-09-16 22:04:00 -07:00
// show statistics such as fps and timing information
2018-09-18 06:45:49 -07:00
sceneView.showsStatistics = true
2018-09-16 22:04:00 -07:00
// configure the view
2018-09-18 06:45:49 -07:00
sceneView.backgroundColor = NSColor.black
2018-09-16 22:04:00 -07:00
// Add a click gesture recognizer
let clickGesture = NSClickGestureRecognizer(target: self, action: #selector(handleClick(_:)))
2018-09-18 06:45:49 -07:00
var gestureRecognizers = sceneView.gestureRecognizers
2018-09-16 22:04:00 -07:00
gestureRecognizers.insert(clickGesture, at: 0)
2018-09-18 06:45:49 -07:00
sceneView.gestureRecognizers = gestureRecognizers
2018-09-16 22:04:00 -07:00
}
@objc
func handleClick(_ gestureRecognizer: NSGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are clicked
let p = gestureRecognizer.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// get its material
let material = result.node.geometry!.firstMaterial!
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.emission.contents = NSColor.black
SCNTransaction.commit()
}
material.emission.contents = NSColor.red
SCNTransaction.commit()
}
}
2018-09-18 06:45:49 -07:00
func delayForSphereAt(x: Int, y: Int) -> Double {
return 0.05 * Double(x) + 0.03 * Double(y)
}
// MARK: - SCNRendererDelegate
var maxY = 0.0
var minY = 0.0
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
for z in 0..<GameViewController.numberOfBallsPerSide {
for x in 0..<GameViewController.numberOfBallsPerSide {
let idx = z * GameViewController.numberOfBallsPerSide + x
let node = balls[idx]
2018-09-18 06:51:25 -07:00
let delay = delayForSphereAt(x: x, y: z)
let inputX = 0.5 * time + delay
2018-09-18 06:45:49 -07:00
let y = sin(inputX) + sin(2.0 * inputX) + sin(4.0 * inputX) + sin(8.0 * inputX)
node.worldPosition.y = CGFloat(y)
maxY = max(y, maxY)
minY = min(y, minY)
let scale = CGFloat(map(y, inMin: minY, inMax: maxY, outMin: 0.0, outMax: 1.0))
node.scale = SCNVector3(x: scale, y: scale, z: scale)
}
}
}
func map(_ x: Double, inMin: Double, inMax: Double, outMin: Double, outMax: Double) -> Double {
return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
2018-09-16 22:04:00 -07:00
}