I'm trying to create ruler application with ARKit
and SceneKit
. I've decided to create ruler image programmatically depends on measured distance.
Here is an extension that I use to draw ruler:
extension UIImage {
static let dashLineWidth: CGFloat = 2.0
static let dashDistance: CGFloat = 163.0 / 25.4
static let rulerFont: UIFont = .systemFont(ofSize: 15.0, weight: .regular)
static let attributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.font: rulerFont,
NSAttributedStringKey.foregroundColor: UIColor.black
]
static func drawRuler(width: CGFloat) -> UIImage? {
let cm = width * 100 // width in centimeters
let size = CGSize(width: dashDistance * cm * 10, height: 50.0)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
let background = UIBezierPath(rect: CGRect(origin: .zero, size: size))
context.addPath(background.cgPath)
context.setFillColor(UIColor.white.cgColor)
context.fillPath()
var i: CGFloat = 0.0
var counter: Int = 0
while i < size.width {
let isLongDash = counter % 10 == 0
let isPartDash = counter % 5 == 0
let dashHeight: CGFloat = size.height * (isLongDash ? 0.25 : isPartDash ? 0.15 : 0.07)
UIColor.black.setFill()
UIRectFill(CGRect(x: i - dashLineWidth / 2, y: 0.0, width: dashLineWidth, height: dashHeight))
if isLongDash && counter != 0 {
let value = "\(counter / 10)"
let valueSize: CGSize = value.size(withAttributes: attributes)
value.draw(at: CGPoint(x: i - dashLineWidth / 2 - valueSize.width / 2, y: dashHeight + 5.0), withAttributes: attributes)
}
i += dashDistance
counter += 1
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func crop(to width: CGFloat, initialWidth: CGFloat) -> UIImage? {
let rect = CGRect(x: 0, y: 0, width: (width / initialWidth) * size.width * scale, height: size.height * scale)
guard let croppedCGImage: CGImage = cgImage?.cropping(to: rect) else { return nil }
return UIImage(cgImage: croppedCGImage)
}
}
So at first I'm drawing 0.5 meter image only once for better performance and then every time just cropping needed part to display in SCNNode
.
And here what I'm trying in my SCNNode
class:
var ruler: SCNNode = initRuler()
var initialWidth: CGFloat = 0.5
var rulerImage: UIImage? = UIImage.drawRuler(width: initialWidth)
func updateRuler() {
guard let geometry = ruler.geometry as? SCNBox else {
fatalError("Geometry is not SCNBox")
}
let width = geometry.width // in meters
if width > initialWidth - 0.05 {
initialWidth += 0.5
rulerImage = UIImage.drawRuler(width: initialWidth)
}
guard let croppedImage = rulerImage?.crop(to: width, initialWidth: initialWidth) else { return }
let texture = SKTexture(image: croppedImage)
let material = SCNMaterial()
material.diffuse.contents = texture
geometry.materials = [material]
}
Everything works fine to the moment when the size of SCNNode
becomes bigger and the image is bigger too. So around 1.3 meters I've got a crash
validateTextureDimensions:759: failed assertion `MTLTextureDescriptor has width (16501) greater than the maximum allowed size of 16384.'
Any help would be appreciated. I was thinking if I can split an image in parts and then assign in to material. Or is there an another way to do this?
不是将一个16501px
宽图像设置为一个代表整个标尺的SCNode
,而是建立数百个1cm
段的标尺更有意义,每个段都有一个纹理,您可以通过编程方式为带有数字的标尺段绘制。
First, probably the best thing to do is just find images of rulers from the internet or use something like photoshop in order to make your own, because making it programmatically isn't very fast. The error has something to do with something called MTLTextureDescriptor the problem is you are using to big of a number this may be because the device you are running it on is to small to fit the whole photo.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.