simd_float4x4 Columns - swift

I want to translate a plane without rotating the image. For any reason my image is being rotated.
var translation = matrix_identity_float4x4
translation.colum = -0.2
let transform = simd_mul(currentFrame.camera.transform, translation)
planeNode.simdWorldTransform = matrix_multiply(currentFrame.camera.transform, translation)
Also, I notice that matrix_identity_float4x4 contains 4 columns but the documentation is not available.
Why 4 columns? Are there the frame of the plane?

The simplest way to do it is to use the following code for positioning:
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 20, height: 20)
// At first we need to rotate a plane about its x axis in radians:
planeNode.rotation = SCNVector4(1, 0, 0, -Double.pi/2)
planeNode.geometry?.materials.first?.diffuse.contents = UIColor.red
planeNode.position.x = 10
planeNode.position.z = 10
// planeNode.position = SCNVector3(x: 10, y: 0, z: 10)
scene.rootNode.addChildNode(planeNode)
or this way:
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 20, height: 20)
planeNode.rotation = SCNVector4(1, 0, 0, -Double.pi/2)
planeNode.geometry?.materials.first?.diffuse.contents = UIColor.red
let distance: Float = 50
planeNode.simdPosition = cameraNode.simdWorldFront * distance // -Z axis
planeNode.simdPosition = cameraNode.simdWorldRight * distance // +X axis
scene.rootNode.addChildNode(planeNode)
If you wanna know more about matrices used in ARKit and SceneKit frameworks just look at Figure 1-8 Matrix configurations for common transformations.
Hope this helps.

Related

Increase displacement resolution / smooth vertices in SceneKit tessellated mesh

I'm trying to generate a mesh from a depth/displacement map using SceneKit
The source depth map I'm using looks like this:
I then generate a plane with an increased segment count and heavily tessellate it and apply the displacement material. Here's the code I'm using:
import Cocoa
import SceneKit
import PlaygroundSupport
// MARK: - View setup
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.camera?.zFar = 1000
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
let scnView = SCNView(frame: NSRect(x: 0, y: 0, width: 400, height: 400))
scnView.autoenablesDefaultLighting = true
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.black
PlaygroundPage.current.liveView = scnView
// MARK: - Plane
let plane = SCNPlane(width: 200, height: 200)
plane.widthSegmentCount = 10
plane.heightSegmentCount = 10
let planeNode = SCNNode(geometry: plane)
planeNode.name = "Plane"
planeNode.geometry?.firstMaterial?.diffuse.contents = NSColor.white
let tessellator = SCNGeometryTessellator()
tessellator.tessellationFactorScale = 25
tessellator.tessellationPartitionMode = .pow2
tessellator.insideTessellationFactor = 4
tessellator.edgeTessellationFactor = 4
tessellator.smoothingMode = .phong
planeNode.geometry?.tessellator = tessellator
planeNode.geometry?.firstMaterial?.displacement.contents = "rabbit"
planeNode.geometry?.firstMaterial?.displacement.textureComponents = .red
planeNode.geometry?.firstMaterial?.displacement.intensity = 200
planeNode.geometry?.firstMaterial?.displacement.maxAnisotropy = 1
planeNode.geometry?.firstMaterial?.displacement.magnificationFilter = .none
planeNode.geometry?.firstMaterial?.lightingModel = .phong
scene.rootNode.addChildNode(planeNode)
Which mostly gives me the 3D mesh I'm aiming for:
But the resulting "blockiness" messes with the lighting. Wireframe view shows it clearer:
Is there any way to "smoothen" the resulting mesh, average vertex positions, or something similar?
I'm very tangentially familiar with 3D and basically unfamiliar with SceneKit and google/docs haven't yielded much.
Subdivision / adaptive subdivision doesn't solve the problem, neither does increasing tessellation detail to the highest count possible. It can make the shadow/highlight patches smaller, but they're still there.
Any help or pointers are much appreciated!
I figured it out!
The ladder effect is an artifact of 8-bit banding in the depth map
Converting the image to 32-bit depth and adding a slight blur pretty much fixed it:

Align a node with its neighbor in SceneKit

Using Swift 5.5, iOS 14
Trying to create a simple 3D bar chart and I immediately find myself in trouble.
I wrote this code...
var virgin = true
for i in stride(from: 0, to: 6, by: 0.5) {
let rnd = CGFloat.random(in: 1.0...4.0)
let targetGeometry = SCNBox(width: 0.5,
height: rnd,
length: 0.5,
chamferRadius: 0.2)
targetGeometry.firstMaterial?.fillMode = .lines
targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
let box = SCNNode(geometry: targetGeometry)
box.simdPosition = SIMD3(x: 0, y: 0, z: 0)
coreNode.addChildNode(box)
}
This works well, but all the bars a centred around their centre. But how can I ask SceneKit to change the alignment?
Almost got this working with this code...
box.simdPosition = SIMD3(x: Float(i) - 3,
y: Float(rnd * 0.5),
z: 0)
But the result isn't right... I want/need the bar to grow from the base.
https://youtu.be/KJgvdBFBfyc
How can I make this grow from the base?
--Updated with working solution--
Tried Andy Jazz suggestion replacing simdposition with the following formulae.
box.simdPosition = SIMD3(x:box.simdPosition.x, y:0, z:0)
box.simdPivot.columns.3.y = box.boundingBox.max.y - Float(rnd * 0.5)
Worked well, to which I added some animation! Thanks Andy.
changer = changeling.sink(receiveValue: { [self] _ in
var rnds:[CGFloat] = []
for i in 0..<12 {
let rnd = CGFloat.random(in: 1.0...2.0)
let targetGeometry = SCNBox(width: 0.45, height: rnd, length: 0.45, chamferRadius: 0.01)
let newNode = SCNNode(geometry: targetGeometry)
sourceNodes[i].simdPivot.columns.3.y = newNode.boundingBox.min.y
sourceNodes[i].simdPosition = SIMD3(x: sourceNodes[i].simdPosition.x, y: 0, z: 0)
rnds.append(rnd)
}
for k in 0..<12 {
if virgin {
coreNode.addChildNode(sourceNodes[k])
}
let targetGeometry = SCNBox(width: 0.45, height: rnds[k], length: 0.45, chamferRadius: 0.01)
targetGeometry.firstMaterial?.fillMode = .lines
targetGeometry.firstMaterial?.diffuse.contents = UIColor.blue
let morpher = SCNMorpher()
morpher.targets = [targetGeometry]
sourceNodes[k].morpher = morpher
let animation = CABasicAnimation(keyPath: "morpher.weights[0]")
animation.toValue = 1.0
animation.repeatCount = 0.0
animation.duration = 1.0
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
sourceNodes[k].addAnimation(animation, forKey: nil)
}
virgin = false
})
You have to position a pivot point of each bar to its base:
boxNode.simdPivot.columns.3.y = someFloatNumber
To reposition a pivot to bar's base use bounding box property:
boxNode.simdPivot.columns.3.y += (boxNode.boundingBox.min.y as? simd_float1)!
After pivot's offset, reposition boxNode towards negative direction of Y-axis.
boxNode.position.y = 0

Add shadow to SCNPlane

I have ARKit artwork app. And I want my artworks to have a shadow just behind it. The shadow mustn't be dynamic. Just static shadow.
I've tried to add a plane behind the artwork. And set a shadow to it. But I discovered that it is not easy.
Can you help me?
let shadow = SCNPlane(width: artwork.size.width * 0.0254 + 0.03, height: artwork.size.height * 0.0254 + 0.015)
let layer = CALayer()
layer.frame = CGRect(origin: .zero, size: CGSize(width: shadow.width, height: shadow.height))
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 1
layer.shadowOffset = .zero
layer.shadowRadius = 10
shadow.firstMaterial?.diffuse.contents = layer
The shadow should be like in UIKit's UIView. But this code just doesn't work.
I need something like
UPDATED.
In order to cast and receive a shadows from 3D lights, you have to use a 3D primitives (like cube, sphere, cylinder, etc), or 3D models in such formats as .usdz, .dae or .obj.
Shadow on a visible plane.
Use the following code to test how light generates shadows for 3D geometry:
let scene = SCNScene()
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .spot
lightNode.light!.castsShadow = true
lightNode.light!.shadowMode = .deferred
lightNode.rotation = SCNVector4(x: -1, y: 0, z: 0, w: CGFloat.pi/2)
lightNode.position = SCNVector3(x: 0, y: 20, z: 0)
scene.rootNode.addChildNode(lightNode)
let sphereNode = SCNNode()
sphereNode.geometry = SCNSphere(radius: 2)
sphereNode.position = SCNVector3(x: 0, y: -2, z: 0)
scene.rootNode.addChildNode(sphereNode)
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 15, height: 15)
planeNode.position = SCNVector3(x: 0, y: -5, z: 0)
planeNode.rotation = SCNVector4(x: -1, y: 0, z: 0, w: CGFloat.pi/2)
planeNode.geometry?.materials.first?.diffuse.contents = UIColor.red
scene.rootNode.addChildNode(planeNode)
Shadow on invisible plane.
In case you need an invisible plane that receives a shadow, use the following code:
planeNode.geometry?.materials.first?.colorBufferWriteMask = []
planeNode.geometry?.materials.first?.writesToDepthBuffer = true
planeNode.geometry?.materials.first?.lightingModel = .constant
Fake shadow.
And if you want to use a fake shadow written as a .png (png file format can hold 4 channels) texture on geometry (with a premultiplied alpha channel – RGB x A), use the following approach:
planeNode.geometry?.materials.first?.diffuse.contents = UIImage(named: "shadow.png")
lightNode.light!.castsShadow = false
Here's my answer on fake shadows.
And here's your premultiplied shadow in .png format (just drag-and-drop it on your desktop):
You can change its size and transparency and, of course, you can blur it.

Add SCNText to SCNPlane

i am adding an SCNPlane using
artPlane = SCNPlane(width: CGFloat(widthRequired) * 0.0254,
height: CGFloat(size["width"]!) * 0.0254)
artworkNode = SCNNode(geometry: artPlane)
artworkNode?.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "loadingar")
let translation = SCNMatrix4MakeTranslation(0, -1, 0)
let rotation = SCNMatrix4MakeRotation(Float.pi / 2, 0, 0, 1)
artworkNode?.geometry?.firstMaterial?.diffuse.contentsTransform = SCNMatrix4Mult(translation, rotation)
artworkNode?.geometry?.firstMaterial?.isDoubleSided = true
artworkNode?.simdTransform = matrix
self.sceneView.scene.rootNode.addChildNode(artworkNode!)
Now i want to add an SCNText to the center of the above plane, how do i specify the position of the new textNode so that its in the center of the Plane?

SCNTransformConstraint not allowing to restrict Eulerangle

I want to limit the angle of the camera in Scenekit to not allow the camera to look upwards (point to the sky). For some reason I am not getting the SCNTransformConstraint right. What to do?
let cameraNode = SCNNode()
let camera = SCNCamera()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 10, z: 30)
let center = SCNNode()
center.position = SCNVector3Make(15, 0, 10)
let lookConstraint = SCNLookAtConstraint(target: center)
lookConstraint.isGimbalLockEnabled = true
// Make the constraint to not allow the camera to point upwards
let transformConstraint = SCNTransformConstraint(inWorldSpace: true, with: {
node, matrix in
var newMatrix = matrix
let currentNode = node as SCNNode
if (currentNode.presentation.eulerAngles.x > 0) {
newMatrix.m31 = 0.0
}
return newMatrix
})
cameraNode.constraints = [lookConstraint, transformConstraint]
scene.rootNode.addChildNode(cameraNode)
I guess I am setting the newMatrix.m31 incorrectly?