How to create a function that will add blocks to a grid? - swift

I have this code that creates a 15x12 grid of blocks, but I am trying to access the index of each block and be able to remove or add blocks at that given index. I think I need to use a 2d array, but I am not sure how to add the blocks to an array that will make it easy for me to get each block at their index. Please comment any tips or advice you have, thanks!
for i in 0...12{
for j in 0...16{
let block = SKSpriteNode(texture: blockImage, size: blockSize)
block.position.x = block.frame.width/2 + CGFloat((64*j))
block.position.y = frame.height - block.frame.height/2 - CGFloat((64*i))
block.zPosition = 1
addChild(block)
}
}

Let's follow the idea you proposed.
Step 1
We can create a Dictionary were we map the index of a node (in the key) with the node (in the value).
struct Index: Hashable {
let i: Int
let j: Int
}
private var grid:[Index: SKNode] = [:]
Step 2
Now when you are adding the nodes to the parent, you just need to save the Index-Node relationship into the dictionary.
func addSprites() {
let blockImage = SKTexture(imageNamed: "TODO")
let blockSize = blockImage.size()
for i in 0...12{
for j in 0...16{
let block = SKSpriteNode(texture: blockImage, size: blockSize)
assert(grid[Index(i: i, j: j)] == nil)
grid[Index(i: i, j: j)] = block // <------------
block.position.x = block.frame.width/2 + CGFloat((64*j))
block.position.y = frame.height - block.frame.height/2 - CGFloat((64*i))
block.zPosition = 1
addChild(block)
}
}
}
Step 3
And finally you can easily remove a node for a given index
func removeSprite(at index: Index) {
guard let node = grid[index] else {
debugPrint("No node found at index: \(index)")
return
}
node.removeFromParent()
grid[index] = nil
}
Full code
class GameScene: SKScene {
struct Index: Hashable {
let i: Int
let j: Int
}
private var grid:[Index: SKNode] = [:]
func addSprites() {
let blockImage = SKTexture(imageNamed: "TODO")
let blockSize = blockImage.size()
for i in 0...4{
for j in 0...4{
let block = SKSpriteNode(texture: blockImage, size: blockSize)
assert(grid[Index(i: i, j: j)] == nil)
grid[Index(i: i, j: j)] = block // <---
block.position.x = block.frame.width/2 + CGFloat((64*j))
block.position.y = frame.height - block.frame.height/2 - CGFloat((64*i))
block.zPosition = 1
addChild(block)
}
}
}
func removeSprite(at index: Index) {
guard let node = grid[index] else {
debugPrint("No node found at index: \(index)")
return
}
node.removeFromParent()
grid[index] = nil
}
}
Quick Playground Test

Related

Bad access with a fixed size array and index in range

I'm getting crashes from a fixed size array even though I thought I'd protected the index from going out of range. The array updates in a loop, like a ring buffer.
This crashes reliably when the array size is 1000, but I've not managed to get it to crash when it's 500 or fewer.
EDIT: It has now crashed with an array size of 500, so the previous statement is no longer true.
I'm wondering whether this is compiler 'optimisation'... and what to do if it is. Any ideas gratefully received.
struct ZeroCrossing {
//The integer index of the second sample (cross over is between two samples)
var index: UInt
//The highest amplitude peak (negative or positive) between this and the previous crossing
let previousPeak: Float
//The interpolated crossover point between the two sample indices
let indexWithOffset: Double
}
class CrossingBuffer {
private var array: [ZeroCrossing]
private let size: Int
private var nextWriteIndex = 0
private var full: Bool {
nextWriteIndex >= size
}
init(size: Int) {
self.size = size
array = [ZeroCrossing](repeating: ZeroCrossing(index: 0, previousPeak: 0, indexWithOffset: 0), count: size)
array.reserveCapacity(size)
}
public func write(_ val: ZeroCrossing) {
array[nextWriteIndex % size] = val
nextWriteIndex += 1
}
public func getAfterIndex(_ refIndex: Double) -> [ZeroCrossing]? {
if !full { return nil }
var subArray = [ZeroCrossing]()
let lastElementIndex = nextWriteIndex - 1
for i in 0...size - 1 {
// CRASHES ON NEXT LINE !!!
let thisCrossing = array[(lastElementIndex - i) % size]
if thisCrossing.indexWithOffset > refIndex {
subArray.append(thisCrossing)
} else {
break
}
}
return subArray.reversed()
}
public func reset() {
array = [ZeroCrossing](repeating: ZeroCrossing(index: 0, previousPeak: 0, indexWithOffset: 0), count: size)
nextWriteIndex = 0
}
}
The backtrace ends with the following
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1016dc008)
frame #0: 0x000000018bf5c090 libswiftCore.dylib`swift_retain + 60
frame #1: 0x000000018bf9c704 libswiftCore.dylib`swift_bridgeObjectRetain + 56
* frame #2: 0x0000000100838e50 CrossingBuffer.getAfterIndex(refIndex=431975.76999999583, self=0x00000002800a86f0) at CrossingBuffer.swift:97:31
frame #3: 0x00000001007cd704 correlate(self=0x000000010130e5c0) at PitchEngine.swift:146:52
frame #4: 0x00000001007a155c correlate(self=0x0000000283b91200) at TunerEngine.swift:57:39
frame #5: 0x00000001007a1bd8 #objc correlate() at <compiler-generated>:0
But what's strange is that I can access the element from the debug console using the same index reference as the line it crashed on:
(lldb) print (lastElementIndex - i) % size
(Int) $R4 = 838
(lldb) print array.count
(Int) $R5 = 1000
(lldb) print array[(lastElementIndex - i) % size]
(ZeroCrossing) $R6 = (index = 438691, previousPeak = 0.0251232013, indexWithOffset = 438690.12000000477)
Update: additional code to illustrate crashing, it seems that in my case it only crashes when the array size is > 638. There may need to be some noise for the mic to pick up to instigate the crash:
import Foundation
import AVKit
final class AudioTap {
private var audioEngine = AVAudioEngine()
private var windowIndex: UInt = 0
private var lastPeak: Float = 0
private var lastSample: Sample?
public var minPeakSize: Float = 0.005
// Size of crossingBuffer changes crashing behavior
private var crossingBuffer = CrossingBuffer(size: 2000)
private var lastSeekIndex: Double = 0.0
private var timer: Timer?
init() {
installTunerTap()
audioEngine.prepare()
do {
try audioEngine.start()
} catch let error as NSError {
print("AVAudioEngine error on start: \(error.domain), \(error)")
}
timer = Timer.scheduledTimer(
timeInterval: 0.01,
target: self,
selector: #selector(getNext),
userInfo: nil,
repeats: true)
}
private func installTunerTap() {
let inputNode = audioEngine.inputNode
inputNode.installTap( onBus: 0,
bufferSize: 1000,
format: nil,
block: { buffer, when in
let sampleCount = Int(buffer.frameLength)
var sampleIndex = 0
while (sampleIndex < sampleCount) {
if let val = buffer.floatChannelData?.pointee[sampleIndex]{
let sample = Sample(index: self.windowIndex, val: val)
self.update(sample: sample)
}
self.windowIndex += 1
sampleIndex += 1
}
})
}
private func update(sample: Sample) {
lastPeak = abs(sample.val) > abs(lastPeak) ? sample.val : lastPeak
if let last = lastSample {
if last.val * sample.val < 0 && abs(lastPeak) > minPeakSize { // this is a zero crossing
let offset = Double(sample.index) + Double(round((sample.val/(last.val - sample.val)) * 100) / 100)
let crossing = ZeroCrossing(
index: sample.index,
previousPeak: lastPeak,
indexWithOffset: offset
)
crossingBuffer.write(crossing)
lastPeak = 0
}
}
lastSample = sample
}
#objc func getNext() {
if let arr = crossingBuffer.getAfterIndex(lastSeekIndex) {
if let s = arr.last {
lastSeekIndex = s.indexWithOffset
}
}
}
}
It's not crashed after many uses so I'm going to call this one fixed. Here's the updated CrossingBuffer code with the NSLock implemented. Thanks for the help #JoakimDainelson:
class CrossingBuffer {
private var array: [ZeroCrossing]
// NSLock Added
var lock = NSLock()
private let size: Int
private var nextWriteIndex = 0
private var full: Bool {
nextWriteIndex >= size
}
init(size: Int) {
self.size = size
array = [ZeroCrossing](repeating: ZeroCrossing(index: 0, previousPeak: 0, indexWithOffset: 0), count: size)
array.reserveCapacity(size)
}
public func write(_ val: ZeroCrossing) {
//Lock before write
lock.lock()
array[nextWriteIndex % size] = val
//Unlock
lock.unlock()
nextWriteIndex += 1
}
public func getAfterIndex(_ refIndex: Double) -> [ZeroCrossing]? {
if !full { return nil }
var subArray = [ZeroCrossing]()
let lastElementIndex = nextWriteIndex - 1
for i in 0...size - 1 {
// Was Crashing here, now wrapped in lock / unlock
lock.lock()
let thisCrossing = array[(lastElementIndex - i) % size]
lock.unlock()
if thisCrossing.indexWithOffset > refIndex {
subArray.append(thisCrossing)
} else {
break
}
}
return subArray.reversed()
}
public func reset() {
array = [ZeroCrossing](repeating: ZeroCrossing(index: 0, previousPeak: 0, indexWithOffset: 0), count: size)
nextWriteIndex = 0
}
}

Identifying memory leak in Swift GCD.async call

Some background about my app: I am drawing a map. When the user moves the map I perform a database query. I first do an rTree query to find the features that would be draw in the current viewport. Once I have those IDs I perform a second database query to extract the features (geojson) from the database. I do a quick check to see if the item already has been drawn, if not I do a addChild to render the feature on the map. I want to do these database looks up in the background via GCD so the user can move the map smoothly. I've implemented this but the memory usage quickly grows to 1gb, whereas if I do all the work in the main thread it uses around 250mb (acceptable for me). I'm assuming something is not being cleaned up because of the closure use. Any insight into the cause of the memory leak is appreciated.
public func drawItemsInBox(boundingBox: [Double]) {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else {
return
}
var drawItems: [Int64] = []
let table = Table("LNDARE_XS")
let tableRTree = Table("LNDARE_XS_virtual")
let coords = Expression<String?>("coords")
let foid = Expression<String>("foid")
let rTree = Expression<Int64>("rTree")
let minX = Expression<Double>("minX")
let maxX = Expression<Double>("maxX")
let minY = Expression<Double>("minY")
let maxY = Expression<Double>("maxY")
let id = Expression<Int64>("id")
// find all the features to draw via an rTree query
for row in try! self.db.prepare(tableRTree.filter(maxX >= boundingBox[0] && minX <= boundingBox[1] && maxY >= boundingBox[2] && minY <= boundingBox[3])) {
drawItems.append(row[id])
}
do {
// get all the features geojson data
let query = table.filter(drawItems.contains(rTree))
for row in try self.db.prepare(query) {
// skip drawing if the feature already exists on the map
if self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] == nil {
// convert the database string to an array of coords
var toBeRendered:[CGPoint] = []
let coordsArray = row[coords]!.components(separatedBy: ",")
for i in 0...(coordsArray.count / 2) - 1 {
toBeRendered.append(CGPoint(x: (Double(coordsArray[i*2])!), y: (Double(coordsArray[(i*2)+1])!)))
}
let linearShapeNode = SKShapeNode(points: &toBeRendered, count: toBeRendered.count)
linearShapeNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
linearShapeNode.lineWidth = 0
linearShapeNode.fillColor = NSColor.black
// append the featureId for tracking and call addChild to draw
self.scaleLayer.addChild(linearShapeNode)
self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] = linearShapeNode
}
}
} catch {
// catch
}
}
}
Maybe change toBeRendered can save some:
var toBeRendered:[CGPoint] = []
for row in try self.db.prepare(query) {
// skip drawing if the feature already exists on the map
if self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] == nil {
// convert the database string to an array of coords
toBeRendered.removeAll()
let coordsArray = row[coords]!.components(separatedBy: ",")
for i in 0...(coordsArray.count / 2) - 1 {
toBeRendered.append(CGPoint(x: (Double(coordsArray[i*2])!), y: (Double(coordsArray[(i*2)+1])!)))
}
Maybe try using an auto release pool since you are not on the main thread
public func drawItemsInBox(boundingBox: [Double]) {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else {
return
}
var drawItems: [Int64] = []
let table = Table("LNDARE_XS")
let tableRTree = Table("LNDARE_XS_virtual")
let coords = Expression<String?>("coords")
let foid = Expression<String>("foid")
let rTree = Expression<Int64>("rTree")
let minX = Expression<Double>("minX")
let maxX = Expression<Double>("maxX")
let minY = Expression<Double>("minY")
let maxY = Expression<Double>("maxY")
let id = Expression<Int64>("id")
// find all the features to draw via an rTree query
for row in try! self.db.prepare(tableRTree.filter(maxX >= boundingBox[0] && minX <= boundingBox[1] && maxY >= boundingBox[2] && minY <= boundingBox[3])) {
drawItems.append(row[id])
}
do {
// get all the features geojson data
let query = table.filter(drawItems.contains(rTree))
for row in try self.db.prepare(query) {
autoreleasepool{
// skip drawing if the feature already exists on the map
if self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] == nil {
// convert the database string to an array of coords
var toBeRendered:[CGPoint] = []
let coordsArray = row[coords]!.components(separatedBy: ",")
for i in 0...(coordsArray.count / 2) - 1 {
toBeRendered.append(CGPoint(x: (Double(coordsArray[i*2])!), y: (Double(coordsArray[(i*2)+1])!)))
}
let linearShapeNode = SKShapeNode(points: &toBeRendered, count: toBeRendered.count)
linearShapeNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
linearShapeNode.lineWidth = 0
linearShapeNode.fillColor = NSColor.black
// append the featureId for tracking and call addChild to draw
self.scaleLayer.addChild(linearShapeNode)
self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] = linearShapeNode
}
}
}
} catch {
// catch
}
}
}

How to bend a SCNShape “line”?

I’m trying to create a lighting bolt using scenekit and I’m following this guide. So far I’ve got a vertical line in my scene using UIBezierPath with an extrusion to make it 3d but I’m not sure how to bend the “line” at the midpoint as described in the link.
func createBolt() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: 1))
path.close()
let shape = SCNShape(path: path, extrusionDepth 0.2)
let color = UIColor.red
shape.firstMaterial?.diffuse.contents = color
let boltNode = SCNNode(geometry: shape)
boltNode.position.z = 0
sceneView.scene.rootNode.addChildNode(boltNode)
}
Algorithm is pretty straightforward:
You start with list of 1 segment from A to B, then on each generation you split each segment on 2 segments by shifting middle point on random offset on his norm
struct Segment {
let start: CGPoint
let end: CGPoint
}
/// Calculate norm of 2d vector
func norm(_ v: CGPoint) -> CGPoint {
let d = max(sqrt(v.x * v.x + v.y * v.y), 0.0001)
return CGPoint(x: v.x / d, y: v.y / -d)
}
/// Splitting segment on 2 segments with middle point be shifted by `offset` on norm
func split(_ segment: Segment, by offset: CGFloat) -> [Segment] {
var midPoint = (segment.start + segment.end) / 2
midPoint = norm(segment.end - segment.start) * offset + midPoint
return [
Segment(start: segment.start, end: midPoint),
Segment(start: midPoint, end: segment.end)
]
}
/// Generate bolt-like line from `start` to `end` with maximal started frequence of `maxOffset`
/// and `generation` of split loops
func generate(from start: CGPoint, to end: CGPoint, withOffset maxOffset: CGFloat, generations: Int = 6) -> UIBezierPath {
var segments = [Segment(start: start, end: end)]
var offset = maxOffset
for _ in 0 ..< generations {
segments = segments.flatMap { split($0, by: CGFloat.random(in: -offset...offset)) }
offset /= 2
}
let path = UIBezierPath()
path.move(to: start)
segments.forEach { path.addLine(to: $0.end) }
return path
}
// MARK: - Example
let start = CGPoint(x: 10, y: 10)
let end = CGPoint(x: 90, y: 90)
let path = generate(from: start, to: end, withOffset: 30, generations: 5)
// MARK: - Helpers
func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs)
}
func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
}
SceneKit make Lightning Bolts
This here gives you another approach how to create randomized, full 3D Lightning Bolts in SceneKit (thank you Harry!).
Create a new SceneKit project (for iOS) in Xcode using the default Game template - the one that shows the aircraft in 3D space - delete the aircraft and create an empty scene with a black background. Also globally define your sceneView (to be able to access it from other classes).
Add the following classes and extensions to a new Swift file (import SceneKit):
Classes
class LightningStrike:
class LightningStrike : Geometry {
var bolt:[Lightning] = []
var start = SCNVector3() // stores start position of the Bolt
var end = SCNVector3() // stores end position of the Bolt
static var delayTime = 0.0
override init() {
start = SCNVector3(0.0, +5.0, 0.0) // default, to be changed
end = SCNVector3(0.0, -5.0, 0.0) // default, to be changed
print("Lightning Strike initialized")
}
private func fadeOutBolt() {
for b in bolt {
SCNTransaction.begin()
SCNTransaction.animationDuration = 2.0
b.face.geometry?.firstMaterial?.transparency = 0.0
SCNTransaction.commit()
}
}
func strike() {
for b in bolt { b.face.removeFromParentNode() }
bolt.removeAll()
// Create Main Bolt
bolt.append(Lightning())
bolt[0].createBolt(start,end)
sceneView.scene?.rootNode.addChildNode(bolt[0].face)
// Create child Bolts
for _ in 0 ..< 15 { // number of child bolts
// let parent = Int.random(in: 0 ..< bolt.count) // random parent bolt, an other method
let parent : Int = 0
let start = bolt[parent].centerLine[10 + Int.random(in: 0 ..< 15)] // random node to start from off of parent, pay attention to: numSegments - changing numbers here can cause out of index crash
let length:SCNVector3 = bolt[parent].end.minus(start) // length from our start to end of parent
var end = SCNVector3()
end.x = start.x + length.x / 1.5 + Float.random(in: 0 ... abs(length.x) / 3) // adjust by playing with this numbers
end.y = start.y + length.y / 1.5 + Float.random(in: 0 ... abs(length.y) / 3) // adjust by playing with this numbers
end.z = start.z + length.z / 1.5 + Float.random(in: 0 ... abs(length.z) / 3) // adjust by playing with this numbers
bolt.append(Lightning())
let index = bolt.count-1
bolt[index].width = bolt[parent].width * 0.2
bolt[index].deviation = bolt[parent].deviation * 0.3
bolt[index].createBolt(start,end)
sceneView.scene?.rootNode.addChildNode(bolt[0].face)
}
// Reset delay time and schedule fadeOut
LightningStrike.delayTime = 0.0 // reset delay time
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { self.fadeOutBolt() }
// Here you can add a Sound Effect
}
deinit {
for b in bolt { b.face.removeFromParentNode() }
bolt.removeAll()
print("Lightning Strike deinitialized")
}
}
class Lightning:
class Lightning : Geometry {
let UNASSIGNED:Float = 999
var start = SCNVector3()
var end = SCNVector3()
var numSegments = Int() // use => 3,5,9,17,33,65
var width = Float()
var deviation = Float()
var vertices:[SCNVector3] = []
var normals:[SCNVector3] = []
var indices:[Int32] = []
var centerLine:[SCNVector3] = []
var face:SCNNode! = nil
override init() {
numSegments = 33 // 17
width = 0.1
deviation = 1.5
centerLine = Array(repeating: SCNVector3(), count: numSegments)
// indexed indices never change
var j:Int = 0
for i in 0 ..< numSegments-1 {
j = i * 3
indices.append(Int32(j + 0)) // 2 triangles on side #1
indices.append(Int32(j + 2))
indices.append(Int32(j + 3))
indices.append(Int32(j + 2))
indices.append(Int32(j + 5))
indices.append(Int32(j + 3))
indices.append(Int32(j + 2)) // side #2
indices.append(Int32(j + 1))
indices.append(Int32(j + 5))
indices.append(Int32(j + 1))
indices.append(Int32(j + 4))
indices.append(Int32(j + 5))
indices.append(Int32(j + 1)) // side #3
indices.append(Int32(j + 0))
indices.append(Int32(j + 4))
indices.append(Int32(j + 0))
indices.append(Int32(j + 3))
indices.append(Int32(j + 4))
}
}
func createNode() -> SCNGeometry {
for i in 0 ..< numSegments { centerLine[i].x = UNASSIGNED }
centerLine[0] = start
centerLine[numSegments-1] = end
var hop:Int = max(numSegments / 2,1)
var currentDeviation = deviation
while true {
for i in stride(from:0, to: numSegments, by:hop) {
if centerLine[i].x != UNASSIGNED { continue }
let p1 = centerLine[i-hop]
let p2 = centerLine[i+hop]
centerLine[i] = SCNVector3(
(p1.x + p2.x)/2 + Float.random(in: -currentDeviation ... currentDeviation),
(p1.y + p2.y)/2 + Float.random(in: -currentDeviation ... currentDeviation),
(p1.z + p2.z)/2 + Float.random(in: -currentDeviation ... currentDeviation))
}
if hop == 1 { break }
hop /= 2
currentDeviation *= 0.6
}
vertices.removeAll()
normals.removeAll()
// triangle of vertices at each centerLine node on XZ plane
let ss:[Float] = [ sin(0), sin(Float.pi * 2/3), sin(Float.pi * 4/3)]
let cc:[Float] = [ cos(0), cos(Float.pi * 2/3), cos(Float.pi * 4/3)]
var w = width
for i in 0 ..< numSegments {
for j in 0 ..< 3 {
vertices.append(SCNVector3(centerLine[i].x + cc[j] * w, centerLine[i].y, centerLine[i].z + ss[j] * w))
}
w *= 0.90 // bolt gets thinner towards endings
}
// normal for each vertex: position vs. position of neighbor on next node
var index1 = Int()
var index2 = Int()
func norm(_ v: SCNVector3) -> SCNVector3 {
let d = max(sqrt(v.x * v.x + v.y * v.y + v.z * v.z), 0.0001)
return SCNVector3(v.x / d, v.y / -d, v.z / d)
}
for i in 0 ..< numSegments {
for j in 0 ..< 3 {
index1 = i * 3 + j // point on current node
index2 = index1 + 3 // neighboring point on next node
if index2 >= vertices.count { index2 -= 6 } // last node references previous node instead
normals.append(norm(vertices[index1].minus(vertices[index2])))
}
}
let geoBolt = self.createGeometry(
vertices: vertices,
normals: normals,
indices: indices,
primitiveType: SCNGeometryPrimitiveType.triangles)
let boltMaterial : SCNMaterial = {
let material = SCNMaterial()
material.name = "bolt"
material.diffuse.contents = UIColor.init(hex: "#BAB1FFFF") // this is a very clear, almost white purple
material.roughness.contents = 1.0
material.emission.contents = UIColor.init(hex: "#BAB1FFFF") // this is a very clear, almost white purple
material.lightingModel = .physicallyBased
material.isDoubleSided = true
material.transparency = 0.0
return material
}()
geoBolt.firstMaterial = boltMaterial
// this makes the bolt not appearing all geometry at the same time - it's an animation effect
DispatchQueue.main.asyncAfter(deadline: .now() + LightningStrike.delayTime) {
boltMaterial.transparency = 1.0
}
LightningStrike.delayTime += 0.01665
// geoBolt.subdivisionLevel = 1 // give it a try or not...
return geoBolt
}
// Creates a Branch of the entire Bolt
func createBolt(_ nstart:SCNVector3, _ nend:SCNVector3) {
start = nstart
end = nend
face = SCNNode(geometry:createNode())
// This will add some glow around the Bolt,
// but it is **enourmous** performence and memory intense,
// you could try to add some SCNTechnique instead
// let gaussianBlur = CIFilter(name: "CIGaussianBlur")
// gaussianBlur?.name = "blur"
// gaussianBlur?.setValue(2, forKey: "inputRadius")
// face.filters = [gaussianBlur] as? [CIFilter]
sceneView.scene?.rootNode.addChildNode(face)
}
}
class Geometry:
class Geometry : NSObject {
internal func createGeometry(
vertices:[SCNVector3],
normals:[SCNVector3],
indices:[Int32],
primitiveType:SCNGeometryPrimitiveType) -> SCNGeometry
{
// Computed property that indicates the number of primitives to create based on primitive type
var primitiveCount:Int {
get {
switch primitiveType {
case SCNGeometryPrimitiveType.line:
return indices.count / 2
case SCNGeometryPrimitiveType.point:
return indices.count
case SCNGeometryPrimitiveType.triangles,
SCNGeometryPrimitiveType.triangleStrip:
return indices.count / 3
default : return 0
}
}
}
//------------------------
let vdata = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count)
let vertexSource = SCNGeometrySource(
data: vdata as Data,
semantic: SCNGeometrySource.Semantic.vertex,
vectorCount: vertices.count,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: 0,
dataStride: MemoryLayout<SCNVector3>.size)
//------------------------
let ndata = NSData(bytes: normals, length: MemoryLayout<SCNVector3>.size * normals.count)
let normalSource = SCNGeometrySource(
data: ndata as Data,
semantic: SCNGeometrySource.Semantic.normal,
vectorCount: normals.count,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float>.size,
dataOffset: 0,
dataStride: MemoryLayout<SCNVector3>.size)
let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
let element = SCNGeometryElement(
data: indexData as Data, primitiveType: primitiveType,
primitiveCount: primitiveCount, bytesPerIndex: MemoryLayout<Int32>.size)
return SCNGeometry(sources: [vertexSource, normalSource], elements: [element])
}
}
Extensions:
for SCNVector3:
extension SCNVector3
{
func length() -> Float { return sqrtf(x*x + y*y + z*z) }
func minus(_ other:SCNVector3) -> SCNVector3 { return SCNVector3(x - other.x, y - other.y, z - other.z) }
func normalized() -> SCNVector3 {
let len = length()
var ans = SCNVector3()
ans.x = self.x / len
ans.y = self.y / len
ans.z = self.z / len
return ans
}
}
for UIColor:
extension UIColor {
public convenience init?(hex: String) {
let r, g, b, a: CGFloat
if hex.hasPrefix("#") {
let start = hex.index(hex.startIndex, offsetBy: 1)
let hexColor = String(hex[start...])
if hexColor.count == 8 {
let scanner = Scanner(string: hexColor)
var hexNumber: UInt64 = 0
if scanner.scanHexInt64(&hexNumber) {
r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
a = CGFloat(hexNumber & 0x000000ff) / 255
self.init(red: r, green: g, blue: b, alpha: a)
return
}
}
}
return nil
}
}
Usage:
Init the class in your ViewController like so:
let lightningStrike = LightningStrike()
Also add a tap gesture recogniser (in viewDidLoad) for easy testing:
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
sceneView.addGestureRecognizer(tapGesture)
And the corresponding function that will trigger the Lightning Bolt:
#objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
lightningStrike.strike() // will fire a Lighting Bolt
}
Results:
Have fun with it.

iOS Charts - Display highest and lowest value for CandleStick charts

I'm trying to create a candlestick chart using Charts
As you guys can notice from my screenshot, the chart only shows the highest and lowest values instead of displaying the values for all the candles. Is there any way I can implement that with the Charts framework?
Thanks in advance.
If you want to display only highest and lowest values, you need to implement you own renderer inherited from CandleStickChartRenderer. In fact you just need to override one function drawValues(context: CGContext).
I have made some example which contain a hundred lines of code, but in fact my custom code contains about thirty lines.
class MyCandleStickChartRenderer: CandleStickChartRenderer {
private var _xBounds = XBounds() // Reusable XBounds object
private var minValue: Double
private var maxValue: Double
// New constructor
init (view: CandleStickChartView, minValue: Double, maxValue: Double) {
self.minValue = minValue
self.maxValue = maxValue
super.init(dataProvider: view, animator: view.chartAnimator, viewPortHandler: view.viewPortHandler)
}
// Override draw function
override func drawValues(context: CGContext)
{
guard
let dataProvider = dataProvider,
let candleData = dataProvider.candleData
else { return }
guard isDrawingValuesAllowed(dataProvider: dataProvider) else { return }
var dataSets = candleData.dataSets
let phaseY = animator.phaseY
var pt = CGPoint()
for i in 0 ..< dataSets.count
{
guard let dataSet = dataSets[i] as? IBarLineScatterCandleBubbleChartDataSet
else { continue }
let valueFont = dataSet.valueFont
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
let valueToPixelMatrix = trans.valueToPixelMatrix
_xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator)
let lineHeight = valueFont.lineHeight
let yOffset: CGFloat = lineHeight + 5.0
for j in stride(from: _xBounds.min, through: _xBounds.range + _xBounds.min, by: 1)
{
guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { break }
guard e.high == maxValue || e.low == minValue else { continue }
pt.x = CGFloat(e.x)
if e.high == maxValue {
pt.y = CGFloat(e.high * phaseY)
} else if e.low == minValue {
pt.y = CGFloat(e.low * phaseY)
}
pt = pt.applying(valueToPixelMatrix)
if (!viewPortHandler.isInBoundsRight(pt.x))
{
break
}
if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y))
{
continue
}
if dataSet.isDrawValuesEnabled
{
// In this part we draw min and max values
var textValue: String?
var align: NSTextAlignment = .center
if e.high == maxValue {
pt.y -= yOffset
textValue = "← " + String(maxValue)
align = .left
} else if e.low == minValue {
pt.y += yOffset / 5
textValue = String(minValue) + " →"
align = .right
}
if let textValue = textValue {
ChartUtils.drawText(
context: context,
text: textValue,
point: CGPoint(
x: pt.x,
y: pt.y ),
align: align,
attributes: [NSAttributedStringKey.font: valueFont, NSAttributedStringKey.foregroundColor: dataSet.valueTextColorAt(j)])
}
}
}
}
}
}
Do not forget use you custom renderer for you chart. ;)
myCandleStickChartView.renderer = MyCandleStickChartRenderer(view: myCandleStickChartView, minValue: 400, maxValue: 1450)

Procedural Level Generation With Cellular Automaton In Swift

Is there a easy way to create a procedural level with a cellular automaton in swift/SpriteKit(library?)? I want to create a 'cave' with 11 fields in the height and 22 width. These should be randomly created and every field without a wall should be reached.
I just found a documentation using Objective-C, which I am not familiar with. I spend quite some time trying to understand the code and follow the example without success.
PS: If there is an easier way I appreciate some algorithms
I made a Playground where you can experiment
//: Playground - noun: a place where people can play
import UIKit
import SpriteKit
import XCPlayground
class Cave {
var cellmap:[[Bool]]
let chanceToStartAlive = 35
let deathLimit = 3
let birthLimit = 4
var xCell = 40 // number of cell in x axes
var yCell = 20 // number of cell in y axes
var wCell = 20 // cell width
var hCell = 20 // cell height
init(){
cellmap = Array(count:yCell, repeatedValue:
Array(count:xCell, repeatedValue:false))
cellmap = self.initialiseMap(xCell, yIndex:yCell)
}
func initialiseMap(xIndex:Int, yIndex:Int) -> [[Bool]]{
var map:[[Bool]] = Array(count:yIndex, repeatedValue:
Array(count:xIndex, repeatedValue:false))
for y in 0...(yIndex - 1) {
for x in 0...(xIndex - 1) {
let diceRoll = Int(arc4random_uniform(100))
if diceRoll < chanceToStartAlive {
map[y][x] = true
} else {
map[y][x] = false
}
}
}
return map
}
func addSprite(scene:SKScene){
for (indexY, row) in cellmap.enumerate(){
for (indexX, isWall) in row.enumerate(){
if isWall {
let wall = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: wCell, height: hCell))
wall.position = CGPoint(x: (indexX * wCell) + (wCell / 2) , y: (indexY * hCell) + (hCell / 2) )
scene.addChild(wall)
}
}
}
}
func countAliveNeighbours(x:Int, y:Int) -> Int{
var count = 0
var neighbour_x = 0
var neighbour_y = 0
for i in -1...1 {
for j in -1...1 {
neighbour_x = x + j
neighbour_y = y + i
if(i == 0 && j == 0){
} else if(neighbour_x < 0 || neighbour_y < 0 || neighbour_y >= cellmap.count || neighbour_x >= cellmap[0].count){
count = count + 1
} else if(cellmap[neighbour_y][neighbour_x]){
count = count + 1
}
}
}
return count
}
func applyRules(){
var newMap:[[Bool]] = Array(count:yCell, repeatedValue:
Array(count:xCell, repeatedValue:false))
for y in 0...(cellmap.count - 1) {
for x in 0...(cellmap[0].count - 1) {
let nbs = countAliveNeighbours( x, y: y);
if(cellmap[y][x]){
if(nbs < deathLimit){
newMap[y][x] = false;
}
else{
newMap[y][x] = true;
}
} else{
if(nbs > birthLimit){
newMap[y][x] = true;
}
else{
newMap[y][x] = false;
}
}
}
}
cellmap = newMap
}
}
let view:SKView = SKView(frame: CGRectMake(0, 0, 1024, 768))
XCPShowView("Live View", view: view)
let scene:SKScene = SKScene(size: CGSizeMake(1024, 768))
scene.scaleMode = SKSceneScaleMode.AspectFit
let aCave = Cave()
aCave.applyRules()
aCave.applyRules()
aCave.addSprite(scene)
view.presentScene(scene)
Updated the playground code for Xcode 8 and Swift 3. I swapped the X and Y cell count since you will likely see the view in a "portrait" orientation.
Remember to open the Assistant Editor to view the results. It also takes a little while to execute, so give it a couple of minutes to run the algorithm.
//: Playground - noun: a place where people can play
import UIKit
import SpriteKit
import XCPlayground
import PlaygroundSupport
class Cave {
var cellmap:[[Bool]]
let chanceToStartAlive = 35
let deathLimit = 3
let birthLimit = 4
var xCell = 20 // number of cell in x axes
var yCell = 40 // number of cell in y axes
var wCell = 20 // cell width
var hCell = 20 // cell height
init(){
cellmap = Array(repeating:
Array(repeating:false, count:xCell), count:yCell)
cellmap = self.initialiseMap(xIndex: xCell, yIndex:yCell)
}
func initialiseMap(xIndex:Int, yIndex:Int) -> [[Bool]]{
var map:[[Bool]] = Array(repeating:
Array(repeating:false, count:xIndex), count:yIndex)
for y in 0...(yIndex - 1) {
for x in 0...(xIndex - 1) {
let diceRoll = Int(arc4random_uniform(100))
if diceRoll < chanceToStartAlive {
map[y][x] = true
} else {
map[y][x] = false
}
}
}
return map
}
func addSprite(scene:SKScene){
for (indexY, row) in cellmap.enumerated(){
for (indexX, isWall) in row.enumerated(){
if isWall {
let wall = SKSpriteNode(color: UIColor.red, size: CGSize(width: wCell, height: hCell))
wall.position = CGPoint(x: (indexX * wCell) + (wCell / 2) , y: (indexY * hCell) + (hCell / 2) )
scene.addChild(wall)
}
}
}
}
func countAliveNeighbours(x:Int, y:Int) -> Int{
var count = 0
var neighbour_x = 0
var neighbour_y = 0
for i in -1...1 {
for j in -1...1 {
neighbour_x = x + j
neighbour_y = y + i
if(i == 0 && j == 0){
} else if(neighbour_x < 0 || neighbour_y < 0 || neighbour_y >= cellmap.count || neighbour_x >= cellmap[0].count){
count = count + 1
} else if(cellmap[neighbour_y][neighbour_x]){
count = count + 1
}
}
}
return count
}
func applyRules(){
var newMap:[[Bool]] = Array(repeating:
Array(repeating:false, count:xCell), count:yCell)
for y in 0...(cellmap.count - 1) {
for x in 0...(cellmap[0].count - 1) {
let nbs = countAliveNeighbours( x: x, y: y);
if(cellmap[y][x]){
if(nbs < deathLimit){
newMap[y][x] = false;
}
else{
newMap[y][x] = true;
}
} else{
if(nbs > birthLimit){
newMap[y][x] = true;
}
else{
newMap[y][x] = false;
}
}
}
}
cellmap = newMap
}
}
let view:SKView = SKView(frame: CGRect(x: 0, y: 0, width: 768, height: 1024))
let scene:SKScene = SKScene(size: CGSize(width: 768, height: 1024))
scene.scaleMode = SKSceneScaleMode.aspectFit
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = view
let aCave = Cave()
aCave.applyRules()
aCave.applyRules()
aCave.addSprite(scene: scene)
view.presentScene(scene)