Repeat Texture on X axis over 3dObject in ARkit SCNODE - SWIFT - ios

I wanted to move the texture over the SCNode in my current scene.
I am currently animating the node but i want the texture to run over the object instead of animating it.
let moveup = SCNAction.moveBy(x: 0.01, y: 0, z: 0, duration: 1)
moveup.timingMode = .easeInEaseOut
moveup.speed = CGFloat(20.5)
let moveDown = SCNAction.moveBy(x: -0.01, y: 0, z: 0, duration: 1)
moveDown.timingMode = .easeInEaseOut;
moveDown.speed = CGFloat(20.5)
let moveupSequence = SCNAction.sequence([moveup, moveDown])
print(moveupSequence.duration, moveupSequence.speed)
WaterNode?.runAction(moveupSequence)
This is my code for annimating the object. I need to know how can i access the texture and move them to X axis.

There is contentsTransform property of material
let action = SCNAction.customAction(duration: 1) { (node, elapsedTime) in
let c = node.geometry?.materials.first?.diffuse.contentsTransform
node.geometry?.materials.first?.diffuse.contentsTransform = SCNMatrix4MakeTranslation((c?.m41)! + 0.01, (c?.m42)!, (c?.m43)!)
}

Related

How to add Shadow on surface plane?

I Have one problem, In ARView Display Shadow on the object but did not display on the surface, I have attached try code? It is working Display shadow on the object but does not display on the surface.
Show Image
Code :
var light2 = SCNLight()
var lightNodeb2 = SCNNode()
light2.castsShadow = true
light2.automaticallyAdjustsShadowProjection = true
light2.maximumShadowDistance = 40.0
light2.orthographicScale = 1
light2.type = .directional
light2.shadowMapSize = CGSize(width: 2048, height: 2048)
light2.shadowMode = .forward
light2.shadowSampleCount = 128
light2.shadowRadius = 3
light2.shadowBias = 32
light2.zNear=1;
light2.zFar=1000;
lightNodeb2.light = light2
lightNodeb2.position = SCNVector3(x: -1, y: 10, z: 1)
lightNodeb2.rotation = SCNVector4Make(2, 0, 0, -Float.pi / 3)
self.sceneView.scene.rootNode.addChildNode(lightNodeb2)
I see here 2 possible problems:
Your light's settings are OK. I think the first problem is following: you use programmatic light with "non-programmatic" 3D objects in Scene graph.
Check it. In this code all objects are programmatic:
let sphereNode1 = SCNNode(geometry: SCNSphere(radius: 1))
sphereNode1.position = SCNVector3(x: 0, y: 5, z: 3)
scene.rootNode.addChildNode(sphereNode1)
let sphereNode2 = SCNNode(geometry: SCNSphere(radius: 3))
sphereNode2.position = SCNVector3(x: 0, y: 1, z: 0)
scene.rootNode.addChildNode(sphereNode2)
let boxNode = SCNNode(geometry: SCNBox(width: 20, height: 0.1, length: 20, chamferRadius: 0))
boxNode.position = SCNVector3(x: 0, y: -3, z: 0)
scene.rootNode.addChildNode(boxNode)
let light2 = SCNLight()
let lightNodeb2 = SCNNode()
light2.castsShadow = true
light2.automaticallyAdjustsShadowProjection = true
light2.maximumShadowDistance = 40.0
light2.orthographicScale = 1
light2.type = .directional
light2.shadowMapSize = CGSize(width: 2048, height: 2048)
light2.shadowMode = .deferred // Renders shadows in a postprocessing pass
light2.shadowSampleCount = 128
light2.shadowRadius = 3
light2.shadowBias = 32
light2.zNear = 1
light2.zFar = 1000
lightNodeb2.light = light2
// DIRECTIONAL LIGHT POSITION doesn't matter. Matters only its direction.
// lightNodeb2.position = SCNVector3(x: -1, y: 10, z: 1)
lightNodeb2.rotation = SCNVector4(2, 0, 0, -Float.pi/3)
scene.rootNode.addChildNode(lightNodeb2)
All objects Shadow castings are On by default.
First problem's Solution:
To make your 3D model to become accessible for virtual programmatic lights use the following approach:
func childNode(withName name: String, recursively: Bool) -> SCNNode? {
return SCNNode()
}
let geometryNode = childNode(withName: "art.scnassets/your3Dmodel",
recursively: true)!
scene.rootNode.addChildNode(geometryNode)
Second problem's Solution:
And if you wanna have a hidden plane with a shadow use this code:
hiddenPlaneNode.castsShadow = false
hiddenPlaneNode.geometry?.firstMaterial?.lightingModel = .constant
hiddenPlaneNode.geometry?.firstMaterial?.isDoubleSided = true
hiddenPlaneNode.geometry?.firstMaterial?.readsFromDepthBuffer = true
hiddenPlaneNode.geometry?.firstMaterial?.writesToDepthBuffer = true
hiddenPlaneNode.geometry?.firstMaterial?.colorBufferWriteMask = []
light2.shadowMode = .deferred

Rotate the Scene using panGestureRecognizer

I'm a new programmer working on a simple demo iOS program which I use SceneKit to render the scene.
I want to rotate the camera to see different perspective of the scene. But the original camera control is little bit tricky. Especially when I want to rotate the scene in one direction. e.g. if I swipe from right to left and the camera goes from right to left, then go downwards, then go upwards, then go downwards again, finally it goes left again.
What I want to achieve is that when I swipe from right to the left, the camera rotate around the z-axis. And when I swipe up and down my camera just move up and down. And I can also zoom in and out. Additionally I want to set a constraint so that the camera won't go underground.
So I come up with an idea that I fix the camera to look at the object that I focus on. Then place the object in the center of the scene. And I want to rotate the scene just around z-axis when I swipe the screen.
I assume that I use panGestureRecognizer and translate the position change to a rotation order.But how can I achieve that?
Or you have some other idea to get this results?
Here's a simple version of what I write so far. I've tried using UIPanGestureRecognizer but it didn't work, so I delete it and set allowCameraControl = true
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
let scene = SCNScene()
sceneView.scene = scene
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: -5.0, y: 5.0, z: 5.0)
let light = SCNLight()
light.type = SCNLight.LightType.spot
light.spotInnerAngle = 30
light.spotOuterAngle = 80
light.castsShadow = true
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
let ambientLight = SCNLight()
ambientLight.type = SCNLight.LightType.ambient
ambientLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
cameraNode.light = ambientLight
let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let cubeNode = SCNNode(geometry: cubeGeometry)
let planeGeometry = SCNPlane(width: 50.0, height: 50.0)
let planeNode = SCNNode(geometry: planeGeometry)
planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)
cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
let constraint = SCNLookAtConstraint(target: cubeNode)
constraint.isGimbalLockEnabled = true
cameraNode.constraints = [constraint]
lightNode.constraints = [constraint]
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(cubeNode)
scene.rootNode.addChildNode(planeNode)
sceneView.allowsCameraControl = true
I solved this problem by using Spherical coordinate system.
General idea is to get the start position and the angle, then transform it to spherical coordinate system. Then you do rotation by just changing the two angle.
It's even easier to use spherical coordinate system to deal with pinch gesture. You just have to change the radius.
here's some code that I use.
func handlePan(_ gestureRecognize: UIPanGestureRecognizer) {
// retrieve scene
let carView = self.view as! SCNView
// save node data and pan gesture data
let cameraNode = carView.scene?.rootNode.childNode(withName: "Camera", recursively: true)!
//let translation = gestureRecognize.translation(in: gestureRecognize.view!)
let speed = gestureRecognize.velocity(in: gestureRecognize.view!)
var speedX = sign(Float(speed.x)) * Float(sqrt(abs(speed.x)))
var speedY = sign(Float(speed.y)) * Float(sqrt(abs(speed.y)))
if speedX.isNaN {speedX = 0}
if speedY.isNaN {speedY = 0}
// record start value
let cameraXStart = cameraNode!.position.x
let cameraYStart = cameraNode!.position.y
let cameraZStart = cameraNode!.position.z - 1.0
let cameraAngleStartZ = cameraNode!.eulerAngles.z
let cameraAngleStartX = cameraNode!.eulerAngles.x
let radiusSquare = cameraXStart * cameraXStart + cameraYStart * cameraYStart + cameraZStart * cameraZStart
// calculate delta value
let deltaAngleZ = -0.003 * Float(speedX)
let deltaAngleX = -0.003 * Float(speedY)
// get new Value
var cameraNewAngleZ = cameraAngleStartZ + deltaAngleZ
var cameraNewAngleX = cameraAngleStartX + deltaAngleX
if cameraNewAngleZ >= 100 * Float.pi {
cameraNewAngleZ = cameraNewAngleZ - 100 * Float.pi
} else if cameraNewAngleZ < -100 * Float.pi {
cameraNewAngleZ = cameraNewAngleZ + 100 * Float.pi
} else {
// set limit
if cameraNewAngleX > 1.4 {
cameraNewAngleX = 1.4
} else if cameraNewAngleX < 0.1 {
cameraNewAngleX = 0.1
}
// use angle value to get position value
let cameraNewX = sqrt(radiusSquare) * cos(cameraNewAngleZ - Float.pi/2) * cos(cameraNewAngleX - Float.pi/2)
let cameraNewY = sqrt(radiusSquare) * sin(cameraNewAngleZ - Float.pi/2) * cos(cameraNewAngleX - Float.pi/2)
let cameraNewZ = -sqrt(radiusSquare) * sin(cameraNewAngleX - Float.pi/2) + 1
if cameraNode?.camera?.usesOrthographicProjection == false {
cameraNode?.position = SCNVector3Make(cameraNewX, cameraNewY, cameraNewZ)
cameraNode?.eulerAngles = SCNVector3Make(cameraNewAngleX, 0, cameraNewAngleZ)
}
else if cameraNode?.camera?.usesOrthographicProjection == true {
cameraNode?.position = SCNVector3Make(0, 0, 10)
cameraNode?.eulerAngles = SCNVector3Make(0, 0, cameraNewAngleZ)
}
}
}

Scene Kit node not rotating

I am trying to rotate by scene kit node, however it is not rotating.
I want it to rotate around the y axis. It is a sphere.
let node = SCNNode()
node.geometry = SCNSphere(radius: 1)
node.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "diffuse.png")
node.geometry?.firstMaterial?.specular.contents = UIImage(named: "specular.png")
node.geometry?.firstMaterial?.emission.contents = UIImage(named: "emission.png")
scene.rootNode.addChildNode(node)
let action = SCNAction.rotate(by:360 * CGFloat((Double.pi)/180.0), around: SCNVector3(x: 0, y: 1, z: 0), duration: 8)
let repeatAction = SCNAction.repeatForever(action)
node.runAction(repeatAction)
Are you certain it isn't rotating? I'm not sure what your diffuse, specular and emission images are but maybe because it's a sphere it just doesn't really look like it is rotating because it looks the same from all sides.
Running the same action on a cube in viewDidAppear is working for me, so it could just be that you can't really notice the sphere rotating. I used this code:
let node = SCNNode()
node.geometry = SCNBox (width: 0.5, height: 0.5, length: 0.5, chamferRadius: 0.1)
node.position = SCNVector3Make(0, 0, -1)
sceneView.scene.rootNode.addChildNode(node)
let action = SCNAction.rotate(by:360 * CGFloat((Double.pi)/180.0), around: SCNVector3(x: 0, y: 1, z: 0), duration: 8)
let repeatAction = SCNAction.repeatForever(action)
node.runAction(repeatAction)
The cube is rotating fine with the way that you rotated the SCNNode.
Hope this helps

SceneKit: vertically center scaled SCNNode inside parent?

The goal is to scale a SCNNode and vertically center it within its parent.
However, scaling a SCNNode does not affect its bounding box and computing the scaled height does not work. Without an accurate height, how can you vertically center the node inside its parent?
To illustrate the problem with using the scaled height, see the attached file, Tiki.dae. The original height of the asset (as shown by the bounding box) is 324.36. If you set the Y-scale to 0.01, however, the height doesn't become ~3.24. It becomes smaller than 3, which you can prove by fitting it comfortably within a sphere of height 3 (radius of 1.5).
The code below attempts to center a scaled node inside its parent, but it doesn't work.
Note: reference node is the fox/panda reference node from the WWDC 2015 fox demo.
// Create reference node
let referenceNode = SCNReferenceNode(URL: referenceURL)
referenceNode?.load()
// Scale reference node
let scale = Float(3)
referenceNode?.scale = SCNVector3(x: scale, y: scale, z: scale)
// Create geometry for sphere
let sphereGeometry = SCNSphere(radius: (gridSphere.geometry as! SCNSphere).radius)
//sphereGeometry.materials = gridSphere.geometry!.materials
sphereGeometry.firstMaterial!.diffuse.contents = gPurpleColor
// Create sphere to hold reference node, position at same place as <gridSphere>
let liveSphere = SCNNode(geometry: sphereGeometry)
liveSphere.position = gridSphere.position
// Center reference node inside <liveSphere>
var min = SCNVector3Zero
var max = SCNVector3Zero
referenceNode?.getBoundingBoxMin(&min, max: &max)
let referenceNodeHeight = max.y - min.y
referenceNode?.position = SCNVector3(x: 0, y: 0 - referenceNodeHeight, z: 0)
// Add reference node to <liveSphere>
liveSphere.addChildNode(referenceNode!)
// This value never changes no matter the scale value???
print(referenceNodeHeight)
Here's a Playground that adds a cube (in place of your reference node) as a child of the sphere. The cube responds to scaling (see the lines following "// Scale reference node").
//: Playground - noun: a place where people can play
import Cocoa
import SceneKit
import XCPlayground
public class GizmoNode: SCNNode {
required public override init() {
super.init()
let axisLength = CGFloat(3.0)
let offset = CGFloat(axisLength/2.0)
let axisSide = CGFloat(0.2)
let chamferRadius = CGFloat(axisSide)
let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
let xNode = SCNNode(geometry: xBox)
let yNode = SCNNode(geometry: yBox)
let zNode = SCNNode(geometry: zBox)
self.addChildNode(xNode)
self.addChildNode(yNode)
self.addChildNode(zNode)
print (xNode.position)
print (yNode.position)
print (zNode.position)
xNode.position.x = offset
yNode.position.y = offset
zNode.position.z = offset
print (xNode.pivot)
print (yNode.pivot)
print (zNode.pivot)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = NSColor.blackColor()
sceneView.allowsCameraControl = true
let gizmo = GizmoNode()
scene.rootNode.addChildNode(gizmo)
XCPlaygroundPage.currentPage.liveView = sceneView
// Create reference node
let cubeSize = CGFloat(0.5)
let cubeGeometry = SCNBox(width: cubeSize, height: cubeSize, length: cubeSize, chamferRadius: 0.0)
let referenceNodeStandIn = SCNNode(geometry: cubeGeometry)
//referenceNodeStandIn?.load()
let cubeColor = NSColor.whiteColor().colorWithAlphaComponent(0.5)
cubeGeometry.firstMaterial!.diffuse.contents = cubeColor
// Scale reference node
let scale = CGFloat(8)
referenceNodeStandIn.scale = SCNVector3(x: scale, y: scale, z: scale)
// Create geometry for sphere
let sphereRadius = CGFloat(2.0)
let sphereGeometry = SCNSphere(radius: sphereRadius)
//sphereGeometry.materials = gridSphere.geometry!.materials
let gPurpleColor = NSColor.purpleColor().colorWithAlphaComponent(1.0)
sphereGeometry.firstMaterial!.diffuse.contents = gPurpleColor
// Create sphere to hold reference node, position at same place as <gridSphere>
let liveSphere = SCNNode(geometry: sphereGeometry)
//liveSphere.position = gridSphere.position
scene.rootNode.addChildNode(liveSphere)
// Center reference node inside <liveSphere>
var min = SCNVector3Zero
var max = SCNVector3Zero
referenceNodeStandIn.getBoundingBoxMin(&min, max: &max)
print("min: \(min) max: \(max)")
let referenceNodeHeight = max.y - min.y
//referenceNodeStandIn.position = SCNVector3(x: 0, y: 0 - referenceNodeHeight, z: 0)
// Add reference node to <liveSphere>
liveSphere.addChildNode(referenceNodeStandIn)
// This value never changes no matter the scale value because it's in local coordinate space
print("reference node height", referenceNodeHeight)

Strange behavior with SceneKit Node

I created a circular array objects, no problems.
When I rotate there appears to be a sinc or gaussian function at the center.
The camera is a z 60, radius of structure is 30.
Initial view, no artifacts
Rotated 90 deg up, no artifacts
Rotated 180 deg, artifact appears in center of object
Continued rotation, artifact is still there.
The code for the object is here
class func Build(scene: SCNScene) -> SCNNode {
let radius: Double = 30.0
let numberOfStrands: Int = 24
//Create the base chromosomes.
let baseChromosome = SCNBox(width: 4.0, height: 24, length: 1.0, chamferRadius: 0.5)
let baseChromosomes = DNA.buildCircleOfObjects(baseChromosome, numberOfItems: numberOfStrands, radius: radius)
baseChromosomes.position = SCNVector3(x: 0, y: 0.5, z: 0)
return baseChromosomes
}
class func buildCircleOfObjects(_geometry: SCNGeometry, numberOfItems: Int, radius: Double) -> SCNNode {
var x: Double = 0.0
var z: Double = radius
let theta: Double = (M_PI) / Double(numberOfItems / 2)
let incrementalY: Double = (M_PI) / Double(numberOfItems) * 2
let nodeCollection = SCNNode()
nodeCollection.position = SCNVector3(x: 0, y: 0.5, z: 0)
for index in 1...numberOfItems {
x = radius * sin(Double(index) * theta)
z = radius * cos(Double(index) * theta)
let node = SCNNode(geometry: _geometry)
node.position = SCNVector3(x: Float(x), y: 0, z:Float(z))
let rotation = Float(incrementalY) * Float(index)
node.rotation = SCNVector4(x: 0, y: 1, z: 0, w: rotation)
nodeCollection.addChildNode(node)
}
return nodeCollection
}
}
Using these settings
cameraNode.camera?.xFov = 60
cameraNode.camera?.yFov = 60
cameraNode.camera?.zFar = 1000
cameraNode.camera?.zNear = 0.01
the artifacts disappeared. I think this is a problem with zFar, it should have clipped the back surface uniformly not like a lens aberration.
Falling out of Viewing frustum may be the one to cause most problem. However there is another common cause by misunderstanding the behavior of SCNView's allowsCameraControl, that is when you Pinch to zoom in or zoom out (change the camera's fieldOfView), but the pointOfView's position reminds the same.
Your nodes still stand BEHIND the pointOfView, however small the camera's zNear is. Hence you see them got clipped. The code below may help you in this case, at least avoid the problem.
sceneView.pointOfView!.simdPosition
= float3(0, 0, -scene.rootNode.boundingSphere.radius)

Resources