I'm using Metal and I want the user to be able to tap certain objects onscreen. I'm using a tap gesture recognizer, and so I have a CGPoint, but I don't know how to find the object at that point. Is there either a way to get the onscreen coordinates for metal, or a way to transform the CGPoint into the metal coordinate space?
I ended up doing this with a raytracing-type technique. I used a bounding sphere for each object and calculated whether or not the ray intersected it and how far from the ray's origin the intersection was, with the ray being from the camera through the onscreen point of clicking. The code is as follows:
var location = gestureRecognizer.locationInView(self.view)
var proj_matrix = projectionMatrix.copy()
var view_matrix = worldMatrix.copy()
var x = (2.0 * location.x) / self.view.bounds.size.width - 1.0;
var y = 1.0 - (2.0 * location.y) / self.view.bounds.size.height;
var newLocation = simpleVertex(x: Float(x), y: Float(y), z: Float(-1.0))
var projSpaceLoc = GLKMatrix4MultiplyVector4(GLKMatrix4Invert(proj_matrix.matrix(), nil), GLKVector4Make(newLocation.x, newLocation.y, newLocation.z, 1.0))
projSpaceLoc = GLKVector4Make(projSpaceLoc.x, projSpaceLoc.y, -1.0, 0.0)
var viewSpaceLoc = GLKMatrix4MultiplyVector4(GLKMatrix4Invert(view_matrix.matrix(), nil), projSpaceLoc)
GLKVector4Normalize(viewSpaceLoc)
//tappable is an array of nodes that can be tapped
for node in tappable {
var r: Float = node.r //this is the radius of the bounding sphere for that node
var c: GLKVector4 = node.origin //this is the origin of the bounding sphere
var little_c = GLKVector4DotProduct(c, c) - powf(r, 2)
var b = GLKVector4DotProduct(viewSpaceLoc, c)
var discriminant = powf(b, 2) - little_c
//If the discriminant is positive, their are two points of intersection, if it is 0, one point, and if it is negative, no points.
if (discriminant >= 0) {
var t_1 = (Float(-1.0)*b) + sqrtf(discriminant)
var t_2 = (Float(-1.0)*b) - sqrtf(discriminant)
if (intersect != nil) {
if (min(t_1, t_2) < intersect.intersect_point) {
intersect = (node, min(t_1, t_2))
}
} else {
intersect = (node, min(t_1, t_2))
}
}
}
Related
I'd like to know whether I can create a rectangle using a top-left and bottom-right CLLocationCoordinate2D, and afterwards check whether a coordinate is part of this rectangle by checking (topLeftCoordinate.latitude < coordinate.latitude && bottomRightCoordinate.latitude > coordinate.latitude) && (topLeftCoordinate.longitude < coordinate.longitude && bottomRightCoordinate.longitude > coordinate.longitude).
I thought a coordinate would represent a point on a sphere, and then this wouldn't work. But I get confused due to the 2D at the end of CLLocationCoordinate2D. Can someone clarify this?
Thanks :)
You will be able to create a MKPolygon from your coordinates and use this function to determine whether a coordinate is inside that polygon:
func isPoint(point: MKMapPoint, insidePolygon poly: MKPolygon) -> Bool {
let polygonVerticies = poly.points()
var isInsidePolygon = false
for i in 0..<poly.pointCount {
let vertex = polygonVerticies[i]
let nextVertex = polygonVerticies[(i + 1) % poly.pointCount]
// The vertices of the edge we are checking.
let xp0 = vertex.x
let yp0 = vertex.y
let xp1 = nextVertex.x
let yp1 = nextVertex.y
if ((yp0 <= point.y) && (yp1 > point.y) || (yp1 <= point.y) && (yp0 > point.y))
{
// If so, get the point where it crosses that line. This is a simple solution
// to a linear equation. Note that we can't get a division by zero here -
// if yp1 == yp0 then the above if be false.
let cross = (xp1 - xp0) * (point.y - yp0) / (yp1 - yp0) + xp0
// Finally check if it crosses to the left of our test point. You could equally
// do right and it should give the same result.
if cross < point.x {
isInsidePolygon = !isInsidePolygon
}
}
}
return isInsidePolygon
}
I want to rotate camera up or down and left or right to look around an object, (360 degrees view) with pan gesture using opengl lookat function, I use swift and Metal(in this case Metal = opengl es). here is the code:
the lookat function(this function is in another ViewController which is inheritance from the main ViewController which is with the viewDidLoad and pan gesture function):
let viewMatrix = lookAt(location, center: ktarget, up: up)
the viewDidLoad and var:
var ktarget = V3f()
var up = V3f()
let location =V3f()
override func viewDidLoad() {
super.viewDidLoad()
location = V3f(0.0, 0.0, -2.0)
ktarget = V3f(0.0, 0.0, 0.0)
up = V3f(0.0, 1.0, 0.0)
}
the pan gesture:
func pan(panGesture: UIPanGestureRecognizer){
up = V3f(0.0, 1.0, 0.0).Normalized()
forward = (ktarget + -location).Normalized()
right = Cross(forward, b: up).Normalized()
if panGesture.state == UIGestureRecognizerState.Changed{
let pointInView = panGesture.locationInView(self.view)
let xDelta = (lastPanLocation.x - pointInView.x)/self.view.bounds.width * panSensivity
let yDelta = (lastPanLocation.y - pointInView.y)/self.view.bounds.height * panSensivity
// To rotate left or right, rotate the forward around up and then use a cross product between forward and up to get the right vector.
forward = rotationM3f(up, angle: Float(xDelta)) * forward.Normalized()
right = Cross(forward, b: up).Normalized()
// To rotate up or down, rotate forward vector around right vector and use a cross product between the right and forward to get the new up vector.
forward = rotationM3f(right, angle: Float(yDelta)) * forward.Normalized()
up = V3f(0.0, 1.0, 0.0).Normalized()
ktarget = location + forward
lastPanLocation = pointInView
} else if panGesture.state == UIGestureRecognizerState.Began {
ktarget = location + forward
lastPanLocation = panGesture.locationInView(self.view)
}
}
But pan the camera, I set up the up vector always =(0,1,0), to make people only can see 180 degree vertically. If user still pan When Camera look up or down(max value)the screen will flip, the real value x and z of target is changed little by little(very small numebr, such as 0.0000somenumber) every frames, so the draw function will draw the object very little difference every frames. Anyone knows how can fix the flip? Thanks~~~
I'm trying to write a code which let my users define some points on the map and once they created a point, the program should draw a circle with defined diameter(in kilometers or ... ) around the point.
I can draw a point but I don't know how I could handle what I said.
Here is an example about what I want:
Use the following function to create circular points around a point.
//pass the
//#pointX,
//#pointY,
//#radius of circle (in your case diameter/2)
//#pointsToFind this is how detail you want the circle to be (360 if you want one point for each rad)
function createCirclePointCoords(circleCenterX,circleCenterY,circleRadius,pointsToFind){
var angleToAdd = 360/pointsToFind;
var coords = [];
var angle = 0;
for (var i=0;i<pointsToFind;i++){
angle = angle+angleToAdd;
console.log(angle);
var coordX = circleCenterX + circleRadius * Math.cos(angle*Math.PI/180);
var coordY = circleCenterY + circleRadius * Math.sin(angle*Math.PI/180);
coords.push([coordX,coordY]);
}
return coords;
}
And then create a polygon out of the coordinates returned from the function
var circleCoords = createCirclePointCoords(0,0,1000,360);
var geom = new ol.geom.Polygon([
circleCoords
])
I am trying to update the current rotation (and sometimes the position) of a CALayer.
What I am trying to in a couple of simple steps:
Store a couple of CALayers in an array, so I can reuse them
Set the anchor point of all CALayers to 0,0.
Draw CALayer objects where the object starts at a position on a circle
The layers are rotated by the same angle as the circle at that position
Update the position and rotation of the CALayer to match new values
Here is a piece of code I have:
lineWidth is the width of a line
self.items is an array containing the CALayer objects
func updateLines() {
var space = 2 * M_PI * Double(circleRadius);
var spaceAvailable = space / (lineWidth)
var visibleItems = [Int]();
var startIndex = items.count - Int(spaceAvailable);
if (startIndex < 0) {
startIndex = 0;
}
for (var i = startIndex; i < self.items.count; i++) {
visibleItems.append(self.items[i]);
}
var circleCenter = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
/* Each line should move up and rotate accordin to this value */
var anglePerLine: CGFloat = (360 / CGFloat(visibleItems.count)).toRadians()
/* Starting position, 270 degrees is on top */
var startAngle: CGFloat = CGFloat(270).toRadians();
/* Lines default rotation, we rotate it to get the right side up */
var lineAngle: CGFloat = CGFloat(180).toRadians();
for (var itemIndex = 0; itemIndex < visibleItems.count; itemIndex++) {
var itemLayer = self.itemLayers[itemIndex];
itemLayer.opacity = 1 - ((0.9 / visibleItems.count) * itemIndex);
/* Calculate start position of layer */
var x = CGFloat(circleRadius) * cos(startAngle) + CGFloat(circleCenter.x);
var y = CGFloat(circleRadius) * sin(startAngle) + CGFloat(circleCenter.y);
var height = CGFloat((arc4random() % 80) + 10);
/* Set position and frame of layer */
itemLayer.frame = CGRectMake(CGFloat(x), CGFloat(y), CGFloat(lineWidth), height);
itemLayer.position = CGPointMake(CGFloat(x), CGFloat(y));
var currentRotation = CGFloat((itemLayer.valueForKeyPath("transform.rotation.z") as NSNumber).floatValue);
var newRotation = lineAngle - currentRotation;
var rotationTransform = CATransform3DRotate(itemLayer.transform, CGFloat(newRotation), 0, 0, 1);
itemLayer.transform = rotationTransform;
lineAngle += anglePerLine;
startAngle += anglePerLine;
}
}
The result of the first run is exactly as I want it to be:
The second run through this code just doesn't update the CALayers correctly and it starts to look like this:
I think it has to do with my code to update the location and transform properties of the CALayer, but whatever I do, it always results in the last picture.
Answered via Twitter: setting frames and transform is mutually exclusive. Happy to help. Finding my login credentials for SO is harder. :D
Found the answer thanks to #iosengineer on Twitter. When setting a position on the CALayer, you do not want to update the frame of the layer, but you want to update the bounds.
Smooth animation FTW
I am building my first iPhone game using Xcode, SpriteKit and Swift. I am new to these technologies but I am familiar with general programming concepts.
Here is what I am trying to do in English. I want circles to randomly appear on the screen and then begin to expand in size. However, I do not want a circle to appear in a location where a circle currently exists. I am having trouble determining each circle's position.
Inside GameScene.swift I have the following code inside the didMoveToView:
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addCircle), SKAction.waitForDuration(3, withRange: 2)]
)))
The piece of code above calls my "addCircle" method:
func addCircle() {
// Create sprite.
let circle = SKSpriteNode(imageNamed: "grad640x640_circle")
circle.name = "circle"
circle.xScale = 0.1
circle.yScale = 0.1
// Determine where to position the circle.
let posX = random(min: 50, max: 270)
let posY = random(min: 50, max: 518)
// ***Check to see if position is currently occupied by another circle here.
circle.position = CGPoint(x: posX, y: posY)
// Add circle to the scene.
addChild(circle)
// Expand the circle.
let expand = SKAction.scaleBy(2, duration: 0.5)
circle.runAction(expand)
}
The random function above just chooses a random number within the given range. How can I check to see if my random functions are generating a location that is currently occupied by another circle?
I was thinking of using a do..while loop to randomly generate a set of x and y coordinates and then check to see if a circle is at that location but I cannot find how to check for that condition. Any help would be greatly appreciated.
There are a few methods which can help you in this regard:
(BOOL) intersectsNode: (SKNode*)node, available with SKNode, and
CGRectContainsPoint() as well as the CGRectContainsRect() methods.
For instance the loop to check for intersection can look as follows:
var point: CGPoint
var exit:Bool = false
while (!exit) {
let posX = random(min: 50, max: 270)
let posY = random(min: 50, max: 518)
point = CGPoint(x: posX, y: posY)
var pointFound: Bool = true
self.enumerateChildNodesWithName("circle", usingBlock: {
node, stop in
let sprite:SKSpriteNode = node as SKSpriteNode
if (CGRectContainsPoint(sprite.frame, point))
{
pointFound = false
stop.memory = true
}
})
if (pointFound)
{
exit = true
}
}
//point contains CGPoint where no other circle exists
//Declare new circle at point