Flickering SCNTube with transparency - swift

UPDATED
I create some SCNTubes with following materials:
let radius: CGFloat = 0.3
let height: CGFloat = 0.5
let color: UIColor = UIColor(red:0.17, green:0.62, blue:0.76, alpha:1.0)
let geometryTube = SCNTube(innerRadius: (radius - 0.1), outerRadius: radius, height: height)
let matOuter = SCNMaterial()
matOuter.diffuse.contents = color
matOuter.transparency = 0.1
let matInner = SCNMaterial()
matInner.diffuse.contents = color
matInner.transparency = 0.6
let matTop = SCNMaterial()
matTop.diffuse.contents = color
geometryTube.materials = [matOuter, matInner, matTop]
for index in 0...20 {
let node = SCNNode(geometry: geometryTube)
node.position = SCNVector3(1.0 + Double(index), -1.0, 0.0);
sceneView.scene.rootNode.addChildNode(node)
}
Depending on the angle the tubes are flickering.

The materials property is designed to have one material for each geometry - whereas you are assigning three materials to a single geometry.
These are all overlapping and scenekit is having to decide what to render at each point.
EDIT: My answer (above) was wrong - rather than delete it I'll add this to highlight why I was wrong:
If a geometry has the same number of materials as it has geometry
elements, the material index corresponds to the element index. For
geometries with fewer materials than elements, SceneKit determines the
material index for each element by calculating the index of that
element modulo the number of materials. For example, in a geometry
with six elements and three materials, SceneKit renders the element at
index 5 using the material at index 5 % 3 = 2.
Apologies to OP for not being helpful with your question.

You might want to look at how your geometry is lit.
For example there are many different types of lightingModel properties you can use e.g.
Try for example to set each materials .lightingModel property to .constant e.g:
matOuter.lightingModel = .constant
Another option you may also want to explore SCNTransparencyMode as well...

Related

How to exclude positions in a field in SpriteKit?

I have a function that spawns little balls, randomly positioned, on the screen. The problem I face is that I want to distribute the balls randomly, but when I do so, some balls spawn on top of each other. I want to exclude all the positions that are already taken (and maybe a buffer of a few pixels around the balls), but I don't know how to do so. I worked around this by giving the balls a Physicsbody, so they move off from one another if they happen to spawn on top of each other. But I want them to not spawn on top of each other in the first place. My code for now is the following:
spawnedBalls = [Ball]()
level = Int()
func setupLevel() {
let numberOfBallsToGenerate = level * 2
let boundary: CGFloat = 26
let rightBoundary = scene!.size.width - boundary
let topBoundary = scene!.size.height - boundary
while spawnedBalls.count < numberOfBallsToGenerate {
let randomPosition = CGPoint(x: CGFloat.random(in: boundary...rightBoundary), y: CGFloat.random(in: boundary...topBoundary))
let ball = Ball()
ball.position = randomPosition
ball.size = CGSize(width: 32, height: 32)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width)
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = 1
spawnedBalls.append(ball)
self.addChild(ball)
}
}
I don't know if this problem should be solved by having an array that stores all taken positions, or if I should use some kind of FiledNode, where occupied space can be sort of subtracted, but sadly I am unfamiliar with FieldNodes, so I don't know if that's the right way to face the problem.
Step 1) Replace
let randomPosition = ....
with
let randomPosition = randomPositionInOpenSpace()
Step 2) Write the randomPositionInOpenSpace function:
Idea is:
a) generate a random position
b) is it in open space? if so return that
c) repeat until OK
Then Step 3) write the 'is it in open space' function
For that you need to know if the proposed coordinate is near any of the other balls. For circles, you can test the distance between their centers is greater than (radiuses + margins). Distance between centers is pythagoras: sqrt of the x delta squared plus the y delta squared.

Why does SceneKit spotlight angle affect shadow?

I'm trying to use a spotlight in my scene and add shadows to an object. However, I noticed that when I increase the spotInnerAngle, the shadow of the object changes significantly. Here's an example:
Both of shadows in these images look quite different – does anyone know why increasing the spot angle is causing the shadow to be less apparent?
This is the code I'm using to create a spotlight/add shadows to my scene:
let spotLight = SCNNode()
spotLight.light = SCNLight()
spotLight.light?.type = SCNLight.LightType.spot
spotLight.light?.spotInnerAngle = 120
spotLight.light?.spotOuterAngle = 120
spotLight.light?.color = UIColor.white
spotLight.light?.castsShadow = true
spotLight.light?.automaticallyAdjustsShadowProjection = true
spotLight.light?.shadowSampleCount = 32
spotLight.light?.shadowRadius = 8
spotLight.light?.shadowMode = .deferred
spotLight.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
spotLight.light?.shadowColor = UIColor.black.withAlphaComponent(1)
spotLight.position = SCNVector3(x: 0,y: 5,z: 0)
spotLight.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
SceneKit's engine calculates shadows slightly differently than 3D software apps do, like Maya or 3dsMax. In SceneKit framework the position and scale of your Spotlight as well as its value of cone angle are crucial for shadow generating. The main rule is the following: when area of spotlight's ray in SceneKit becomes greater, the shadow edges become more obscure (aka blurry).
Here's a properties you have to take into consideration when using spotlight:
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .spot
lightNode.rotation = SCNVector4(x: 0, y: 0, z: 0, w: 1)
lightNode.castsShadow = true
/* THESE SEVEN SPOTLIGHT PROPERTIES AFFECT SHADOW'S APPEARANCE */
lightNode.position = SCNVector3(0, 10, 0)
lightNode.scale = SCNVector3(7, 7, 7)
lightNode.light?.spotOuterAngle = 120
lightNode.light?.shadowRadius = 10
lightNode.light?.zNear = 0
lightNode.light?.zFar = 1000000
lightNode.light?.shadowSampleCount = 20
lightNode.light?.shadowColor = UIColor(write: 0, alpha: 0.75)
lightNode.light?.shadowMode = .deferred
scene.rootNode.addChildNode(lightNode)
Also, I recommend you use Ambient Light with very low Intensity for lighting up dark areas on your 3D models.
Hope this helps.
From Apple's documentation:
"[spotInnerAngle] determines the width of the fully illuminated area."
The default value of this property is 0, which means only the center of the area illuminated by the spotlight is lit at full intensity.
Increasing the inner angle will increase the area that is lit at full intensity, thus adding more light to the scene. In your case, this additional light was decreasing the visible shadow from that angle.

Swift SceneKit — physical blocks do not stick to each other

Blocks just crumble apart.
How can this problem be solved?
Initializing blocks:
var boxNode = SCNNode(geometry: SCNBox(width: 0.75, height: 0.15, length: 0.25, chamferRadius: 0))
boxNode.position = SCNVector3(x: x1, y: y, z: z1)
boxNode.geometry?.firstMaterial = SCNMaterial()
boxNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "wood.jpg")
boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
boxNode.eulerAngles.y = Float(Double.pi / 2) * rotation
boxNode.physicsBody?.friction = 1
boxNode.physicsBody?.mass = 0.5
boxNode.physicsBody?.angularDamping = 1.0
boxNode.physicsBody?.damping = 1
picture
video
full code
I won't be able to tell you how to fix it as I have the exact same problem which I wasn't able to solve. However, as I played around I figured out a couple of things (which you may find useful):
The same problem hasn't happened to me in pure SceneKit, hence I think it's a bug in ARKit
Node with physics has to be added to the rootNode of the scene, otherwise odd stuff happens (elements passing through each other, gravity behaving in an inconsistent way)
If you pass nil as shape parameter, SceneKit will figure bounding box based on the geometry of the node. This hasn't worked properly for me so what I've done (using SceneKit editor) was to duplicate the geometry and then set it as a custom shape for the bounding box (have a look at the attached image)
Overall I've found physics simulation in SceneKit when used with ARKit to be extremely buggy and I spent a lot of time "tricking" it into working more-or-less how I wanted it to work.

Simple SpriteKit game performance issues - Swift

Apologies in advance as I'm not sure exactly what the right question is. The problems that I'm ultimately trying to address are:
1) Game gets laggy at times
2) CPU % can get high, as much as 50-60% at times, but is also sometimes relatively low (<20%)
3) Device (iPhone 6s) can get slightly warm
I believe what's driving the lagginess is that I'm constantly creating and removing circles in the SKScene. It's pretty much unavoidable because the circles are a critical element to the game and I have to constantly change their size and physicsBody properties so there's not much I can do in terms of reusing nodes. Additionally, I'm moving another node almost constantly.
func addCircle() {
let attributes = getTargetAttributes() //sets size, position, and color of the circle
let target = /*SKShapeNode()*/SKShapeNode(circleOfRadius: attributes.size.width)
let outerPathRect = CGRect(x: 0, y: 0, width: attributes.size.width * 2, height: attributes.size.width * 2)
target.position = attributes.position
target.fillColor = attributes.color
target.strokeColor = attributes.stroke
target.lineWidth = 8 * attributes.size.width / 35
target.physicsBody = SKPhysicsBody(circleOfRadius: attributes.size.width)
addStandardProperties(node: target, name: "circle", z: 5, contactTest: ContactCategory, category: CircleCategory) //Sets physicsBody properties
addChild(target)
}
The getAttributes() function is not too costly. It does have a while loop to set the circle position, but it doesn't usually get used when the function is called. Otherwise, it's simple math.
Some other details:
1) The app runs at a constant 120 fps. I've tried setting the scene/view lower by adding view.preferredFramesPerSecond = 60 in GameScene.swift and gameScene.preferredFramesPerSecond = 60 in GameViewController. Neither one of these does anything to change the fps. Normally when I've had performance issues in other apps, the fps dipped, however, that isn't happening here.
2) I’ve tried switching the SKShapeNode initializer to use a path versus circleOfRadius and then resetting the path. I’ve also tried images, however, because I have to reset the physicsBody, there doesn’t appear to be a performance gain.
3) I tried changing the physicsWorld speed, but this also had little effect.
4) I've also used Instruments to try to identify the issue. There are big chunks of resources being used by SKRenderer, however, I can't find much information on this.
Creating SKShapeNodes are inefficient, try to use it as few times as you can. instead, create a template shape, and convert it to an SKSpriteNode.
If you need to change the size, then use xScale and yScale, if you need to change the color, then use color with colorBlendFactor of 1
If you need to have a varying color stroke, then change the below code to have 2 SKSpriteNodes, 1 SKSpriteNode that handles only the fill, and 1 SKSpriteNode that handles only the stroke. Have the stroke sprite be a child of the fill sprite with a zPosition of 0 and set the stroke color to white. You can then apply the color and colorBlendFactor to the child node of the circle to change the color.
lazy var circle =
{
let target = SKShapeNode(circleOfRadius: 1000)
target.fillColor = .white
//target.strokeColor = .black //if stroke is anything other than black, you may need to do 2 SKSpriteNodes that layer each other
target.lineWidth = 8 * 1000 / 35
let texture = SKView().texture(from:target)
let spr = SKSpriteNode(texture:texture)
spr.physicsBody = SKPhysicsBody(circleOfRadius: 1000)
addStandardProperties(node: spr, name: "circle", z: 5, contactTest:ContactCategory, category: CircleCategory) //Sets physicsBody properties
return spr
}()
func createCircle(of radius:CGFloat,color:UIColor) -> SKSpriteNode
{
let spr = circle.copy()
let scale = radius/1000.0
spr.xScale = scale
spr.yScale = scale
spr.color = color
spr.colorBlendFactor = 1.0
return spr
}
func addCircle() {
let attributes = getTargetAttributes() //sets size, position, and color of the circle
let spr = createCircle(of:attribute.width,color:attributes.color)
spr.position = attributes.position
addChild(str)
}

Smoothing Edges of Unstroked Paths on SKShapeNode

I've been doing some shape drawing using Swift's built in drawing tools to draw simple paths with straight lines. One anomaly I've noticed is that if you want a path that is filled but does not have a line stroke on its edge, it renders without antialiasing and has a rough appearance. In contrast, a line by default always renders smoothly. There is an option to turn antialiasing on but this also makes no difference. I could just make the stroke the same colour as the fill, but this means that the edge expands outwards from the point positions that define the shape.
//creates shape node to contain path
let myTriangle = SKShapeNode()
//declares path object
let myPath = CGPathCreateMutable()
//moves to starting point of shape
CGPathMoveToPoint(myPath, nil, -50, 0)
//draws lines of shape
CGPathAddLineToPoint(myPath, nil, 0, 100 )
CGPathAddLineToPoint(myPath, nil, 50, 0 )
CGPathAddLineToPoint(myPath, nil, -50, 0 )
//closes path
CGPathCloseSubpath(myPath)
myTriangle.antialiased = true
//adds path to shape node
myTriangle.path = myPath
//sets fill colour and zero line width
myTriangle.fillColor = SKColor.blueColor()
myTriangle.lineWidth = 0
//sets antialiasing
myTriangle.antialiased = true
//sets position in parent object and adds node
myTriangle.position = CGPointMake(600, 600)
self.addChild(myTriangle)
Any ideas?
Many thanks,
Kw
About the antialiasing you can take a look here to this document.
I've seen also you have a lineWidth equal to 0, that's not help according with this document..
To improve the appearance of your path you could try also:
yourPath.lineCap = CGLineCap(rawValue: 1)!
where, following the source:
/* Line cap styles. */
public enum CGLineCap : Int32 {
case butt
case round
case square
}
Some code to example written in Swift 3:
let myTriangle = SKShapeNode()
let myPath = CGMutablePath()
myPath.move(to: CGPoint(x:-110,y: 0))
myPath.addLine(to: CGPoint(x:0,y: 290))
myPath.addLine(to: CGPoint(x:260,y: 0))
myPath.addLine(to: CGPoint(x:-110,y: 0))
myPath.closeSubpath()
myTriangle.path = myPath
myTriangle.fillColor = SKColor.white
myTriangle.lineWidth = 2
myTriangle.lineCap = CGLineCap(rawValue: 1)!
myTriangle.strokeColor = SKColor.white
myTriangle.isAntialiased = true
myTriangle.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
self.addChild(myTriangle)
The Output of this code (lineWidth 2 and lineCap valorized..):
Your Output (lineWidth equal to 0):