Skip to content

Instantly share code, notes, and snippets.

@lachlanhurst
Created May 26, 2017 05:04
Show Gist options
  • Save lachlanhurst/f933bbdcaf1f0c6675df26c09883135f to your computer and use it in GitHub Desktop.
Save lachlanhurst/f933bbdcaf1f0c6675df26c09883135f to your computer and use it in GitHub Desktop.
Poorly written extract of source code used to create SceneKit 'bike dude'. Video here https://www.youtube.com/watch?v=UYwLltzNu_w
//
// MIT License
// Copyright (c) 2017 Lachlan Hurst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import UIKit
import GLKit
import SceneKit
enum SvgName:String {
case Bars = "BikeAndRider_bars"
case BarTops = "BikeAndRider_barTops"
case Fork = "BikeAndRider_fork"
case Frame = "BikeAndRider_frame"
case Rear = "BikeAndRider_rear"
case Wheel = "BikeAndRider_wheel"
case RiderFoot = "Rider_foot"
case RiderUpperLeg = "Rider_legupper"
case RiderLowerLeg = "Rider_leglower"
case RiderHead = "Rider_head"
case RiderUpperArm = "Rider_upperarm"
case RiderLowerArm = "Rider_lowerarm"
case RiderTorso = "Rider_torso"
}
enum RiderState:String {
case Climb = "Climb"
case Recover = "Recover"
case Sprint = "Sprint"
case Tempo = "Tempo"
case Work = "Work"
}
class BikeAndRiderNode:SvgBasedNode {
var bike:BikeNode?
var rider:RiderNode?
var bikeRotationNode:SCNNode
var isRocking:Bool = false
init(rider:RiderNode, bike:BikeNode) {
self.bike = bike
self.rider = rider
bikeRotationNode = SCNNode()
bikeRotationNode.addChildNode(bike)
var bikeTrans = SCNMatrix4Identity
super.init()
bikeTrans = SCNMatrix4Translate(bikeTrans, -1365 * svgScale, 1050 * svgScale, 0)
rider.position = SCNVector3Make(-1365 * svgScale, 1050 * svgScale, 0)
//bikeTrans = SCNMatrix4Rotate(bikeTrans, -1.0*Float(M_PI_4)/4, 1, 0, 0)
bikeRotationNode.transform = bikeTrans
self.addChildNode(bikeRotationNode)
self.addChildNode(rider)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func update(timeDelta:Float, speed:Float) {
bike?.update(timeDelta, speed: speed)
rider?.update(timeDelta, speed: speed)
var rotationTol:Float = 0.001
if rider?.state == RiderState.Sprint {
var bikeTrans = SCNMatrix4Identity
bikeTrans = SCNMatrix4Translate(bikeTrans, -1365 * svgScale, 1050 * svgScale, 0)
bikeTrans = SCNMatrix4Rotate(bikeTrans, rider!.torsoSwayRotation * 10, 1, 0, 0)
bikeRotationNode.transform = bikeTrans
isRocking = true
} else if (rider!.torsoSwayRotation < rotationTol * -1 || rider!.torsoSwayRotation > 0.001) && isRocking {
var bikeTrans = SCNMatrix4Identity
bikeTrans = SCNMatrix4Translate(bikeTrans, -1365 * svgScale, 1050 * svgScale, 0)
bikeTrans = SCNMatrix4Rotate(bikeTrans, rider!.torsoSwayRotation * 10, 1, 0, 0)
bikeRotationNode.transform = bikeTrans
} else {
isRocking = false
}
}
}
class RiderNode: SvgBasedNode {
private var _state:RiderState = .Work
private var _oldstate:RiderState = .Work
private var stateChangeCompletedFraction:Float = 0
private var torsoAngle:Float = 0
private var torsoPosition:SCNVector3 = SCNVector3Make(191, 90, 0)
private var lastSetTorsoPosition:SCNVector3 = SCNVector3Make(191, 90, 0)
private var lastSetTorsoAngle:Float = 0
private var lastSetLH:SCNVector3 = SCNVector3Zero
private var lastSetRH:SCNVector3 = SCNVector3Zero
var torsoSwayRotation:Float = 0
var torsoWidth:Float = 315
var bike:BikeNode? = nil
var leftLeg:RiderLegNode? = nil
var rightLeg:RiderLegNode? = nil
var torso:SCNNode? = nil
var torsoRotation:SCNNode? = nil
var head:SCNNode? = nil
var leftArm:RiderArmNode? = nil
var rightArm:RiderArmNode? = nil
override init() {
super.init()
torsoPosition = torsoPosition * svgScale
lastSetTorsoPosition = torsoPosition
loadBodyParts()
}
required override init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
torsoPosition = torsoPosition * svgScale
lastSetTorsoPosition = torsoPosition
loadBodyParts()
}
var state:RiderState {
get {
return _state
}
set {
_oldstate = _state
_state = newValue
leftLeg?.state = newValue
rightLeg?.state = newValue
stateChangeCompletedFraction = 0
lastSetTorsoAngle = self.torsoAngle
lastSetTorsoPosition = self.torsoPosition
lastSetLH = self.leftArm!.handConstraint!.targetPosition
lastSetRH = self.rightArm!.handConstraint!.targetPosition
}
}
override func update(timeDelta:Float, speed:Float) {
var distance = speed * timeDelta / 5
var revolution = distance / 2.105
var thisRotation = revolution * 2 * Float(M_PI)
if let bike = bike {
var pwc = bike.pedalWorldCoordinates()
self.leftLeg?.footNodeConstraint!.targetPosition = pwc.left
self.rightLeg?.footNodeConstraint!.targetPosition = pwc.right
let maxDist:Float = pwc.left.distance(pwc.right)
let current = pwc.left.y - pwc.right.y
let ratio = current / maxDist
let rotation = -1 * ratio * 0.0174532925
self.torsoSwayRotation = rotation
var torsoSwayRotation = SCNVector4Make(0, 1, 0, rotation)
var torsoUpDownRotation = SCNVector4Make(0, 0, 1, self.torsoAngle * 0.0174532925)
if _oldstate != _state {
stateChangeCompletedFraction = stateChangeCompletedFraction + timeDelta * 2
let osRot = lastSetTorsoAngle //torsoAngleForState(_oldstate)
let nsRot = torsoAngleForState(_state)
let stepSize = (nsRot - osRot) * timeDelta * 2
self.torsoAngle = self.torsoAngle + stepSize
let newTorsoPos = torsoPositionForState(_state)
self.torsoPosition = SCNVector3Lerp(lastSetTorsoPosition, newTorsoPos, stateChangeCompletedFraction)
var oldhwc = (left:lastSetLH ,right:lastSetRH) //bike.handWorldCoordinates(_oldstate)
var newhwc = bike.handWorldCoordinates(_state)
self.leftArm?.handConstraint!.targetPosition = SCNVector3Lerp(oldhwc.left, newhwc.left, stateChangeCompletedFraction)
self.rightArm?.handConstraint!.targetPosition = SCNVector3Lerp(oldhwc.right, newhwc.right, stateChangeCompletedFraction)
if stateChangeCompletedFraction >= 1 {
_oldstate = _state
self.torsoAngle = nsRot
self.torsoPosition = newTorsoPos
self.leftArm?.handConstraint!.targetPosition = newhwc.left
self.rightArm?.handConstraint!.targetPosition = newhwc.right
}
} else {
var newhwc = bike.handWorldCoordinates(_state)
self.leftArm?.handConstraint!.targetPosition = newhwc.left
self.rightArm?.handConstraint!.targetPosition = newhwc.right
}
var trans = SCNMatrix4Identity
trans = SCNMatrix4Rotate(trans, self.torsoAngle * 0.0174532925, 0, 0, 1)
trans = SCNMatrix4Rotate(trans, rotation, 0, 1, 0)
self.torsoRotation?.transform = trans
var transPos = SCNMatrix4Identity
transPos = SCNMatrix4Translate(transPos, torsoPosition.x, torsoPosition.y, torsoPosition.z)
self.torso!.transform = transPos
}
}
func torsoPositionForState(state:RiderState) -> SCNVector3 {
switch state {
case .Tempo, .Work, .Climb, .Recover:
return SCNVector3Make(191, 90, 0) * svgScale
case .Sprint:
return SCNVector3Make(350, 170, 0) * svgScale
}
}
func torsoAngleForState(state:RiderState) -> Float{
switch state {
case .Tempo:
return 2
case .Work:
return -3
case .Sprint:
return -12
case .Climb:
return 10
case .Recover:
return 15
}
}
func loadBodyParts() {
loadTorso()
loadHead()
var lookAtNode = SCNNode()//geometry: SCNSphere(radius: 100))
lookAtNode.position = SCNVector3Make(2400, 250, 0) * svgScale
self.addChildNode(lookAtNode)
var lac = SCNLookAtConstraint(target: lookAtNode)
lac.gimbalLockEnabled = true
self.head!.constraints = [lac]
let leftLegPosition = SCNNode()
leftLegPosition.position = SCNVector3Make(-43, -90, -120) * svgScale
let leftLeg = RiderLegNode()
leftLeg.rotation = SCNVector4Make(0, 1, 0, 47 * Float(M_PI_2)/180)
leftLegPosition.addChildNode(leftLeg)
torso!.addChildNode(leftLegPosition)
self.leftLeg = leftLeg
let rightLegPosition = SCNNode()
rightLegPosition.position = SCNVector3Make(-43, -90, 120) * svgScale
let rightLeg = RiderLegNode()
rightLeg.rotation = SCNVector4Make(0, 1, 0, -1.0 * 47 * Float(M_PI_2)/180)
rightLegPosition.addChildNode(rightLeg)
torso!.addChildNode(rightLegPosition)
self.rightLeg = rightLeg
let leftArmPosition = SCNNode()
leftArmPosition.position = SCNVector3Make(474, 100, -1 * (torsoWidth / 2.0 + 35)) * svgScale
let leftArm = RiderArmNode()
leftArm.rotation = SCNVector4Make(1, 0, 0, 40 * Float(M_PI_2)/180)
leftArmPosition.addChildNode(leftArm)
torsoRotation!.addChildNode(leftArmPosition)
self.leftArm = leftArm
let rightArmPosition = SCNNode()
rightArmPosition.position = SCNVector3Make(474, 100, (torsoWidth / 2.0 + 35)) * svgScale
let rightArm = RiderArmNode()
rightArm.rotation = SCNVector4Make(1, 0, 0, -1.0 * 40 * Float(M_PI_2)/180)
rightArmPosition.addChildNode(rightArm)
torsoRotation!.addChildNode(rightArmPosition)
self.rightArm = rightArm
}
func loadHead() {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderHead] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderHead)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(150 * svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
//chamfer.addLineToPoint(CGPointMake(1, 0.5))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(50 * svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderHead] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 837 * svgScale, 250 * svgScale, 0)
var headPivotNode = SCNNode()
headPivotNode.position = SCNVector3Make(0, 0, 0)
headPivotNode.rotation = SCNVector4Make(0, 1, 0, Float(M_PI_2))
headPivotNode.addChildNode(pathNode)
var headRotation = SCNNode()
//headRotation.rotation = SCNVector4Make(0, 1, 0, Float(M_PI_2))
headRotation.addChildNode(headPivotNode)
headRotation = headRotation.flattenedClone()
head = headRotation
var node = SCNNode()
node.position = SCNVector3Make(647, 182, 0) * svgScale
node.addChildNode(headRotation)
torsoRotation!.addChildNode(node)
}
func loadTorso() {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderTorso] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderTorso)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(torsoWidth * svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0.25))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(torsoWidth/3 * svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderTorso] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 191 * svgScale, 90 * svgScale, 0)
var nodeR = SCNNode()
nodeR.addChildNode(pathNode)
nodeR = nodeR.flattenedClone()
torsoRotation = nodeR
var node = SCNNode()
//node.position = SCNVector3Make(191, 90, 0)
node.addChildNode(nodeR)
torso = node
self.addChildNode(node)
}
}
class RiderArmNode: SvgBasedNode {
var bike:BikeNode? = nil
var shoulder:SCNNode? = nil
var upperArm:SCNNode? = nil
var lowerArm:SCNNode? = nil
var hand:SCNNode? = nil
var handConstraint:SCNIKConstraint? = nil
override init() {
super.init()
loadArmParts()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func loadArmParts() {
loadUpperArm()
loadLowerArm()
var ikConstraint = SCNIKConstraint.inverseKinematicsConstraintWithChainRootNode(shoulder!)
hand!.constraints = [ikConstraint]
ikConstraint.influenceFactor = 1.0
handConstraint = ikConstraint
//ikConstraint.setMaxAllowedRotationAngle(0, forJoint: hand!)
ikConstraint.setMaxAllowedRotationAngle(70, forJoint: lowerArm!)
ikConstraint.setMaxAllowedRotationAngle(90, forJoint: upperArm!)
//var tConstraint = SCNTransformConstraint(inWorldSpace: true, withBlock: constrainInZ)
//lowerArm?.constraints = [tConstraint]
}
func constrainInZ(node:SCNNode!, t:SCNMatrix4) -> SCNMatrix4 {
//var newMat = SCNMatrix4Translate(transform, 0, 0, -transform.m43)
var point = SCNVector3Make(t.m41, t.m42, t.m43)
var shoulderPoint = shoulder!.convertPosition(SCNVector3Zero, toNode: nil)
var handPoint = hand!.convertPosition(SCNVector3Zero, toNode: nil)
var downPoint = SCNVector3Make(0, -200, 0)
downPoint = shoulder!.convertPosition(downPoint, toNode: nil)
var v1 = downPoint - shoulderPoint
var v2 = downPoint - handPoint
var normal = v1.cross(v2)
normal = normal.normalized()
var v3 = downPoint - point
var v3m = v3 - normal * normal.dot(v3)
var rm = downPoint - v3m
var projected_point = rm
var nt = node.transform
var newMat = SCNMatrix4(m11: t.m11, m12: t.m12, m13: t.m13, m14: t.m14,
m21: t.m21, m22: t.m22, m23: t.m23, m24: t.m24,
m31: t.m31, m32: t.m32, m33: t.m33, m34: t.m34,
m41: projected_point.x, m42: projected_point.y, m43: projected_point.z, m44: t.m44)
/*
var newMat = SCNMatrix4(m11: t.m11, m12: t.m12, m13: t.m13, m14: t.m14,
m21: t.m21, m22: t.m22, m23: t.m23, m24: t.m24,
m31: t.m31, m32: t.m32, m33: t.m33, m34: t.m34,
m41: t.m41, m42: t.m42, m43: t.m43, m44: t.m44)
*/
return newMat
}
func loadLowerArm() {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderLowerArm] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderLowerArm)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(45 * svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(0.3, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(30 * svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderLowerArm] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 720 * svgScale, -132 * svgScale, 0 * svgScale)
var node = SCNNode()
node.position = SCNVector3Make(50, -292, 0) * svgScale
node.addChildNode(pathNode)
node = node.flattenedClone()
var handNode = SCNNode() //geometry: SCNBox(width: 30, height: 30, length: 30, chamferRadius: 0))
handNode.position = SCNVector3Make(340, -90, 0) * svgScale
node.addChildNode(handNode)
hand = handNode
lowerArm = node
upperArm!.addChildNode(node)
}
func loadUpperArm() {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderUpperArm] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderUpperArm)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(55 * svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(0.3, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(30 * svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderUpperArm] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 674 * svgScale, 158 * svgScale, 0 * svgScale)
var node = SCNNode()
node.addChildNode(pathNode)
node = node.flattenedClone()
upperArm = node
var shoulderNode = SCNNode()
shoulderNode.addChildNode(node)
shoulder = shoulderNode
addChildNode(shoulderNode)
}
}
class RiderLegNode: SvgBasedNode {
var bike:BikeNode? = nil
var footNode:SCNNode? = nil
var ankleNode:SCNNode? = nil
var upperlegNode:SCNNode? = nil
var lowerlegNode:SCNNode? = nil
var hipNode:SCNNode? = nil
var footNodeConstraint:SCNIKConstraint? = nil
private var _state:RiderState = RiderState.Work
override init() {
super.init()
loadLegParts()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var state:RiderState {
get {
return _state
}
set {
_state = newValue
/*if _state == .Sprint {
footNodeConstraint!.setMaxAllowedRotationAngle(0, forJoint: footNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(4, forJoint: ankleNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(10, forJoint: upperlegNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(50, forJoint: lowerlegNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(45, forJoint: hipNode!)
} else {
footNodeConstraint!.setMaxAllowedRotationAngle(0, forJoint: footNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(4, forJoint: ankleNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(10, forJoint: upperlegNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(50, forJoint: lowerlegNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(55, forJoint: hipNode!)
}*/
}
}
func loadLegParts() {
var upperLeg = loadUpperLeg()
var ikConstraint = SCNIKConstraint.inverseKinematicsConstraintWithChainRootNode(upperLeg)
footNode!.constraints = [ikConstraint]
footNodeConstraint = ikConstraint
ikConstraint.influenceFactor = 1.0
footNodeConstraint!.setMaxAllowedRotationAngle(10, forJoint: footNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(4, forJoint: ankleNode!)
//footNodeConstraint!.setMaxAllowedRotationAngle(10, forJoint: upperlegNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(50, forJoint: lowerlegNode!)
footNodeConstraint!.setMaxAllowedRotationAngle(50, forJoint: hipNode!)
self.state = .Work
//var frontNode = SCNNode()
//frontNode.position = SCNVector3Make(0, 0, -2000)
//var lac = SCNLookAtConstraint(target: frontNode)
//ankleNode!.constraints = [lac]
addChildNode(upperLeg)
}
func loadFoot() -> SCNNode {
//var path = pathFromSvg(.RiderFoot)
//var shape = SCNShape(path: path, extrusionDepth: 100)
var shape = SCNBox(width: 10, height: 10, length: 10, chamferRadius: 0)
var node = SCNNode()//geometry: shape)
node.position = SCNVector3Make(180, 0, 0) * svgScale
footNode = node
return node
}
func loadAnkle() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderFoot] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderFoot)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(90 * svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(24 * svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderFoot] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 460 * svgScale, -710 * svgScale, 0)
pathNode.position = SCNVector3Make(0, 20, 0) * svgScale
var shape = SCNBox(width: 20, height: 20, length: 20, chamferRadius: 0)
var node = SCNNode()//geometry: shape)
node.position = SCNVector3Make(-170, -480, 0) * svgScale
node.addChildNode(pathNode)
node = node.flattenedClone()
ankleNode = node
var subNode = loadFoot()
node.addChildNode(subNode)
return node
}
func loadLowerLeg() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderLowerLeg] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderLowerLeg)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(70 * svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(24 * svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderLowerLeg] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 620 * svgScale, -237 * svgScale, 0)
var shape = SCNBox(width: 30, height: 30, length: 30, chamferRadius: 0)
var node = SCNNode()//geometry: shape)
node.position = SCNVector3Make(470, -208, 0) * svgScale
node.addChildNode(pathNode)
node = node.flattenedClone()
lowerlegNode = node
var subNode = loadAnkle()
node.addChildNode(subNode)
return node
}
func loadUpperLeg() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.RiderUpperLeg] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.RiderUpperLeg)
var pshape = SCNShape(path: path, extrusionDepth: CGFloat(120*svgScale))
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
pshape.chamferMode = SCNChamferMode.Both
pshape.chamferRadius = CGFloat(33.3*svgScale)
pshape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.RiderUpperLeg] = pshape
geom = pshape
}
var pathNode = SCNNode(geometry: geom)
pathNode.pivot = SCNMatrix4Translate(SCNMatrix4Identity, 150*svgScale, -30*svgScale, 0*svgScale)
var shape = SCNBox(width: 40, height: 40, length: 40, chamferRadius: 0)
var node = SCNNode()//geometry: shape)
node.position = SCNVector3Make(0, 0, 0)
upperlegNode = node
var nodeIntermediate = SCNNode()//geometry: pshape)
node.addChildNode(nodeIntermediate)
nodeIntermediate.addChildNode(pathNode)
self.hipNode = nodeIntermediate
var subNode = loadLowerLeg()
nodeIntermediate.addChildNode(subNode)
return node
}
}
class BikeNode: SvgBasedNode {
var barWidth:Float = 380
var wheelRotation:Float = 0
var crankRotation:Float = 0
var celShade:Bool = false
var frontWheel:SCNNode? = nil
var rearWheel:SCNNode? = nil
var bottomBracket:SCNNode? = nil
var leftCrankTip:SCNNode? = nil
var rightCrankTip:SCNNode? = nil
override init() {
super.init()
loadBikeParts()
}
required override init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadBikeParts()
}
override func update(timeDelta:Float, speed:Float) {
var distance = speed * timeDelta
var revolution = distance / 2.105
var thisRotation = revolution * 2 * Float(M_PI)
wheelRotation = wheelRotation - thisRotation
crankRotation = crankRotation - thisRotation * (14/53)
frontWheel?.rotation = SCNVector4Make(0, 0, 1, wheelRotation)
rearWheel?.rotation = SCNVector4Make(0, 0, 1, wheelRotation)
bottomBracket?.rotation = SCNVector4Make(0, 0, 1, crankRotation)
}
func loadBikeParts() {
addRearWheel()
addFrontWheel()
addBottomBracket()
var tempFrame = SCNNode()
tempFrame.addChildNode(addFrame())
tempFrame.addChildNode(addFork())
var (left, right) = addBars()
tempFrame.addChildNode(left)
tempFrame.addChildNode(right)
tempFrame.addChildNode(addBarTops())
tempFrame.addChildNode(addRearTriangle())
self.addChildNode(tempFrame.flattenedClone())
}
func handWorldCoordinates(riderState:RiderState) -> (left:SCNVector3, right:SCNVector3) {
var res:(SCNVector3,SCNVector3)
switch riderState {
case .Tempo:
let leftZ = -1 * self.barWidth / 2
let rightZ = self.barWidth / 2
let x:Float = 1058
let y:Float = -210
let left = self.convertPosition(SCNVector3Make(x, y, leftZ)*svgScale, toNode: nil)
let right = self.convertPosition(SCNVector3Make(x, y, rightZ)*svgScale, toNode: nil)
res = (left, right)
case .Work, .Sprint:
let leftZ = -1 * self.barWidth / 2
let rightZ = self.barWidth / 2
let x:Float = 1010
let y:Float = -330
let left = self.convertPosition(SCNVector3Make(x, y, leftZ)*svgScale, toNode: nil)
let right = self.convertPosition(SCNVector3Make(x, y, rightZ)*svgScale, toNode: nil)
res = (left, right)
case .Climb, .Recover:
let leftZ = -1 * (self.barWidth / 2 - 60)
let rightZ = (self.barWidth / 2 - 60)
let x:Float = 952
let y:Float = -210
let left = self.convertPosition(SCNVector3Make(x, y, leftZ)*svgScale, toNode: nil)
let right = self.convertPosition(SCNVector3Make(x, y, rightZ)*svgScale, toNode: nil)
res = (left, right)
}
return res
}
func pedalWorldCoordinates() -> (left:SCNVector3, right:SCNVector3) {
let left = self.leftCrankTip!.convertPosition(SCNVector3Zero, toNode: nil)
let right = self.rightCrankTip!.convertPosition(SCNVector3Zero, toNode: nil)
var res = (left, right)
return res
}
func addBottomBracket()
{
var shape = SCNBox(width: 30, height: 30, length: 100, chamferRadius: 0)
var bbNode = SCNNode()//geometry: shape)
bottomBracket = bbNode
var bbOffset = SCNNode()
bbOffset.addChildNode(bbNode)
bbOffset.position = SCNVector3Make(475, -1 * (1050-240), 0) * svgScale
self.addChildNode(bbOffset)
addCrank()
}
func addCrank() {
var leftOffset = SCNNode()
leftOffset.position = SCNVector3Make(0, 0, -120) * svgScale
var rightOffset = SCNNode()
rightOffset.position = SCNVector3Make(0, 0, 120) * svgScale
bottomBracket!.addChildNode(leftOffset)
bottomBracket!.addChildNode(rightOffset)
var leftCrankTipG = SCNBox(width: 30, height: 30, length: 30, chamferRadius: 0)
var leftCrankTip = SCNNode()//geometry: leftCrankTipG)
leftCrankTip.position = SCNVector3Make(175, 0, 0) * svgScale
leftOffset.addChildNode(leftCrankTip)
self.leftCrankTip = leftCrankTip
var rightCrankTipG = SCNBox(width: 30, height: 30, length: 30, chamferRadius: 0)
var rightCrankTip = SCNNode()//geometry: rightCrankTipG)
rightCrankTip.position = SCNVector3Make(-175, 0, 0) * svgScale
rightOffset.addChildNode(rightCrankTip)
self.rightCrankTip = rightCrankTip
}
func addBarTops() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.BarTops] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.BarTops)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shape = SCNShape(path: path, extrusionDepth: CGFloat((barWidth - 40)*svgScale))
shape.chamferMode = SCNChamferMode.Both
shape.chamferRadius = CGFloat(7.5 * svgScale)
shape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.BarTops] = shape
geom = shape
}
var node = SCNNode(geometry: geom)
return node
}
func addBars() -> (SCNNode, SCNNode) {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.Bars] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.Bars)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shapeL = SCNShape(path: path, extrusionDepth: CGFloat(20 * svgScale))
shapeL.chamferMode = SCNChamferMode.Both
shapeL.chamferRadius = CGFloat(7.5 * svgScale)
shapeL.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.Bars] = shapeL
geom = shapeL
}
if celShade {
applyCelShading(geom)
}
var nodeL = SCNNode(geometry: geom)
nodeL.position = SCNVector3Make(0, 0, self.barWidth / 2) * svgScale
var nodeR = SCNNode(geometry: geom)
nodeR.position = SCNVector3Make(0, 0, -1 * self.barWidth / 2) * svgScale
return (nodeL, nodeR)
}
func addFork() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.Fork] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.Fork)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shape = SCNShape(path: path, extrusionDepth: CGFloat(15 * svgScale))
shape.chamferMode = SCNChamferMode.Both
shape.chamferRadius = CGFloat(6 * svgScale)
shape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.Fork] = shape
geom = shape
}
if celShade {
applyCelShading(geom)
}
var node = SCNNode(geometry: geom)
node.position = SCNVector3Make(0, 0, 35*svgScale)
return node
}
func addRearTriangle() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.Rear] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.Rear)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shape = SCNShape(path: path, extrusionDepth: CGFloat(10 * svgScale))
shape.chamferMode = SCNChamferMode.Both
shape.chamferRadius = CGFloat(3 * svgScale)
shape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.Rear] = shape
geom = shape
}
if celShade {
applyCelShading(geom)
}
var node = SCNNode(geometry: geom)
node.position = SCNVector3Make(0, 0, 35*svgScale)
return node
}
func addFrame() -> SCNNode {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.Frame] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var path = pathFromSvg(.Frame)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shape = SCNShape(path: path, extrusionDepth: CGFloat(20 * svgScale))
shape.chamferMode = SCNChamferMode.Both
shape.chamferRadius = CGFloat(10 * svgScale)
shape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.Frame] = shape
geom = shape
}
if celShade {
applyCelShading(geom)
}
var node = SCNNode(geometry: geom)
return node
}
func addRearWheel() {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.Wheel] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var backWheelPath = pathFromSvg(.Wheel)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shape = SCNShape(path: backWheelPath, extrusionDepth: CGFloat(30 * svgScale))
shape.chamferMode = SCNChamferMode.Both
shape.chamferRadius = CGFloat(10 * svgScale)
shape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.Wheel] = shape
geom = shape
}
if celShade {
applyCelShading(geom)
}
var center:SCNVector3 = SCNVector3Make(0, 0, 0)
var radius:CGFloat = 0
geom.getBoundingSphereCenter(&center, radius: &radius)
var wheelNode = SCNNode(geometry: geom)
wheelNode.pivot = SCNMatrix4MakeTranslation(center.x, center.y, center.z)
var wheelNodeOffset = SCNNode()
wheelNodeOffset.addChildNode(wheelNode)
wheelNodeOffset.position = SCNVector3Make((50 - center.x)*svgScale, -731*svgScale, center.z * svgScale)
rearWheel = wheelNode
self.addChildNode(wheelNodeOffset)
}
func addFrontWheel() {
var geom:SCNGeometry
if let existingGeom = GeometryStoreSingleton.sharedInstance[.Wheel] {
geom = existingGeom //existingGeom.copy() as SCNGeometry
} else {
var wheelPath = pathFromSvg(.Wheel)
var chamfer = UIBezierPath()
chamfer.moveToPoint(CGPointMake(0, 1))
chamfer.addLineToPoint(CGPointMake(1, 0))
var shape = SCNShape(path: wheelPath, extrusionDepth: CGFloat(30 * svgScale))
shape.chamferMode = SCNChamferMode.Both
shape.chamferRadius = CGFloat(10 * svgScale)
shape.chamferProfile = chamfer
GeometryStoreSingleton.sharedInstance[.Wheel] = shape
geom = shape
}
if celShade {
applyCelShading(geom)
}
var center:SCNVector3 = SCNVector3Make(0, 0, 0)
var radius:CGFloat = 0
geom.getBoundingSphereCenter(&center, radius: &radius)
var wheelNode = SCNNode(geometry: geom)
wheelNode.pivot = SCNMatrix4MakeTranslation(center.x, center.y, center.z)
var wheelNodeOffset = SCNNode()
wheelNodeOffset.addChildNode(wheelNode)
wheelNodeOffset.position = SCNVector3Make((1040 - center.x) * svgScale , -731 * svgScale , center.z * svgScale)
frontWheel = wheelNode
self.addChildNode(wheelNodeOffset)
}
}
class SvgBasedNode:SCNNode {
var svgScale:Float = 1/100
func update(timeDelta:Float, speed:Float) {
assertionFailure("must override update")
}
func pathFromSvg(svg:SvgName) -> UIBezierPath {
var bundle:NSBundle = NSBundle(forClass: BikeNode.self)
var path = bundle.pathForResource("imageResources/\(svg.rawValue)", ofType: "txt")
var error:NSError?
let fileContents = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: &error)
var svgStringBits:[String] = split(fileContents!) {$0 == " "}
var bp = UIBezierPath()
var lastPoint:CGPoint = CGPointMake(0, 0)
var isFirst = true
var isAbsolute = true
for svgBit in svgStringBits {
if svgBit == "M" {
isAbsolute = true
isFirst = true
} else if svgBit == "m" {
isAbsolute = false
isFirst = true
} else if svgBit == "l" {
isAbsolute = false
} else if svgBit == "L" {
isAbsolute = true
} else if svgBit.lowercaseString == "z" {
bp.closePath()
} else {
let xAndY:[String] = split(svgBit) {$0 == ","}
let x = ((xAndY[0] as NSString).floatValue)
let y = ((xAndY[1] as NSString).floatValue) * -1
if isFirst {
isFirst = false
let fp = CGPointMake(CGFloat(x * self.svgScale), CGFloat(y * self.svgScale))
bp.moveToPoint(fp)
lastPoint = fp
} else {
var thisPoint:CGPoint
if isAbsolute {
thisPoint = CGPointMake(CGFloat(x * self.svgScale), CGFloat(y * self.svgScale))
} else {
thisPoint = CGPointMake(lastPoint.x + CGFloat(x * self.svgScale), lastPoint.y + CGFloat(y * self.svgScale))
}
bp.addLineToPoint(thisPoint)
lastPoint = thisPoint
}
}
}
return bp
}
func applyCelShading(geom:SCNGeometry) {
var lightingShader = "" +
"\n" +
"float stepmix(float edge0, float edge1, float E, float x) \n" +
"{ \n" +
" float T = clamp(0.5 * (x - edge0 + E) / E, 0.0, 1.0); \n" +
" return mix(edge0, edge1, T); \n" +
"} \n" +
"\n" +
"#pragma body \n" +
"\n" +
"vec3 Eye = vec3(0, 0, 1); \n" +
"vec3 H = normalize(_light.direction + Eye); \n" +
"" +
"const float A = 0.1; \n" +
"const float B = 0.3; \n" +
"const float C = 0.6; \n" +
"const float D = 1.0; \n" +
"\n" +
"float df = max(0.0, dot(_surface.normal, _light.direction)); \n" +
"float E = 0.01; \n" +
"if (df > A - E && df < A + E) df = stepmix(A, B, E, df); \n" +
"else if (df > B - E && df < B + E) df = stepmix(B, C, E, df); \n" +
"else if (df > C - E && df < C + E) df = stepmix(C, D, E, df); \n" +
"else if (df < A) df = 0.0; \n" +
"else if (df < B) df = B; \n" +
"else if (df < C) df = C; \n" +
"else df = D; \n" +
"float sf = max(0.0, dot(_surface.normal, H)); \n" +
"sf = pow(sf, _surface.shininess); \n" +
"sf = step(0.5, sf); \n" +
"" +
"_lightingContribution.specular += sf * _light.intensity.rgb; \n" +
"_lightingContribution.diffuse += df * _light.intensity.rgb; \n" +
""
var fragmentShader = "" +
"if (dot(_surface.view, _surface.normal) < 0.1) \n " +
"{ \n " +
" _output.color.rgba = vec4(0.0,0.0,0.0,1.0); \n " +
"} \n " +
""
if let mat = geom.firstMaterial {
mat.litPerPixel = true
mat.shaderModifiers = [SCNShaderModifierEntryPointLightingModel : lightingShader, SCNShaderModifierEntryPointFragment : fragmentShader]
}
}
}
class GeometryStoreSingleton {
class var sharedInstance: GeometryStoreSingleton {
struct Static {
static var instance: GeometryStoreSingleton?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = GeometryStoreSingleton()
}
return Static.instance!
}
var geometries = [SvgName:SCNGeometry]()
subscript(svgName:SvgName) -> SCNGeometry? {
get {
return geometries[svgName]
}
set {
//newValue!.firstMaterial?.lightingModelName = SCNLightingModelPhong
//newValue!.firstMaterial?.lightingModelName = SCNLightingModelBlinn
geometries[svgName] = newValue
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment