2015-12-30 02:51:22 -08:00
|
|
|
//
|
2015-12-30 18:47:13 -08:00
|
|
|
// VisualizationViewController.swift
|
2015-12-30 02:51:22 -08:00
|
|
|
// XIONControlPanel
|
|
|
|
|
//
|
|
|
|
|
// Created by Charles Magahern on 12/29/15.
|
|
|
|
|
// Copyright © 2015 XION. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Darwin
|
|
|
|
|
import Foundation
|
|
|
|
|
import GLKit
|
|
|
|
|
import SceneKit
|
|
|
|
|
import UIKit
|
2017-05-13 00:40:03 -07:00
|
|
|
import SceneKit
|
2015-12-30 02:51:22 -08:00
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
let π = CGFloat(Double.pi)
|
2015-12-30 02:51:22 -08:00
|
|
|
|
2016-07-24 00:14:04 -07:00
|
|
|
class VisualizationViewController: UIViewController
|
|
|
|
|
{
|
2017-05-13 00:40:03 -07:00
|
|
|
fileprivate var _scene: SCNScene = SCNScene()
|
|
|
|
|
fileprivate var _sceneView: SCNView?
|
|
|
|
|
fileprivate var _cameraNode: SCNNode = SCNNode()
|
|
|
|
|
fileprivate var _lightNode: SCNNode = SCNNode()
|
|
|
|
|
fileprivate var _cubletsNode: SCNNode = SCNNode()
|
|
|
|
|
fileprivate var _cublets: [SCNNode] = []
|
|
|
|
|
fileprivate var _percentActivated: Float = 0.0
|
2015-12-30 02:51:22 -08:00
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
static fileprivate let cubletsDimensions = 5
|
|
|
|
|
static fileprivate let cubletsSize = 1.0
|
|
|
|
|
static fileprivate let cubletsSpacing = 2.0
|
|
|
|
|
static fileprivate let rotationAnimationKey = "RotationAnimation"
|
2015-12-30 02:51:22 -08:00
|
|
|
|
2015-12-30 16:56:15 -08:00
|
|
|
// MARK: Overrides
|
|
|
|
|
|
2015-12-30 02:51:22 -08:00
|
|
|
override func loadView()
|
|
|
|
|
{
|
2017-09-04 18:31:34 -07:00
|
|
|
let opts = [SCNView.Option.preferredRenderingAPI.rawValue : SCNRenderingAPI.openGLES2.rawValue]
|
2017-05-13 00:40:03 -07:00
|
|
|
let view = SCNView(frame: UIScreen.main.bounds, options: opts)
|
|
|
|
|
view.backgroundColor = UIColor.black
|
2015-12-30 02:51:22 -08:00
|
|
|
view.scene = _scene
|
|
|
|
|
view.allowsCameraControl = false
|
|
|
|
|
|
|
|
|
|
_sceneView = view
|
|
|
|
|
self.view = view
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func viewDidLoad()
|
|
|
|
|
{
|
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
_setupCamera()
|
|
|
|
|
_setupLights()
|
|
|
|
|
_setupModel()
|
|
|
|
|
_setupAnimation()
|
|
|
|
|
_setupEffects()
|
2016-07-06 22:11:48 -07:00
|
|
|
|
|
|
|
|
_beginModelResetTimer()
|
2015-12-30 02:51:22 -08:00
|
|
|
}
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
override func viewDidAppear(_ animated: Bool)
|
2015-12-30 02:51:22 -08:00
|
|
|
{
|
|
|
|
|
super.viewDidAppear(animated)
|
|
|
|
|
_sceneView?.play(nil)
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
override func viewDidDisappear(_ animated: Bool)
|
2015-12-30 02:51:22 -08:00
|
|
|
{
|
|
|
|
|
super.viewDidDisappear(animated)
|
|
|
|
|
_sceneView?.stop(nil)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-30 16:56:15 -08:00
|
|
|
// MARK: API
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
var connectionStatus: ConnectionStatus = .disconnected
|
2016-01-01 00:30:13 -08:00
|
|
|
{
|
|
|
|
|
didSet
|
|
|
|
|
{
|
|
|
|
|
switch (self.connectionStatus) {
|
2017-05-13 00:40:03 -07:00
|
|
|
case .disconnected, .connecting, .error:
|
|
|
|
|
_cubletsNode.isPaused = true
|
2016-01-01 00:30:13 -08:00
|
|
|
_lightNode.light?.color = UIColor(white: 0.3, alpha: 1.0)
|
2017-05-13 00:40:03 -07:00
|
|
|
case .connected:
|
|
|
|
|
_cubletsNode.isPaused = false
|
|
|
|
|
_lightNode.light?.color = UIColor.white
|
2016-01-01 00:30:13 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-31 18:20:50 -08:00
|
|
|
var percentActivated: Float
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _percentActivated
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set(percentage)
|
|
|
|
|
{
|
|
|
|
|
self.setPercentActivated(percentage, animated: false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
func setPercentActivated(_ percentage: Float, animated: Bool)
|
2015-12-30 16:56:15 -08:00
|
|
|
{
|
|
|
|
|
let cubletsCount = _cublets.count
|
|
|
|
|
let cubletsToActivate = UInt(percentage * Float(cubletsCount))
|
|
|
|
|
var shuffledCublets = _cublets
|
|
|
|
|
var cubletsActivated: UInt = 0
|
|
|
|
|
|
|
|
|
|
for i in 0 ..< cubletsCount {
|
|
|
|
|
let rnd = Int(arc4random_uniform(UInt32(cubletsCount)))
|
|
|
|
|
let tmp = shuffledCublets[rnd]
|
|
|
|
|
shuffledCublets[rnd] = shuffledCublets[i]
|
|
|
|
|
shuffledCublets[i] = tmp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for cublet in shuffledCublets {
|
|
|
|
|
_setCubletActivated(cublet, activated: (cubletsActivated < cubletsToActivate))
|
2016-07-06 22:11:48 -07:00
|
|
|
cubletsActivated += 1
|
2015-12-30 16:56:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (animated) {
|
|
|
|
|
let rx = Float(arc4random()) / Float(UInt32.max)
|
|
|
|
|
let ry = Float(arc4random()) / Float(UInt32.max)
|
|
|
|
|
let rz = Float(arc4random()) / Float(UInt32.max)
|
|
|
|
|
let rotAxis = SCNVector3Make(rx, ry, rz)
|
2015-12-30 18:47:13 -08:00
|
|
|
let rotCoeff = CGFloat(arc4random() % 2 == 0 ? -1.0 : 1.0)
|
2015-12-30 16:56:15 -08:00
|
|
|
let rotAngle = CGFloat(rotCoeff * π / 4.0)
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
let rotAction = SCNAction.rotate(by: rotAngle, around: rotAxis, duration: 0.8)
|
|
|
|
|
rotAction.timingMode = .linear
|
2015-12-30 16:56:15 -08:00
|
|
|
rotAction.timingFunction = { (t: Float) -> Float in
|
|
|
|
|
return min(((log10(4.0 * (t + 0.03)) + 1.0) / 1.5), 1.0)
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-01 00:30:13 -08:00
|
|
|
// modify the existing rotation animation as well, so that we have a
|
|
|
|
|
// smooth transition
|
|
|
|
|
let longTermAnimKey = VisualizationViewController.rotationAnimationKey
|
|
|
|
|
let newLongTermRotAction = _createLongTermRotationAnimation((rotCoeff * 2.0 * π), rotAxis)
|
2017-05-13 00:40:03 -07:00
|
|
|
_cubletsNode.removeAction(forKey: longTermAnimKey)
|
2016-01-01 00:30:13 -08:00
|
|
|
|
|
|
|
|
let actionSeq = SCNAction.sequence([rotAction, newLongTermRotAction])
|
|
|
|
|
_cubletsNode.runAction(actionSeq, forKey: longTermAnimKey)
|
2015-12-30 16:56:15 -08:00
|
|
|
}
|
2015-12-31 18:20:50 -08:00
|
|
|
|
2016-01-01 00:30:13 -08:00
|
|
|
_setAnimationSpeed(CGFloat(1.0 + (percentage * 2.0)))
|
|
|
|
|
|
2015-12-31 18:20:50 -08:00
|
|
|
_percentActivated = percentage
|
2015-12-30 16:56:15 -08:00
|
|
|
}
|
|
|
|
|
|
2015-12-30 02:51:22 -08:00
|
|
|
// MARK: Internal
|
|
|
|
|
|
|
|
|
|
internal func _setupCamera()
|
|
|
|
|
{
|
|
|
|
|
let camera = SCNCamera()
|
|
|
|
|
|
|
|
|
|
_cameraNode.camera = camera
|
2015-12-30 18:47:13 -08:00
|
|
|
_cameraNode.position = SCNVector3Make(0.0, 0.0, 30.0)
|
2015-12-30 02:51:22 -08:00
|
|
|
_scene.rootNode.addChildNode(_cameraNode)
|
|
|
|
|
|
|
|
|
|
let centerNode = SCNNode()
|
|
|
|
|
centerNode.position = SCNVector3Zero
|
|
|
|
|
_scene.rootNode.addChildNode(centerNode)
|
|
|
|
|
|
|
|
|
|
let constraint = SCNLookAtConstraint(target: centerNode)
|
|
|
|
|
_cameraNode.constraints = [constraint]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal func _setupLights()
|
|
|
|
|
{
|
|
|
|
|
let light = SCNLight()
|
2017-05-13 00:40:03 -07:00
|
|
|
light.type = SCNLight.LightType.omni
|
|
|
|
|
light.color = UIColor.white
|
2015-12-30 02:51:22 -08:00
|
|
|
|
2016-01-01 00:30:13 -08:00
|
|
|
_lightNode = SCNNode()
|
|
|
|
|
_lightNode.light = light
|
|
|
|
|
_lightNode.position = _cameraNode.position
|
2015-12-30 02:51:22 -08:00
|
|
|
|
2016-01-01 00:30:13 -08:00
|
|
|
_scene.rootNode.addChildNode(_lightNode)
|
2015-12-30 02:51:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal func _setupModel()
|
|
|
|
|
{
|
2017-05-13 00:40:03 -07:00
|
|
|
_cubletsNode.enumerateChildNodes { $0.0.removeFromParentNode() }
|
2015-12-30 16:56:15 -08:00
|
|
|
_cublets.removeAll()
|
|
|
|
|
|
2015-12-30 18:47:13 -08:00
|
|
|
let sz = Float(VisualizationViewController.cubletsSize)
|
|
|
|
|
let dim = VisualizationViewController.cubletsDimensions
|
2015-12-30 02:51:22 -08:00
|
|
|
let dimf = Float(dim)
|
2015-12-30 18:47:13 -08:00
|
|
|
let spacing = Float(VisualizationViewController.cubletsSpacing)
|
2015-12-30 02:51:22 -08:00
|
|
|
let volume = (dimf * sz) + ((dimf - 1.0) * spacing)
|
|
|
|
|
let orig = -volume / 2.0
|
|
|
|
|
|
2016-01-01 00:30:13 -08:00
|
|
|
// setup material and geometry. each node needs its own material for the activation effect.
|
2015-12-30 16:56:15 -08:00
|
|
|
let geom = SCNBox(width: CGFloat(sz), height: CGFloat(sz), length: CGFloat(sz), chamferRadius: 0.0)
|
2015-12-30 02:51:22 -08:00
|
|
|
let material = SCNMaterial()
|
2017-05-13 00:40:03 -07:00
|
|
|
material.diffuse.contents = UIColor.white
|
2015-12-30 02:51:22 -08:00
|
|
|
material.transparency = 0.75
|
|
|
|
|
|
2015-12-30 16:56:15 -08:00
|
|
|
// generate nodes for each cublet
|
2016-07-06 22:11:48 -07:00
|
|
|
for xi in 0 ..< dim {
|
2015-12-30 02:51:22 -08:00
|
|
|
let x = orig + Float(xi) * (sz + spacing)
|
|
|
|
|
|
2016-07-06 22:11:48 -07:00
|
|
|
for yi in 0 ..< dim {
|
2015-12-30 02:51:22 -08:00
|
|
|
let y = orig + Float(yi) * (sz + spacing)
|
|
|
|
|
|
2016-07-06 22:11:48 -07:00
|
|
|
for zi in 0 ..< dim {
|
2015-12-30 02:51:22 -08:00
|
|
|
let z = orig + Float(zi) * (sz + spacing)
|
|
|
|
|
|
2015-12-30 16:56:15 -08:00
|
|
|
let nodeGeom = geom.copy() as! SCNGeometry
|
|
|
|
|
nodeGeom.firstMaterial = material.copy() as? SCNMaterial
|
|
|
|
|
|
|
|
|
|
let node = SCNNode(geometry: nodeGeom)
|
2015-12-30 02:51:22 -08:00
|
|
|
node.position = SCNVector3Make(x, y, z)
|
|
|
|
|
_cubletsNode.addChildNode(node)
|
2015-12-30 16:56:15 -08:00
|
|
|
_cublets.append(node)
|
2015-12-30 02:51:22 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_cubletsNode.position = SCNVector3Zero
|
2017-05-13 00:40:03 -07:00
|
|
|
if (_cubletsNode.parent == nil) {
|
2016-07-06 22:11:48 -07:00
|
|
|
_scene.rootNode.addChildNode(_cubletsNode)
|
|
|
|
|
}
|
2015-12-30 02:51:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal func _setupAnimation()
|
|
|
|
|
{
|
2015-12-30 18:47:13 -08:00
|
|
|
let rotAngle = 2.0 * π
|
2016-01-01 00:30:13 -08:00
|
|
|
let rotAxis = SCNVector3Make(0.25, 1.75, 1.0)
|
|
|
|
|
let rotationAction = _createLongTermRotationAnimation(rotAngle, rotAxis)
|
2016-07-06 22:11:48 -07:00
|
|
|
_cubletsNode.removeAllActions()
|
2016-01-01 00:30:13 -08:00
|
|
|
_cubletsNode.runAction(rotationAction, forKey: VisualizationViewController.rotationAnimationKey)
|
2015-12-30 02:51:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal func _setupEffects()
|
|
|
|
|
{
|
2017-05-13 00:40:03 -07:00
|
|
|
let techniqueURL = Bundle.main.url(forResource: "CubletsTechnique", withExtension: "plist")
|
|
|
|
|
let techniqueDict = NSDictionary(contentsOf: techniqueURL!) as! [String : AnyObject]
|
2015-12-30 02:51:22 -08:00
|
|
|
let technique = SCNTechnique(dictionary: techniqueDict)
|
|
|
|
|
|
|
|
|
|
_sceneView?.technique = technique
|
|
|
|
|
}
|
2015-12-30 16:56:15 -08:00
|
|
|
|
2016-07-06 22:11:48 -07:00
|
|
|
internal func _beginModelResetTimer()
|
|
|
|
|
{
|
|
|
|
|
/* since this visualization is running all the time, trigonometric functions begin
|
|
|
|
|
malfunctioning at very large numbers. just reload the model every 24 hours so we
|
|
|
|
|
don't have to see it */
|
2017-05-13 00:40:03 -07:00
|
|
|
let reloadModelTime = DispatchTime.now() + Double(Int64(60 * 60 * 24 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: reloadModelTime) { [weak self] in
|
2016-07-06 22:11:48 -07:00
|
|
|
if let strongSelf = self {
|
|
|
|
|
strongSelf._cubletsNode.removeFromParentNode()
|
|
|
|
|
strongSelf._cubletsNode = SCNNode()
|
|
|
|
|
|
|
|
|
|
strongSelf._setupModel()
|
|
|
|
|
strongSelf._setupAnimation()
|
|
|
|
|
|
|
|
|
|
let currentPercentActivated = strongSelf.percentActivated
|
|
|
|
|
let currentConnectionStatus = strongSelf.connectionStatus
|
|
|
|
|
strongSelf.percentActivated = currentPercentActivated
|
|
|
|
|
strongSelf.connectionStatus = currentConnectionStatus
|
|
|
|
|
|
|
|
|
|
strongSelf._beginModelResetTimer()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
internal func _setCubletActivated(_ cublet: SCNNode, activated: Bool)
|
2015-12-30 16:56:15 -08:00
|
|
|
{
|
|
|
|
|
let material = cublet.geometry?.firstMaterial
|
2017-05-13 00:40:03 -07:00
|
|
|
material?.diffuse.contents = (activated ? UIColor.red : UIColor.white)
|
2015-12-30 16:56:15 -08:00
|
|
|
}
|
2016-01-01 00:30:13 -08:00
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
internal func _setAnimationSpeed(_ speed: CGFloat)
|
2016-01-01 00:30:13 -08:00
|
|
|
{
|
|
|
|
|
let key = VisualizationViewController.rotationAnimationKey
|
2017-05-13 00:40:03 -07:00
|
|
|
if let action = _cubletsNode.action(forKey: key) {
|
|
|
|
|
_cubletsNode.removeAction(forKey: key)
|
2016-07-06 22:11:48 -07:00
|
|
|
|
|
|
|
|
action.speed = speed
|
|
|
|
|
_cubletsNode.runAction(action, forKey: key)
|
|
|
|
|
}
|
2016-01-01 00:30:13 -08:00
|
|
|
}
|
|
|
|
|
|
2017-05-13 00:40:03 -07:00
|
|
|
internal func _createLongTermRotationAnimation(_ rotAngle: CGFloat, _ rotAxis: SCNVector3) -> SCNAction
|
2016-01-01 00:30:13 -08:00
|
|
|
{
|
2017-05-13 00:40:03 -07:00
|
|
|
return SCNAction.repeatForever(SCNAction.rotate(by: rotAngle, around: rotAxis, duration: 40.0))
|
2016-01-01 00:30:13 -08:00
|
|
|
}
|
2015-12-30 02:51:22 -08:00
|
|
|
}
|