How to determine which childNode is closest to screen center in SceneKit? - arrays

I have a bunch of markers placed in my scene as childNodes at fixed node positions in 3D world. When I move the phone around, I need to determine which marker node is the closest to the 2D screen center, so I can get the text description corresponding to that node and display it.
Right now, in a renderloop, I just determined the distance of each node from the screen center in a forEach loop, and decide if that distance is <150, if so get title and copy of that node. However, this doesn't solve my problem because there could be multiple nodes that satisfy that condition. I need to compare the distances from the center across all the nodes and get that one node that's is closest
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval){
scene.rootNode.childNodes.filter{ $0.name != nil }.forEach{ node in
guard let pointOfView = sceneView.pointOfView else { return }
let isVisible = sceneView.isNode(node, insideFrustumOf: pointOfView)
if isVisible {
let nodePos = sceneView.projectPoint(node.position)
let nodeScreenPos = CGPoint(x: CGFloat(nodePos.x), y: CGFloat(nodePos.y))
let distance = CGPointDistance(from: nodeScreenPos, to: view.center)
if distance < 150.0 {
print("display description of: \(node.name!)")
guard let title = ar360Experience?.threeSixtyHotspot?[Int(node.name!)!].title else { return }
guard let copy = ar360Experience?.threeSixtyHotspot?[Int(node.name!)!].copy else { return }
titleLabel.text = title
copyLabel.text = copy
cardView.isHidden = false
}else {
cardView.isHidden = true
}
}
}
}

There are various ways to do this. Like iterating all the nodes to find their distance. But like you said this becomes inefficient.
What you can do is to store your node data in a different format using something like GKQuadTree... https://developer.apple.com/documentation/gameplaykit/gkquadtree
This is a GameplayKit That will allow you to much more quickly iterate the data set so that you can find the node closest to the centre.
It works by breaking down the area into four (hence quad) sections and storing nodes into one of those sections storing the rect of the section too. It then breaks down each of those four sections into four more and so on.
So when you ask for the nodes closest to a given point it can quickly eliminate the majority of the nodes.

Related

Is there a way to prevent node glitching shaking during collisions?

The function (code below) simply moves a character node (contact.nodeA) back after it has penetrated a collision node (contact.nodeB) that it's not allowed to pass through.
In a scenario where a user is trying to move the character node (contact.nodeA) while it's stuck in a corner, etc. the scene/character repeatedly shakes/glitches while its position is repeatedly being moved back away from contact.nodeB at 60hz.
Is it possible with SCNPhysicsContact in SceneKit to prevent contact.nodeA from moving forward into contact.nodeB in the first place? (ie., why does "contact.penetrationDistance" occur at all? Why can't we just be alerted when a physics contact has begun w/o contact.nodeA actually moving forward into contact.nodeB? We are, after all, catching the contact on "didBegin" yet there's already penetration that we have to readjust.)
(When a user is trying to move the character node while it's "stuck" in a corner, or between two rocks, etc.)
Or, is there another solution to prevent the repeated shaking of contact.nodeA as its position is repeatedly being readjusted to compensate for its penetration into contact.nodeB? Transform instead of position change?
I've already tried setting momentum to 0.0 and removing all animations, etc. However, since contact is still occurring with contact.nodeB, contact.nodeA cannot be moved away since its momentum is still being stopped during contact with nodeB.
Any help would be greatly appreciated.
private func characterNode(_ characterNode: SCNNode, hitWall wall: SCNNode, withContact contact: SCNPhysicsContact) {
if characterNode.name != "collider" { return }
if maxPenetrationDistance > contact.penetrationDistance { return }
var characterPosition = float3(characterNode.parent!.position)
var positionOffset = float3(contact.contactNormal) * Float(contact.penetrationDistance)
maxPenetrationDistance = contact.penetrationDistance
characterPosition.y += positionOffset.y
replacementPositions[characterNode.parent!] = SCNVector3(characterPosition)
}
extension GameViewController : SCNPhysicsContactDelegate {
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if gameState != .playing { return }
contact.match(BitmaskWall) {
(matching, other) in
if other.name == "collider" {
self.characterNode(other, hitWall: matching, withContact: contact)
}
}
}

Scenekit - SCNLookupConstraint causes ConvertPosition of childNode not to update its position

I have a main node, with a sequence of childNodes. The childNodes are firePoints, so as the target comes into view, the main node rotates to target and the firepoint is an offset where I need to shoot from. It works fine if I target via some vector classes I built, but it is smoother if I use SCNConstraint on the main node. The main node (and firepoints) rotate to target, but the fire points vector values do not ever change when convertPosition is called. I can see that the fireNodes are rotating along with the base node properly. Thanks
func shoot()
{
isShooting = true
// Convert position so that projectile fires from FirePoint
let fireNode = gNodes.getNode(vName: attr.name + "FirePoint" + "\(attr.firePointsSequence)", vRequired: true, vError: "FP0-Sproj")
let fireNodeStart = gNodes.gameNodes.convertPosition(fireNode.presentation.position, from: attr.node)
print("FireNodePosition: \(fireNodeStart)
}
func setTarget()
{
attr.node.constraints = []
let vConstraint = SCNLookAtConstraint(target: targetNode)
vConstraint.isGimbalLockEnabled = true
attr.node.constraints = [vConstraint]
}
I had this problem. What solved it for me was using SCN's presentation scn. It has up to date positions.

nested for-loops on an array

I'm using SpriteKit and Swift 3 to create a simple game.
I have an array of Rings/Circles:
mRings = [mRingOne, mRingTwo, mRingThree, mRingFour, mRingFive]
each object in the array has a different color, In some point in the game I want to change the color of each ring, but I have 2 condition for this to happen:
1. a ring should not have the color it had one iteration before.
2. each ring should be in a different color from the others.
for the first condition I did this:
func changeRingsColor(){
var previousColor: UIColor?
for ring in mRings {
previousColor = ring.fillColor
repeat{
ring.fillColor = hexStringToUIColor(hex: mColors[Int(arc4random_uniform(UInt32(5)))])
}while(ring.fillColor.isEqual(previousColor))
}
}
and it is working, however, I couldn't figure out a way to answer the second condition.
In Java I would probably do something like this:
for (int i=0; i<mRings.length; i++){
for( int j=1; j<mRings.length; j++){
if (ring[i].fillColor == ring[j].fillColor){
generate another color for 'j' ring.
}
}
}
but nothing I tried worked.
Hope you guys can help me, Thanks in advance!
btw, mColors is an array of 5 different colors, from there I pick the colors.
I'm going to ignore some of the implementation details and focus on the core of the question, which is:
Loop through an array
Within each loop, start a new loop at the current index to the end of the array.
Let me know if I'm misunderstanding the above. But I would do it like this:
for (index, ring) in mRings.enumerated() {
let remainingRings = mRings[index+1..<mRings.count]
for otherRing in remainingRings {
print("Now comparing \(ring) to \(otherRing)")
}
}
First, enumerated() gives you both the current ring, and the index, on each iteration of the first for loop. Then, we slice the array from the current index to the end (remainingRings), and loop through those.
It is possible to rewrite Java code in this way :
let mRings = ["mRingOne", "mRingTwo", "mRingThree", "mRingFour", "mRingFive"]
for (index, item) in mRings.enumerated() {
for item in index + 1..<mRings.count {
}
}
Something like this will work:
func changeRingsColor(){
// extract the previous colors
let colors = rings.map { $0.fillColor }
// the order in which the rings will pass color to each other
let passOrder = mRings.indices.shuffled()
for i in mRings.indices {
let previous = i == 0 ? mRings.count - 1 : i - 1
mRings[passOrder[i]].fillColor = colors[passOrder[previous]]
}
}
Using this implementation of shuffled
How does it work?
Instead of randomly choosing a color for each individual ring, I am randomly generating an order in which the rings will swap the colors (passOrder). Since every ring will pass its color to a different ring in that order, you can be sure no color will stay the same.

Swift, Generating Array Variables of a Numbered List

I'm making a simple game in swift and xcode and I ran into this problem that I can't figure out. Because I have so many levels, the code locks up indexing and slows down the whole program. I get a color wheel spinning for a few minutes but it never crashes. Just takes several minutes everytime I type in a few characters. Strange, but xcode has always had it's bugs right?
Each Button ("button1, button2..." below) gets a single number from "level1:Array". It's like a code that fills in the button's value for the game. There are only 4 buttons, but the numbers should be able to change as they each have their own variables.
I want to generate this for every level. I should be able to generate something like "button1 = level#[0]" where # is replaced by "userLevel". Changing to a string and doing something like "button1 = ("level(userLevel)") as! Array... doesn't seem to work. Look below and use my terminology when giving examples if you can. Thanks!
Here's the example:
let level1:Array = [9,7,4,1] // current puzzle, to go directly into button vars below (button1,button2,ect)
var userLevel = 1 // current user's level
if userLevel == 1 {
print("making Level 1, setting buttons to value from array")
button1 = level1[0]
button2 = level1[1]
button3 = level1[2]
button4 = level1[3]
}
Now, since level# is repeated so often (for each level as the number progresses) I would rather just make it something like this:
//this doesn't work in swift, but what else can I do?
if userLevel > 0 {
button1 = level\(userLevel)[0]
button2 = level\(userLevel)[1]
button3 = level\(userLevel)[2]
button4 = level\(userLevel)[3]
}
Is there an easy way to do this? Thanks!
-GG
Try using a for-in loop. Create an array of the buttons, and then
var index = 0
for button in buttons {
button = level1[index]
index++
}
EDIT since you want both the user level and the level number to increase, I suggest you define the levels like this. (Make sure that the number of buttons is equal to the number of userLevels, otherwise you will have problems)
var array = [1,2,3]
let levels = [1:[1,3,8],2:[3,6,4],3:[4,2,5]]
var index = 0
if array.count == levels.count {
for number in array {
array[index] = levels[index+1]![index]//The second index can be 0 if you want
index++
}
}
//array = [1,6,5]
// You could create a second index to match the number of levels within the main user level.
In this case, assume array to be your array of buttons
EDIT 2 :)
I've made a function that will allow you to assign all the levels to your array for a specific userLevel, since I see that is what you want
let levels = [1:[1,3,8],2:[3,6,4],3:[4,2,5]]
func assignValuesToArray(levelNo:Int) -> [Int] {
var array: [Int] = []
if (levelNo > 0) && (levelNo <= levels.count) {
for (level,levelArray) in levels {
if level == levelNo {
for value in levelArray {
array.append(value)
}
}
}
return array
} else {
print("This Level number does not exist")
return []
}
}
var finalArray = assignValuesToArray(2)
print(finalArray) // Returns [3,6,4]
As you can see from this example, you will return your array of buttons from the function, and you can assign the returned array values to whatever you like.

Picking a random sprite node from an array in Sprite Kit

I am trying to create an app in which one of a few objects is randomly placed on the screen wherever you touch. I have my objects a, b, and c and I have them in an array.
(NSArray *) gamePieces {
NSArray *things = [NSArray arrayWithObjects: #"a", #"b", #"c", nil];
return things;
}
And then my touch method.
(void)touchesBegan:(NSArray *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// code that chooses random object from array
}
}
Normally I would put in an SKSpriteNode and it would make just that one appear on the screen, but I am looking to randomly select one of many. I'm new to programming and I'm not sure if I'm on the right path. If I am, what needs to go in the touch method? If not, what am I doing wrong?
You can get a random item by getting a random index based on the number of elements in the array :
int randomIndex = arc4Random() % things.count;
NSString *thing = things[randomIndex];
// do something with thing

Resources