Created
April 29, 2020 23:28
-
-
Save iosdevzone/ad7f0474c7cb7c8c048d588948f96196 to your computer and use it in GitHub Desktop.
Source code demonstrating placement of alternately flipped triangles to form hexagon in SceneKit (StackOverflow question)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// StockOverflowSceneKit | |
// | |
// Created by idz on 4/28/20. | |
// | |
import SceneKit | |
class TrianglePlane: SCNNode { | |
var size: CGFloat = 0.1 | |
var coords: SCNVector3 = SCNVector3Zero | |
var innerCoords: Int = 0 | |
init(coords: SCNVector3, innerCoords: Int, identifier: Int) { | |
super.init() | |
self.coords = coords | |
self.innerCoords = innerCoords | |
setup() | |
} | |
init(identifier: Int) { | |
super.init() | |
// super.init(identifier: identifier) | |
setup() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
func setup() { | |
let myPath = path() | |
let geo = SCNShape(path: myPath, extrusionDepth: 0) | |
geo.firstMaterial?.diffuse.contents = UIColor.red | |
geo.firstMaterial?.blendMode = .multiply | |
self.geometry = geo | |
} | |
func path() -> UIBezierPath { | |
let max: CGFloat = self.size | |
let min: CGFloat = 0 | |
let bPath = UIBezierPath() | |
bPath.move(to: .zero) | |
bPath.addLine(to: CGPoint(x: max / 2, | |
y: UIBezierPath.middlePeak(height: max))) | |
bPath.addLine(to: CGPoint(x: max, y: min)) | |
bPath.close() | |
return bPath | |
} | |
} | |
extension TrianglePlane { | |
/// Calculates the centroid of the triangle | |
func centroid() -> CGPoint | |
{ | |
let max: CGFloat = self.size | |
let min: CGFloat = 0 | |
let peak = UIBezierPath.middlePeak(height: max) | |
let xAvg = (min + max / CGFloat(2.0) + max) / CGFloat(3.0) | |
let yAvg = (min + peak + min) / CGFloat(3.0) | |
return CGPoint(x: xAvg, y: yAvg) | |
} | |
} | |
extension TrianglePlane { | |
static func generateHexagon() -> [TrianglePlane] { | |
var myArr: [TrianglePlane] = [] | |
let colors = [UIColor.red, UIColor.green, | |
UIColor.yellow, UIColor.systemTeal, | |
UIColor.cyan, UIColor.magenta] | |
for i in 0 ..< 6 { | |
let tri = TrianglePlane(identifier: 0) | |
tri.geometry?.firstMaterial?.diffuse.contents = colors[i] | |
tri.position = SCNVector3( -0.05, 0, -0.5) | |
// Rotate bezier path | |
let angleInDegrees = (Float(i) + 1) * 180.0 | |
print(angleInDegrees) | |
let angle = CGFloat(deg2rad(angleInDegrees)) | |
let geo = tri.geometry as! SCNShape | |
let path = geo.path! | |
path.rotateAroundCenter(angle: angle) | |
geo.path = path | |
// Position triangle in hexagon | |
let height = Float(UIBezierPath.middlePeak(height: tri.size)) | |
let centroid = tri.centroid() | |
let radius = height - Float(centroid.y) | |
let deg: Float = (Float(i) * 60) - 90.0 | |
let radians = deg2rad(-deg) | |
let x1 = radius * cos(radians) | |
let y1 = radius * sin(radians) | |
let dx = Float(-centroid.x) | |
let dy = (i % 2 == 0) ? Float(centroid.y) - height : Float(-centroid.y) | |
tri.position.x = x1 + dx | |
tri.position.y = y1 + dy | |
myArr.append(tri) | |
} | |
return myArr | |
} | |
static func deg2rad(_ number: Float) -> Float { | |
return number * Float.pi / 180 | |
} | |
} | |
extension UIBezierPath { | |
func rotateAroundCenter(angle: CGFloat) { | |
let center = self.bounds.center | |
var transform = CGAffineTransform.identity | |
transform = transform.translatedBy(x: center.x, y: center.y) | |
transform = transform.rotated(by: angle) | |
transform = transform.translatedBy(x: -center.x, y: -center.y) | |
self.apply(transform) | |
} | |
func rotateAroundPoint(angle: CGFloat, point: CGPoint) { | |
let center = point | |
var transform = CGAffineTransform.identity | |
transform = transform.translatedBy(x: center.x, y: center.y) | |
transform = transform.rotated(by: angle) | |
transform = transform.translatedBy(x: -center.x, y: -center.y) | |
self.apply(transform) | |
} | |
static func middlePeak(height: CGFloat) -> CGFloat { | |
return sqrt(3.0) / 2 * height | |
} | |
} | |
extension CGRect { | |
var center : CGPoint { | |
return CGPoint(x:self.midX, y:self.midY) | |
} | |
} | |
class ViewController: UIViewController { | |
func oneSolution(scene: SCNScene) | |
{ | |
for i in 1...6 { | |
let triangleNode = SCNNode(geometry: SCNPyramid(width: 1.15, | |
height: 1, | |
length: 1)) | |
// the depth of pyramid is almost zero | |
triangleNode.scale = SCNVector3(5, 5, 0.001) | |
// move a pivot point from pyramid base to its upper vertex | |
triangleNode.simdPivot.columns.3.y = 1 | |
triangleNode.geometry?.firstMaterial?.diffuse.contents = UIColor( | |
hue: CGFloat(i)/6, | |
saturation: 1.0, | |
brightness: 1.0, | |
alpha: 1.0) | |
triangleNode.rotation = SCNVector4(0, 0, 1, | |
-CGFloat.pi/3 * CGFloat(i)) | |
scene.rootNode.addChildNode(triangleNode) | |
} | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
let sceneView = self.view as! SCNView | |
let scene = SCNScene() | |
sceneView.scene = scene | |
sceneView.allowsCameraControl = true | |
sceneView.backgroundColor = UIColor.white | |
let cameraNode = SCNNode() | |
cameraNode.camera = SCNCamera() | |
scene.rootNode.addChildNode(cameraNode) | |
cameraNode.position = SCNVector3(x: 0, y: 0, z: 1) | |
let planes = TrianglePlane.generateHexagon() | |
for plane in planes { | |
scene.rootNode.addChildNode(plane) | |
let box = SCNSphere(radius: 0.5)//SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0) | |
let node = SCNNode(geometry: box) | |
//node.position = plane.position; | |
scene.rootNode.addChildNode(node) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment