SceneKit – How to rotate a light? - swift

I am learning SceneKit. And I have a simple scene with a single shape and a light.
Here is the light code:
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.castsShadow = true
lightNode.light?.type = .directional
lightNode.light?.intensity = 1000
lightNode.position = SCNVector3(x: -6, y: 0, z: 30)
lightNode.rotation = SCNVector4(x: 0, y: 0, z: 1.0, w: 3.14 / 2)
scene.rootNode.addChildNode(lightNode)
It produces the following result:
When I comment/uncomment lightNode.rotation the result remains the same. However I expect, that this circle highlight at the center of the shape changes or disappears.

You are rotating the directional light around the z-axis and it will have no effect, because your light source is directed along the -Z axis by default. Try rotating your Sun light source around the x-axis or y-axis.
lightNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: -.pi / 15)
P. S.
There is no sense in using .position for Sun light. For this type of light, only orientation matters.

Related

swift SceneKit Sphere deformation after changing position

I have two spheres on the sceneView
let earthSphere = SCNSphere(radius: 5.0)
earthSphere.firstMaterial?.diffuse.contents = UIColor.green
let objNode1 = SCNNode(geometry: earthSphere)
objNode1.position = SCNVector3(x: 0, y: 0, z: 0)
let moonSphere = SCNSphere(radius: 0.5)
moonSphere.firstMaterial?.diffuse.contents = UIColor.red
let objNode2 = SCNNode(geometry: moonSphere)
objNode2.position = SCNVector3(x: 3, y: 3, z: 0)
but moonSphere is deforming image. How to fix that ? It is possible to do that earthSphere and moonSphere look same ? without deformation
etc: cameraNode set to position SCNVector3(x: 0, y: 0, z: 7)

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.

Rotate SCNNode then move relative to rotation?

My node setup:
let node = SCNNode()
node.position = SCNVector3(0.0, 0.0, 0.0)
I then wish to rotate the node by 90 degrees to the left which can be achieved with:
node.transform = SCNMatrix4Rotate(node.transform, .pi/2, 0.0, 1.0, 0.0)
Next, I want to translate the node forward one unit along the negative z axis relative to current rotation and end up with:
node.position = SCNVector3(-1.0, 0.0, 0.0)
I have no idea how to get from the rotation of the node to the position of the node programmatically.
Basically node begins at (0, 0, 0), its forward vector is the -z axis, node turns left and moves one unit forward to end up at (-1, 0, 0).
This isn't working:
func move(_ direction: moveDirection) {
switch direction {
case .forward: characterNode.position = SCNVector3(characterNode.position.x, characterNode.position.y, characterNode.position.z - 1.0)
case .left: characterNode.pivot = SCNMatrix4Rotate(characterNode.pivot, -.pi/32, 0.0, 1.0, 0.0)
case .backward: characterNode.position = SCNVector3(characterNode.position.x, characterNode.position.y, characterNode.position.z + 1.0)
case .right: characterNode.pivot = SCNMatrix4Rotate(characterNode.pivot, .pi/32, 0.0, 1.0, 0.0)
}
}
Assuming I have understood you correctly, once you have rotated your SCNNode, you want to move it forward it in that direction.
This can be done by using the worldFront value which is simply:
The local unit -Z axis (0,0,-1) in world space.
As such this may be the answer you are looking for:
//1. Create A Node Holder
let nodeToAdd = SCNNode()
//2. Create An SCNBox Geometry
let nodeGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
nodeGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
nodeGeometry.firstMaterial?.lightingModel = .constant
nodeToAdd.geometry = nodeGeometry
//3. Position It 1.5m Away From The Camera
nodeToAdd.position = SCNVector3(0, 0, -1.5)
//4. Rotate The Node By 90 Degrees On It's Y Axis
nodeToAdd.rotation = SCNVector4Make(0, 1, 0, .pi / 2)
//5. Add The Node To The ARSCNView
augmentedRealityView.scene.rootNode.addChildNode(nodeToAdd)
//6. Use The World Front To Move The Node Forward e.g
/*
nodeToAdd.simdPosition += nodeToAdd.simdWorldFront * 1.2
*/
nodeToAdd.simdPosition += nodeToAdd.simdWorldFront
//7. Print The Position
print(nodeToAdd.position)
/*
SCNVector3(x: -0.999999881, y: 0.0, z: -1.50000024)
*/

SCNKit: Scaling parent node not working

I'm trying to scale down a SCNNode that contains other nodes, but itself has no geometries. I read the documentation on scale, but I'm a little skeptical that they would make positioning relative to the parent, but not scale.
The problem:
scale does not seem to do anything.
Here's a snippet of my SCNNode sub-class
addChildNode(Node1)
addChildNode(Node2)
Node2.addChildNode(Node21)
addChildNode(Node3)
print("pre-scale", self.scale)
// prints SCNVector3(x: 1.0, y: 1.0, z: 1.0)
self.scale = SCNVector3(x:0.05, y:0.05, z:0.05)
print("post-scale", self.scale)
// prints SCNVector3(x: 0.05, y: 0.05, z: 0.05)
Visibly, nothing changes.
I've considered doing a loop and applying the scaling factor to every child node, but I think the relative positions will get all messed up.
I'd like everything to scale as one and retain its integrity. Is there something I'm missing?
Try to scale all child nodes one by one:
let newScale = Float(0.05)
for c in self.childNodes
{
c.scale = SCNVector3Make(newScale, newScale, newScale)
}
The parent node may not assign to any camera. try adding the camera to the node and try the scaling.
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
scene.rootNode.addChildNode(cameraNode)
parentNode.scale = SCNVector3(0.5, 0.5, 0.5)

Swift: Make translucent overlapping lines of the same color not change color when intersecting

Currently I have drawn 2 lines on the screen which are the same color but both have an alpha value less than 1. When these lines intersect, the intersection is a different color than the rest of the lines. There was a previous post addressing the same issue: swift drawing translucent lines, how to make overlapping parts not getting darker? However, this post wasn't sufficiently answered. I draw the lines currently like this:
var points = [CGPoint]()
points = [CGPoint(x: -100, y: 100), CGPoint(x: 100, y: -100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 0.5)
FirstLine.lineWidth = 30
addChild(FirstLine)
points = [CGPoint(x: 100, y: 100), CGPoint(x: -100, y: -100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 0.5)
SecondLine.lineWidth = 30
addChild(SecondLine)
Here is what it looks like:
I understand why this occurs, but is there any way to make the intersection look the same so it looks much better?
Edit: I have decided to implement #Confused's answer. However, the problem now is that the texture always centres to the middle of the screen. This is an example:
The red cross is in its correct position as it is connecting the specified points together. However, once I make the red cross a texture, it always centres to the middle of the screen (the green cross is the red cross as a texture). Can I use any code that could re-position the texture to its correct position. Note: this code can't just work for this example, I need one that works all the time regardless of the red cross' position.
FINAL EDIT FOR PEOPLE WITH THE SAME PROBLEM
First, setup everything like this:
var points = [CGPoint]()
let crossParent = SKNode()
addChild(crossParent)
Please note THAT YOU MUST create a parent SKNode for the texture otherwise everything on the screen will become the texture and not just the node that you want. Then, add that parent node to the scene.
After, create the lines that you want (in this case the green cross):
//The first line of the green cross
points = [CGPoint(x: -300, y: 300), CGPoint(x: -100, y: 100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1.0)
FirstLine.lineWidth = 30
crossParent.addChild(FirstLine)
Remember to NOT add the first line that you create to the scene, but rather to the parent SKNode that you made at the beginning. Also, set the alpha value to 1.0 for every line that you draw. Then add your other lines:
//The second line of the green cross
points = [CGPoint(x: -100, y: 300), CGPoint(x: -300, y: 100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1.0)
SecondLine.lineWidth = 30
FirstLine.addChild(SecondLine)
Note that you MUST add that line to the first line like this and not to the scene. If you are adding more than one line after adding the first line, then add it to the first line like I have done here too for each consecutive
line that you add.
Now, create the texture like this:
let tex = view?.texture(from: FirstLine)
let cross = SKSpriteNode(texture: tex, color: .clear, size: (tex?.size())!)
cross.alpha = 0.5
addChild(cross)
After doing this, whatever you call cross will be your texture, and you can change the alpha value like I have done here to anything you like and the image will not have varying colors. Remember to then add that texture to the scene.
Finally, you may notice that the texture is not in the same position as where you originally put the points. You can put it back into the same position like this:
cross.position = CGPoint(x: (FirstLine.frame.midX), y: (FirstLine.frame.midY))
Hope this helps :) Thank you to #Confused for the texture part of the program :D
This is a technique. It does not solve your problem, nor directly answer your question. Instead it offers a way to have the desired result, but without the flexibility and inherent nature of lines you actually want.
You can create textures from anything you draw with any number of nodes, with any number of techniques.
You do this (easiest way) by attaching all the drawing elements to a single node, within an SKView space that you have access to, and then render the "parent" node of your drawn objects to a texture.
How does this help?
I'm glad you asked:
You can draw everything at an opacity level of 100%, and render it to a texture, then take that texture of your drawings, put it where you like, and reduce its opacity to any percentage you like, and get an even result. No bright spots where things overlay each other.
Here's code that does all the above:
var points = [CGPoint]()
points = [CGPoint(x: -100, y: 100), CGPoint(x: 100, y: -100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
FirstLine.lineWidth = 30
// ^^ Note the FirstLine is not added to the Scene
points = [CGPoint(x: 100, y: 100), CGPoint(x: -100, y: -100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
SecondLine.lineWidth = 30
FirstLine.addChild(SecondLine)
// ^^ Note SecondLine being added to FirstLine, and that they both have alpha of 1
// Now the magic: use the view of the SKScene to render FirstLine and its child (SecondLine)
// They are rendered into a texture, named, imaginatively, "tex"
let tex = view.texture(from: FirstLine)
let cross = SKSpriteNode(texture: tex, color: .clear, size: (tex?.size())!)
cross.alpha = 0.5
// ^^ The alpha of the above sprite is set to your original desire of 0.5
// And then added to the scene, with the desired result.
addChild(cross)
and here's the result:
NO! I think is, unfortunately, the answer.
There are the following blend modes, none of which provide the result you're looking for:
case alpha // Blends the source and destination colors by multiplying the source alpha value.
case add // Blends the source and destination colors by adding them up.
case subtract // Blends the source and destination colors by subtracting the source from the destination.
case multiply // Blends the source and destination colors by multiplying them.
case multiplyX2 // Blends the source and destination colors by multiplying them and doubling the result.
case screen // Blends the source and destination colors by multiplying one minus the source with the destination and adding the source.
case replace // Replaces the destination with the source (ignores alpha).
The only option that might work is a custom shader that figures out if it's overlapping itself, somehow. But I have NO CLUE where to even start making something like that, or if it's even possible.
Make the stroke color fully opaque.
Add your path nodes to an SKEffectNode.
Set the alpha of the effect node to the desired value.
Put the effect node into your main scene.
While doing this you can also put all lines in one shape node:
let path = CGMutablePath()
path.move( to: CGPoint(x: -100, y: 100))
path.addLine(to: CGPoint(x: 100, y: -100))
path.move( to: CGPoint(x: 100, y: 100))
path.addLine(to: CGPoint(x: -100, y: -100))
let shape = SKShapeNode(path: path)
shape.strokeColor = UIColor(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
shape.lineWidth = 30
let effect = SKEffectNode()
effect.addChild(shape)
effect.alpha = 0.5
addChild(effect)
The problem is you are doing the alpha blend at the color level, do the alpha blending at the node level
var points = [CGPoint]()
var cross = SKEffectNode()
points = [CGPoint(x: -100, y: 100), CGPoint(x: 100, y: -100)]
let FirstLine = SKShapeNode(points: &points, count: points.count)
FirstLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
FirstLine.lineWidth = 30
cross.addChild(FirstLine)
points = [CGPoint(x: 100, y: 100), CGPoint(x: -100, y: -100)]
let SecondLine = SKShapeNode(points: &points, count: points.count)
SecondLine.strokeColor = UIColor.init(red: 0.25, green: 0.62, blue: 0.0, alpha: 1)
SecondLine.lineWidth = 30
cross.addChild(SecondLine)
cross.alpha = 0.5
addChild(cross)