How to calculate size of SCNNode in pixels? - ios

I have an SCNSphere and I'd like to get its projected size in screen pixels (or more accurately, points).
I thought that this would do it:
let bounds = endNode.boundingBox
let projectedMin = renderer.projectPoint(bounds.min)
let projectedMax = renderer.projectPoint(bounds.max)
let sizeInPts = CGSize(width: CGFloat(projectedMax.x - projectedMin.x), height: CGFloat(projectedMax.y - projectedMin.y))
However that doesn't work. The width and height in sizeInPts is always way off.

I think you should check all vertices of the bounding box.
I didn't test this code, but I hope it works.
let (localMin, localMax) = endNode.boundingBox
let min = endNode.convertPosition(localMin, to: nil)
let max = endNode.convertPosition(localMax, to: nil)
let arr = [
renderer.projectPoint(SCNVector3(min.x, min.y, min.z)),
renderer.projectPoint(SCNVector3(max.x, min.y, min.z)),
renderer.projectPoint(SCNVector3(min.x, max.y, min.z)),
renderer.projectPoint(SCNVector3(max.x, max.y, min.z)),
renderer.projectPoint(SCNVector3(min.x, min.y, max.z)),
renderer.projectPoint(SCNVector3(max.x, min.y, max.z)),
renderer.projectPoint(SCNVector3(min.x, max.y, max.z)),
renderer.projectPoint(SCNVector3(max.x, max.y, max.z))
]
let minX: CGFloat = arr.reduce(CGFloat.infinity, { $0 > $1.x ? $1.x : $0 })
let minY: CGFloat = arr.reduce(CGFloat.infinity, { $0 > $1.y ? $1.y : $0 })
let minZ: CGFloat = arr.reduce(CGFloat.infinity, { $0 > $1.z ? $1.z : $0 })
let maxX: CGFloat = arr.reduce(-CGFloat.infinity, { $0 < $1.x ? $1.x : $0 })
let maxY: CGFloat = arr.reduce(-CGFloat.infinity, { $0 < $1.y ? $1.y : $0 })
let maxZ: CGFloat = arr.reduce(-CGFloat.infinity, { $0 < $1.z ? $1.z : $0 })
let width = maxX - minX
let height = maxY - minY
let depth = maxZ - minZ
let sizeInPts = CGSize(width: width, height: height)
I uploaded the Xcode Playground example to Github.

Related

Depth map normalization

I am applying CIDepthBlurEffect filter to a PHAsset as follows:
PHImageManager.default().requestImageDataAndOrientation(for: self.asset!, options: imageOptions) { (data, responseString, imageOrientation, info) in
if data != nil {
DispatchQueue.main.async {
if let depthImage = CIImage(data: data!, options: [CIImageOption.auxiliaryDisparity : true])?.oriented(imageOrientation) {
if let newMainImage = CIImage(data: data!)?.oriented(imageOrientation) {
let filter = CIFilter(name : "CIDepthBlurEffect",
parameters: [kCIInputImageKey : newMainImage,
kCIInputDisparityImageKey: depthImage,
"inputAperture" : withRadius])
self.imageView.image = UIImage(ciImage: filter!.outputImage!)
}
}
}
}
This works great most of the time. However, sometimes, the filter blur planes seem to be off. Almost as the Z plane is wrong or shifted.
Does this method require the depth data to be normalized?
Previously, when CVPixelBuffer was used, I was using this extension:
final func normalize() -> CVPixelBuffer {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
let floatBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(self), to: UnsafeMutablePointer<Float>.self)
var minPixel: Float = 1.0
var maxPixel: Float = 0.0
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = floatBuffer[y * width + x]
minPixel = min(pixel, minPixel)
maxPixel = max(pixel, maxPixel)
}
}
let range = maxPixel - minPixel
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = floatBuffer[y * width + x]
floatBuffer[y * width + x] = (pixel - minPixel) / range
}
}
CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
return self
}
Is there such a way to do this to a CIImage?

Find the Bounding Box of Submesh in iOS

I am drawing a 3D model as below from a obj file. I need to find the Bounding box for each submesh of that obj file.
let assetURL = Bundle.main.url(forResource: "train", withExtension: "obj")!
let allocator = MTKMeshBufferAllocator(device: device)
let asset = MDLAsset(url: assetURL, vertexDescriptor: vertexDescriptor, bufferAllocator: meshAllocator)
let mdlMesh = asset.object(at: 0) as! MDLMesh
mesh = try! MTKMesh(mesh: mdlMesh, device: device)
for submesh in mesh.submeshes {
//I need to find the bounding box for each Submesh
}
How can I achieve that in iOS.
Here's a function that takes an MTKMesh and produces an array of MDLAxisAlignedBoundingBoxes in model space:
enum BoundingBoxError : Error {
case invalidIndexType(String)
}
func boundingBoxesForSubmeshes(of mtkMesh: MTKMesh, positionAttributeData: MDLVertexAttributeData) throws -> [MDLAxisAlignedBoundingBox]
{
struct VertexPosition {
var x, y, z: Float
}
var boundingBoxes = [MDLAxisAlignedBoundingBox]()
var minX = Float.greatestFiniteMagnitude
var minY = Float.greatestFiniteMagnitude
var minZ = Float.greatestFiniteMagnitude
var maxX = -Float.greatestFiniteMagnitude
var maxY = -Float.greatestFiniteMagnitude
var maxZ = -Float.greatestFiniteMagnitude
let positionsPtr = positionAttributeData.dataStart
for submesh in mtkMesh.submeshes {
let indexBuffer = submesh.indexBuffer
let mtlIndexBuffer = indexBuffer.buffer
let submeshIndicesRaw = mtlIndexBuffer.contents().advanced(by: indexBuffer.offset)
if submesh.indexType != .uint32 {
throw BoundingBoxError.invalidIndexType("Expected 32-bit indices")
}
let submeshIndicesPtr = submeshIndicesRaw.bindMemory(to: UInt32.self,
capacity: submesh.indexCount)
let submeshIndices = UnsafeMutableBufferPointer<UInt32>(start: submeshIndicesPtr,
count:submesh.indexCount)
for index in submeshIndices {
let positionPtr = positionsPtr.advanced(by: Int(index) * positionAttributeData.stride)
let position = positionPtr.assumingMemoryBound(to: VertexPosition.self).pointee
if position.x < minX { minX = position.x }
if position.y < minY { minY = position.y }
if position.z < minZ { minZ = position.z }
if position.x > maxX { maxX = position.x }
if position.y > maxY { maxY = position.y }
if position.z > maxZ { maxZ = position.z }
}
let min = SIMD3<Float>(x: minX, y: minY, z: minZ)
let max = SIMD3<Float>(x: maxX, y: maxY, z: maxZ)
let box = MDLAxisAlignedBoundingBox(maxBounds: max, minBounds: min)
boundingBoxes.append(box)
}
return boundingBoxes
}
Note that this function expects the submesh to have 32-bit indices, though it could be adapted to support 16-bit indices as well.
To drive it, you'll also need to supply an MDLVertexAttributeData object that points to the vertex data in the containing mesh. Here's how to do that:
let mesh = try! MTKMesh(mesh: mdlMesh, device: device)
let positionAttrData = mdlMesh.vertexAttributeData(forAttributeNamed: MDLVertexAttributePosition, as: .float3)!
let boundingBoxes = try? boundingBoxesForSubmeshes(of: mesh, positionAttributeData: positionAttrData)
I haven't tested this code thoroughly, but it seems to produce sane results on my limited set of test cases. Visualizing the boxes with a debug mesh should immediately show whether or not they're correct.

Generate UIImage for ColorPicker (CGDataProvider and CGImageSource)

Guys I am new in swift and programming, I want to generate a Image to be shown for a ColorPicker I'm using SwiftColorPicker for that and have the following code for image creating:
private func createImageFromData(_ width:Int, height:Int) {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
provider = CGDataProvider(data: mutableData)
imageSource = CGImageSourceCreateWithDataProvider(provider, nil)
let cgImage = CGImage.init(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: width * 4, space: colorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent)
if let cgimg = cgImage {
image = UIImage(cgImage: cgimg)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: self.image!)
}, completionHandler: { success, error in
if success {
print("succes")
}
else if let error = error {
print("error")
debugPrint(error as Any)
}
else {
print("woooot?")
}
})
} else { print("Where the hell is the image?") }
}
func changeSize(_ width:Int, height:Int) {
self.width = width
self.height = height
let size:Int = width * height * 4
CFDataSetLength(mutableData, size)
createImageFromData(width, height: height)
}
init(width:Int, height:Int) {
self.width = width
self.height = height
let size:Int = width * height * 4
mutableData = CFDataCreateMutable(kCFAllocatorDefault, size)
createImageFromData(width, height: height)
}
public func writeColorData(_ h:CGFloat, a:CGFloat) {
let d = CFDataGetMutableBytePtr(self.mutableData)
if width == 0 || height == 0 {
return
}
var i:Int = 0
let h360:CGFloat = ((h == 1 ? 0 : h) * 360) / 60.0
let sector:Int = Int(floor(h360))
let f:CGFloat = h360 - CGFloat(sector)
let f1:CGFloat = 1.0 - f
var p:CGFloat = 0.0
var q:CGFloat = 0.0
var t:CGFloat = 0.0
let sd:CGFloat = 1.0 / CGFloat(width)
let vd:CGFloat = 1 / CGFloat(height)
var double_s:CGFloat = 0
var pf:CGFloat = 0
let v_range = 0..<height
let s_range = 0..<width
for v in v_range {
pf = 255 * CGFloat(v) * vd
for s in s_range {
i = (v * width + s) * 4
d?[i] = UInt8(255)
if s == 0 {
q = pf
d?[i+1] = UInt8(q)
d?[i+2] = UInt8(q)
d?[i+3] = UInt8(q)
continue
}
double_s = CGFloat(s) * sd
p = pf * (1.0 - double_s)
q = pf * (1.0 - double_s * f)
t = pf * ( 1.0 - double_s * f1)
switch(sector) {
case 0:
d?[i+1] = UInt8(pf)
d?[i+2] = UInt8(t)
d?[i+3] = UInt8(p)
case 1:
d?[i+1] = UInt8(q)
d?[i+2] = UInt8(pf)
d?[i+3] = UInt8(p)
case 2:
d?[i+1] = UInt8(p)
d?[i+2] = UInt8(pf)
d?[i+3] = UInt8(t)
case 3:
d?[i+1] = UInt8(p)
d?[i+2] = UInt8(q)
d?[i+3] = UInt8(pf)
case 4:
d?[i+1] = UInt8(t)
d?[i+2] = UInt8(p)
d?[i+3] = UInt8(pf)
default:
d?[i+1] = UInt8(pf)
d?[i+2] = UInt8(p)
d?[i+3] = UInt8(q)
}
}
}
}
and:
// Write colors to data array
if self.data1Shown { self.pickerImage2!.writeColorData(self.h, a:self.a) }
else { self.pickerImage1!.writeColorData(self.h, a:self.a)}
with:
public var a:CGFloat = 1 {
didSet {
if a < 0 || a > 1 {
a = max(0, min(1, a))
}
}
}
public var h:CGFloat = 0 { // // [0,1]
didSet {
if h > 1 || h < 0 {
h = max(0, min(1, h))
}
renderBitmap()
setNeedsDisplay()
}
}
It fails when i try to save it to photo albums (i've done that cause I was not able to set the image so I tried to save it to understand if it's ok or it's a error), and voila! An graceful error appears:
Error Domain=NSCocoaErrorDomain Code=-1 "(null)"
So I wanna know maybe someone knows what to do, how I can fix this, here is the link on gitHub for the Source (maybe it will help):
https://github.com/MrMatthias/SwiftColorPicker
Thanks!
I've made a helpful swift extension to create a UIImage of a solid color.
Feel free to use it:
extension UIImage {
static func image(of color: UIColor, size: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
color.set()
UIRectFill(CGRect(origin: CGPoint.zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Then you can use it by:
let redImage = UIImage.image(of: .red, size: CGSize(width: 200, height: 200))
Hope that helps!

Why is my screen not rendering anything?

I have the following code. When I run this I am getting a blank screen. This block of code is inside sceneDidLoad() so they will get executed but not displaying anything. Am I missing something ?
let worldNode = SKNode()
let height = 100
let width = 100
let regions:[Float: String] = [-0.04: "sand", -0.08: "water", 0.9: "grass"]
let noiseSource = GKPerlinNoiseSource()
let noise: GKNoise = GKNoise(noiseSource: noiseSource)
let noiseMap: GKNoiseMap = GKNoiseMap(noise: noise)
let tileMapNode = SKTileMapNode()
tileMapNode.enableAutomapping = true
tileMapNode.numberOfRows = height
tileMapNode.numberOfColumns = width
worldNode.addChild(tileMapNode)
for y in 0 ... height {
for x in 0 ... width {
let currentHeight = noiseMap.value(atPosition: vector2(Int32(x), Int32(y)));
for (key, value) in regions {
if (currentHeight <= key) {
//colourMap [y * mapChunkSize + x] = regions[i];
let tileSize = CGSize(width: 32.0, height: 32.0)
let tileTexture = SKTexture(imageNamed: value)
let tileDef = SKTileDefinition(texture: tileTexture, size: tileSize)
let tileGroup = SKTileGroup(tileDefinition: tileDef)
tileMapNode.setTileGroup(tileGroup, forColumn: x, row: y)
print("Tiling: \(value)")
break;
}
}
}
}
self.addChild(worldNode)

Logarithmic y-axis in swift?

I'm working on a financial application that has some quite large numbers.
One of my charts for example is growing from $25,000 to about $20,000,000,000 (exponentially)
Because of this, using any regular chart without log-scale causes the majority of my data to simply "flatline", while it's in fact growing quickly.
Here's an example
(Ignore that it's not set up yet)
The above linechart was made using SwiftChart
Now I have to be honest, I am not even at this point entirely sure if I'm even allowed to ask a question like this on SO. But I just have no idea where to begin.
After a couple of hours of searching I have not been able to find any libraries, tutorials, sample apps or anything IOS related that features a graph with a logarithmic axis (neither X or Y).
After I couldn't find anything, I realised I'd have to implement this feature on my own. I found the most suitable library for my application, and I've been trying to figure out how I can implement a way to allow the y-scale to be scaled with a log-scale (with the power of 10).
I've done a fair bit of research on how log-scales work, and used them plenty in my university. But I have never actually seen an example of one programmed.
I found one written in JavaScript (I think). But unfortunately I have no experience with web-development and wasn't able to properly translate it :(
I really don't feel comfortable putting someone elses code on SO, but I really have no clue where to begin.
If this isn't allowed, please post a comment, and I'll get rid of it immediately
All credits of this goes to this guy: https://github.com/gpbl/SwiftChart
Here's the code that I believe is where I need to implement a log-scale.
private func drawChart() {
drawingHeight = bounds.height - bottomInset - topInset
drawingWidth = bounds.width
let minMax = getMinMax()
min = minMax.min
max = minMax.max
highlightShapeLayer = nil
// Remove things before drawing, e.g. when changing orientation
for view in self.subviews {
view.removeFromSuperview()
}
for layer in layerStore {
layer.removeFromSuperlayer()
}
layerStore.removeAll()
// Draw content
for (index, series) in enumerate(self.series) {
// Separate each line in multiple segments over and below the x axis
var segments = Chart.segmentLine(series.data as ChartLineSegment)
for (i, segment) in enumerate(segments) {
let scaledXValues = scaleValuesOnXAxis( segment.map( { return $0.x } ) )
let scaledYValues = scaleValuesOnYAxis( segment.map( { return $0.y } ) )
if series.line {
drawLine(xValues: scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
if series.area {
drawArea(xValues: scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
}
}
drawAxes()
if xLabels != nil || series.count > 0 {
drawLabelsAndGridOnXAxis()
}
if yLabels != nil || series.count > 0 {
drawLabelsAndGridOnYAxis()
}
}
// MARK: - Scaling
private func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
// Start with user-provided values
var min = (x: minX, y: minY)
var max = (x: maxX, y: maxY)
// Check in datasets
for series in self.series {
let xValues = series.data.map( { (point: ChartPoint) -> Float in
return point.x } )
let yValues = series.data.map( { (point: ChartPoint) -> Float in
return point.y } )
let newMinX = minElement(xValues)
let newMinY = minElement(yValues)
let newMaxX = maxElement(xValues)
let newMaxY = maxElement(yValues)
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
// Check in labels
if xLabels != nil {
let newMinX = minElement(xLabels!)
let newMaxX = maxElement(xLabels!)
if min.x == nil || newMinX < min.x { min.x = newMinX }
if max.x == nil || newMaxX > max.x { max.x = newMaxX }
}
if yLabels != nil {
let newMinY = minElement(yLabels!)
let newMaxY = maxElement(yLabels!)
if min.y == nil || newMinY < min.y { min.y = newMinY }
if max.y == nil || newMaxY > max.y { max.y = newMaxY }
}
if min.x == nil { min.x = 0 }
if min.y == nil { min.y = 0 }
if max.x == nil { max.x = 0 }
if max.y == nil { max.y = 0 }
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
}
private func scaleValuesOnXAxis(values: Array<Float>) -> Array<Float> {
let width = Float(drawingWidth)
var factor: Float
if max.x - min.x == 0 { factor = 0 }
else { factor = width / (max.x - min.x) }
let scaled = values.map { factor * ($0 - self.min.x) }
return scaled
}
private func scaleValuesOnYAxis(values: Array<Float>) -> Array<Float> {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 { factor = 0 }
else { factor = height / (max.y - min.y) }
let scaled = values.map { Float(self.topInset) + height - factor * ($0 - self.min.y) }
return scaled
}
private func scaleValueOnYAxis(value: Float) -> Float {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 { factor = 0 }
else { factor = height / (max.y - min.y) }
let scaled = Float(self.topInset) + height - factor * (value - min.y)
return scaled
}
private func getZeroValueOnYAxis() -> Float {
if min.y > 0 {
return scaleValueOnYAxis(min.y)
}
else {
return scaleValueOnYAxis(0)
}
}
// MARK: - Drawings
private func isVerticalSegmentAboveXAxis(yValues: Array<Float>) -> Bool {
// YValues are "reverted" from top to bottom, so min is actually the maxz
let min = maxElement(yValues)
let zero = getZeroValueOnYAxis()
return min <= zero
}
private func drawLine(#xValues: Array<Float>, yValues: Array<Float>, seriesIndex: Int) -> CAShapeLayer {
let isAboveXAxis = isVerticalSegmentAboveXAxis(yValues)
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, CGFloat(xValues.first!), CGFloat(yValues.first!))
for i in 1..<yValues.count {
let y = yValues[i]
CGPathAddLineToPoint(path, nil, CGFloat(xValues[i]), CGFloat(y))
}
var lineLayer = CAShapeLayer()
lineLayer.frame = self.bounds
lineLayer.path = path
if isAboveXAxis {
lineLayer.strokeColor = series[seriesIndex].colors.above.CGColor
}
else {
lineLayer.strokeColor = series[seriesIndex].colors.below.CGColor
}
lineLayer.fillColor = nil
lineLayer.lineWidth = lineWidth
lineLayer.lineJoin = kCALineJoinBevel
self.layer.addSublayer(lineLayer)
layerStore.append(lineLayer)
return lineLayer
}
private func drawArea(#xValues: Array<Float>, yValues: Array<Float>, seriesIndex: Int) {
let isAboveXAxis = isVerticalSegmentAboveXAxis(yValues)
let area = CGPathCreateMutable()
let zero = CGFloat(getZeroValueOnYAxis())
CGPathMoveToPoint(area, nil, CGFloat(xValues[0]), zero)
for i in 0..<xValues.count {
CGPathAddLineToPoint(area, nil, CGFloat(xValues[i]), CGFloat(yValues[i]))
}
CGPathAddLineToPoint(area, nil, CGFloat(xValues.last!), zero)
var areaLayer = CAShapeLayer()
areaLayer.frame = self.bounds
areaLayer.path = area
areaLayer.strokeColor = nil
if isAboveXAxis {
areaLayer.fillColor = series[seriesIndex].colors.above.colorWithAlphaComponent(areaAlphaComponent).CGColor
}
else {
areaLayer.fillColor = series[seriesIndex].colors.below.colorWithAlphaComponent(areaAlphaComponent).CGColor
}
areaLayer.lineWidth = 0
self.layer.addSublayer(areaLayer)
layerStore.append(areaLayer)
}
private func drawAxes() {
let context = UIGraphicsGetCurrentContext()
CGContextSetStrokeColorWithColor(context, axesColor.CGColor)
CGContextSetLineWidth(context, 0.5)
// horizontal axis at the bottom
CGContextMoveToPoint(context, 0, drawingHeight + topInset)
CGContextAddLineToPoint(context, drawingWidth, drawingHeight + topInset)
CGContextStrokePath(context)
// horizontal axis at the top
CGContextMoveToPoint(context, 0, 0)
CGContextAddLineToPoint(context, drawingWidth, 0)
CGContextStrokePath(context)
// horizontal axis when y = 0
if min.y < 0 && max.y > 0 {
let y = CGFloat(getZeroValueOnYAxis())
CGContextMoveToPoint(context, 0, y)
CGContextAddLineToPoint(context, drawingWidth, y)
CGContextStrokePath(context)
}
// vertical axis on the left
CGContextMoveToPoint(context, 0, 0)
CGContextAddLineToPoint(context, 0, drawingHeight + topInset)
CGContextStrokePath(context)
// vertical axis on the right
CGContextMoveToPoint(context, drawingWidth, 0)
CGContextAddLineToPoint(context, drawingWidth, drawingHeight + topInset)
CGContextStrokePath(context)
}
Any hint or help as to where I need to implement this would be massively appreciated.
And I apologize for the awfulness that is this question begging for help or a direction.

Resources