SpriteKit circle particle effect - ios

In iOS SpriteKit. I am trying to create a sk particle emitter where the particle effect will be a circle with some radius r. Any ideas how to do that? Thanks!

The way I achieved this is by using the Color Ramp, starting with a color with zero opacity, to a color with 1% opacity near the end of the range and then having a third color with 100% opacity for the actual circle.
Here are the parameters I used:
Particles: 800, maximum:0
Lifetime: 1.1, range 0.2 (play with that and the location of the colors on the color ramp to set the radius)
position range: x=5, y=5 (not a must)
speed: 30 (should be around that value)
acceleration: x=10, y=10 (small values are good here)
alpha: start=0.3, range=1, speed: 0.1 (play with these)

One solution could be :
the particles of a SKEmitter have a position
all we have to do is to find where and how to change that position
where : in : override func update(_ currentTime: TimeInterval)
how : by : The parametric equation for a circle :
xx = cx + r * cos(a)
yy = cy + r * sin(a)
cx and cy = origin of the circle
r = radius
a: Int = angle : this variable must be a general variable in range 0...359 and reset to zero when it goes over 359
Do not use a for loop in update function but a = a + 1
myEmitter.particlePosition = CGPoint(x: xx, y: yy)

Easiest way is to use the Particle Emitter Editor. There are a couple of ways to achieve your goal. One of them is to change the Lifetime Start setting.
You can also set these values in code with the particleLifetime and particleLifetimeRange commands.

Related

How to calculate distance from the centering point between two eyes to the camera using ARCore + SceneKit?

I'm using ARCore + SceneKit (Swift language) to calculate the distance from the centering point between two eyes to the camera.
I determine the coordinates of the camera:
let cameraPos = sceneView.pointOfView?.position
The coordinates of the left eye and right eye:
let buffer = face.mesh.vertices
let left = buffer[LF]
let right = buffer[RT]
NOTE:
LF and RT is defined base on: https://github.com/ManuelTS/augmentedFaceMeshIndices
LF = 159 is the index that contain the Vector3 condinate of the Left eye
RT = 386 is the index that contain the Vector3 condinate of the Right eye
Compute the centering point (in SCNVector3):
let center = SCNVector3(x: (left.x - right.x) * 0.5,
y: (left.y - right.y) * 0.5,
z: (left.z - right.z) * 0.5)
Finally, I calculate the distance:
let distance = distance(start: cameraPos!, end: center)
distance is defined as:
func distance(start: SCNVector3, end: SCNVector3) -> Float {
let dx = start.x - end.x
let dy = start.y - end.y
let dz = start.z - end.z
let distance = sqrt(dx * dx + dy * dy + dz * dz)
return round(distance * 100 * 10) / 10.0
}
Runtime result is incorrect.
Actual distance: ~20 cm
In-app distance: ~3 cm
Can someone tell me where the problem lies, even another solution?
Thanks.
Assuming center is the midpoint between the eyes, then shouldn't the formula be:
Midpoint:
(x1, y1, z1) and (x2, y2, z2) is (x1+x2 )/2,(y1+y2 )/2,(z1+z2 )/2.
Edit: Taking a guess here, but...
Example: So that a projectile will actually launch from a turret with a long barrel cannon exactly where the barrel is rotated to at the time of firing, you have to calculate that position at the end of the tube as it relates to the position of the node that the barrel is attached to, otherwise the shot will not look like it came from the right spot.
Requires a little imagination, but this is your face moving around = turret is moving around. I "think" that's what's happening to your math. I don't think you are getting the right LF/RF positions because you didn't mention converting the point. The link you sent [The face mesh consists of hundreds of vertices that make up the face, and is defined relative to the center pose.] Relative to the center pose - I'm pretty sure that means you have to convert LF with relation to the center to get the real position.
// Convert position something like this:
let REAL_LF = gNodes.gameNodes.convertPosition(LF.presentation.position, from: POSE_POSITION)
convertPosition(_:to:)
Converts a position from the node’s local coordinate space to that of another node

Using CATransform3DRotate with perspective: how to correct the 2D size increase?

I'm trying to create a paper folding effect in Swift using CALayers and CATransform3DRotate. There are some libraries out there, but those are pretty outdated and don't fit my needs (they don't have symmetric folds, for example).
My content view controller will squeeze to the right half side of the screen, revealing the menu at the left side.
Everything went well, until I applied perspective: then the dimensions I calculate are not correct anymore.
To explain the problem, I created a demo to show you what I'm doing.
This the content view controller with three squares. I will use three folds, so each square will be on a separate fold.
The even folds will get anchor point (0, 0.5) and the odd folds will get anchor point (1, 0.5), plus they'll receive a shadow.
When fully folded, the content view will be half of the screen's width.
On an iPhone 7, each fold/plane will be 125 points unfolded and 62.5 points fully folded when looked at.
To calculate the rotation needed to achieve this 62.5 points width, we can use a trigonometric function. To illustrate, look at this top-down view:
We know the original plane size (125) and the 2D width (62.5), so we can calculate the angle α using arccos:
let angle = acos(width / originalWidth)
The result is 1.04719755 rad or 60 degrees.
When using this formula with CATransform3DRotate, I get the correct result:
Now for the problem: when I add perspective, my calculation isn't correct anymore. The planes are bigger. Probably because of the now different projection.
You can see the planes are now overlapping and being clipped.
I reconstructed the desired result on the right by playing with the angle, but the correction needed is not consistent, unfortunately.
Here's the code I use. It works perfectly without perspective.
// Loop layers
for i in 0..<self.layers.count {
// Get layer
let layer = self.layers[i]
// Get dimensions
let width = self.frame.size.width / CGFloat(self.numberOfFolds)
let originalWidth = self.sourceView.frame.size.width / CGFloat(self.numberOfFolds)
// Calculate angle
let angle = acos(width / originalWidth)
// Set transform
layer.transform = CATransform3DIdentity
layer.transform.m34 = 1.0 / -500
layer.transform = CATransform3DRotate(layer.transform, angle * (i % 2 == 0 ? -1 : 1), 0, 1, 0)
// Update position
if i % 2 == 0 {
layer.position = CGPoint(x: (width * CGFloat(i)), y: layer.position.y)
} else {
layer.position = CGPoint(x: (width * CGFloat(i + 1)), y: layer.position.y)
}
}
So my question is: how do I achieve the desired result? Do I need to correct the angle, or should I calculate the projected/2D width differently?
Thanks in advance! :)

Move view in direction of specific angle

I have an angle that I am calculating based on the positioning of a view from the centre of the screen. I need a way to move the view from it's current position, off the screen in the direction of the angle.
I'm sure there is a fairly simple way of calculating a new x and y value, but I haven't been able to figure out the maths. I want to do it using an animation, but I can figure that out myself once I have the coordinates.
Anyone have any suggestions?
If you have angle you can calculate new coordinates by getting sine and cosine values. You can try out following code
let pathLength = 50 as Double // total distance view should move
let piFactor = M_PI / 180
let angle = 90 as Double // direction in which you need to move it
let xCoord = outView.frame.origin.x + CGFloat(pathLength * sin(piFactor*angle)) //outView is name of view you want to animate
let yCoord = outView.frame.origin.y + CGFloat(pathLength * cos(piFactor*angle))
UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
self.outView.frame = CGRectMake(xCoord, yCoord, self.outView.frame.size.width, self.outView.frame.size.height)
}, completion: { (Bool) -> Void in
})
To me it sounds what you need to do is convert a vector from polar representation (angle and radius) to cartesian representation (x and y coordinates) which should be fairly easy.
You already got the angle so you only need to get the radius, which is the length of the vector. In you case (if I understand it correctly) is the distance from the current center of the view that needs to be animated to it's new position. While it may be complex to know that exactly (cause this part of what you are trying to calculate) you can go on the safe side and take a large enough value that will surely throw the view out of its super view frame. The length of the superview diagonal plus the length of the animated view diagonal should do the work, or even more simple just take the sum of the height and width of both views.
Once you have the complete polar representation of the vector (angle and radius) you can use that simple formula to convert to cartesian representation (x = r * cos(a), y = r * sin(a)) and finally add that vector coordinates to the center of the view you need to animate.

How to make a sprite jump to a specific height with SpriteKit?

I am trying to use applyImpulse to make a sprite jump to a specific height. In the example code below, the dynamic black circle jumps to the same height as the static red circle, but it only works with a kludge.
If my physics is right, the required initial vertical momentum to launch a projectile to height Y is given by mass * sqrt(2 * gravity * Y). Yet this formula results in the black circle moving very little.
Through trial and error, I have discovered that I can make the red circle jump more or less accurately by multiplying the vertical component of the impulse vector by 12.3, as illustrated in the code below.
This seems completely arbitrary and is driving me crazy. I am obviously doing something wrong. What's the right way to do this?
Here's what I think is the relevant bit of code:
let dy = mass * sqrt(
2 * -self.physicsWorld.gravity.dy
* (fixedCircle.position.y-jumpingCircle.position.y))
* 12.3
jumpingCircle.physicsBody?.applyImpulse(CGVectorMake(0, CGFloat(dy)))
Here's the GameScene.swift class in its entirety, should you wish to copy and paste...
import SpriteKit
class GameScene: SKScene {
var jumpingCircle = SKShapeNode()
var fixedCircle = SKShapeNode()
override func didMoveToView(view: SKView) {
self.scaleMode = .AspectFit
// Create an exterior physical boundary
self.physicsBody = SKPhysicsBody(edgeLoopFromRect:self.frame)
self.physicsWorld.gravity = CGVectorMake(0, -5);
// Create the jumping circle
jumpingCircle = SKShapeNode(circleOfRadius: 50)
jumpingCircle.position = CGPoint(x: 100,y: 0)
jumpingCircle.strokeColor = .blackColor()
jumpingCircle.physicsBody = SKPhysicsBody(circleOfRadius: 50)
jumpingCircle.physicsBody?.linearDamping = 0.0
self.addChild(jumpingCircle)
// Create the fixed circle
fixedCircle = SKShapeNode(circleOfRadius: 50)
fixedCircle.position = CGPoint(x: 100,y: 384)
fixedCircle.strokeColor = .redColor()
self.addChild(fixedCircle)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
// to reach certain height, initial y velocity should by sqrt(2 * gravity * Y)
// momentum required to reach this velocity is mass * velocity
// therefore, vertical impulse should be given by mass * sqrt(2 * gravity * Y)
if let mass = jumpingCircle.physicsBody?.mass{
// calculate vertical impulse
// this formula "should work" but does not
// let dy = mass * sqrt(2 * -self.physicsWorld.gravity.dy * (fixedCircle.position.y-jumpingCircle.position.y))
// this formula "works", but has the arbitrary multiplier
let dy = mass * sqrt(2 * -self.physicsWorld.gravity.dy * (fixedCircle.position.y-jumpingCircle.position.y)) * 12.3
// apply the Impulse
jumpingCircle.physicsBody?.applyImpulse(CGVectorMake(0, CGFloat(dy)))
}
}
}
Figured it out.
SpriteKit apparently has an undocumented pixel-to-"meter" ratio of 150.
I discovered this when I realized the mass SpriteKit was automatically calculating for my circles was not 50*pi*r^2 as it should be. I worked backward from mass to calculate the radius SpriteKit was using, and it was 7500, which happens to be 50*150.
And 12.3? It just happens to be (approximately) the square root of 150.
So to make these physics simulations work, you have to consider this ratio. I'm calling it "pixel to unit" (PTU) because it has nothing to do with meters in spite of Apple's insistence that SpriteKit uses SI units. But because it's undocumented it seems possible to change, so I'm kicking off my simulation using the following line of code to determine the true PTU:
let ptu = 1.0 / sqrt(SKPhysicsBody(rectangleOfSize: CGSize(width:1,height:1)).mass)
This is an expensive operation, so it should only be called once. I'm calling it when setting up the initial Scene.
My impulse calculation is now as follows:
let dy = mass * sqrt(2
* -self.physicsWorld.gravity.dy
* (fixedCircle.position.y-jumpingCircle.position.y
* ptu))
And now the black circle jumps to perfect alignment with the red circle and I can go back to sleeping at night.
Instead of applyImpulse use velocity. Velocity will be more specific.
jumpingCircle.physicsBody?.velocity = CGVectorMake(jumpingCircle.physicsBody.velocity.dx, 300.0f);
Code not tested.

continuos smooth incremental rotation of a node (camera)

I'm trying to incrementally rotate the camera around x-axis by 5 degrees. it works fine except for animation at 355 jumps suddenly. it happens due to animation chaining. if my following method is called in SCNSceneRendererDelegate then it is not time based rotation. the SCNSceneRendererDelegate is triggered in each frame. and it means my scene animation action with duration is not ready yet. by lowering the animation duration, the animation is not smooth anymore. doing a timer based with same interval as the animation duration looks bad as well. is there anyway to get this animation smooth?
-(void) updateCameraRotation
{
SCNQuaternion oldRotScnQuat = _cameraNode.presentationNode.rotation;
GLKQuaternion glQuatOldRot = GLKQuaternionMakeWithAngleAndAxis(oldRotScnQuat.w, oldRotScnQuat.x, oldRotScnQuat.y, oldRotScnQuat.z);
float xan = GLKMathDegreesToRadians(5);
GLKQuaternion newx = GLKQuaternionIdentity;
GLKVector3 vec = GLKVector3Normalize(GLKVector3Make(1, 0, 0));
double result = sinf(xan/2);
newx = GLKQuaternionMakeWithAngleAndAxis(cosf(xan/2), vec.x *result, vec.y * result, vec.z * result);
newx = GLKQuaternionNormalize(newx);
glQuatOldRot = GLKQuaternionMultiply(glQuatOldRot, newx);
axis = GLKQuaternionAxis(glQuatOldRot);
angle = GLKQuaternionAngle(glQuatOldRot);
[_cameraNode runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(axis.x, axis.y, axis.z, angle) duration:1]];
}
SLERP is used to make smooth rotations between two known attitudes. Try and avoid needless conversions between axis-angle and quaternions (leave them in quaternions if possible).
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
You will also need to calculate the angle between the quaternion axes: https://math.stackexchange.com/questions/90081/quaternion-distance
I have used both these functions myself and they work well.
Rather than adding 5 degrees per frame, you can specify a scalar t in the range of [0, 1].
For instance, when rotating from quaternion P to Q, R = SLERP(P,Q,t) will give you the quaternion R from P to Q. If t = 0 then R = P, if t = 1 then R = Q.
Try to provide a CABasicAnimation with byValue set to your 5 degrees and set repititions to be infinite. Won't that work?

Resources