Changing rotation of a UIBezierPath in SpriteKit without changing orientation - xcode

I'm making a game. I want 2 separate lines to rotate clockwise and when the player touches the screen the rotation should change to counterclockwise. Right now, when it changes to counterclockwise, the UIBezierPath flips the node. I don't want the node to be flipped, just rotate the other way.
func clockwise() {
let dx = leftside.position.x - self.frame.width / 2
let dy = leftside.position.y - self.frame.height / 2
let rad = atan2(dy, dx)
let path = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 85, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(path.CGPath, asOffset: false, orientToPath: true, speed: 250)
leftside.runAction(SKAction.repeatActionForever(follow).reversedAction())
let dx1 = rightside.position.x - self.frame.width / 2
let dy1 = rightside.position.y - self.frame.height / 2
let rad1 = atan2(dy1, dx1)
let path1 = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 85, startAngle: rad1, endAngle: rad1 + CGFloat(M_PI * 4), clockwise: true)
let follow1 = SKAction.followPath(path1.CGPath, asOffset: false, orientToPath: true, speed: 250)
rightside.runAction(SKAction.repeatActionForever(follow1).reversedAction())
}
func counterclockwise() {
let dx = leftside.position.x - self.frame.width / 2
let dy = leftside.position.y - self.frame.height / 2
let rad = atan2(dy, dx)
let path = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 85, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(path.CGPath, asOffset: false, orientToPath: true, speed: 250)
leftside.runAction(SKAction.repeatActionForever(follow))
let dx1 = rightside.position.x - self.frame.width / 2
let dy1 = rightside.position.y - self.frame.height / 2
let rad1 = atan2(dy1, dx1)
let path1 = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 85, startAngle: rad1, endAngle: rad1 + CGFloat(M_PI * 4), clockwise: true)
let follow1 = SKAction.followPath(path1.CGPath, asOffset: false, orientToPath: true, speed: 250)
rightside.runAction(SKAction.repeatActionForever(follow1))
}

Related

How can I add animation on Path

I made custom button with Shape. When I click on custom Shape. I changed path CGPoint.
I wanna change this points with animation but I couldn't. How can I do this ?
ContentView:
struct ContentView: View {
#State var buttonStatus: ButtonStatus = .left
#State var foo: Bool = false
var body: some View {
BarButton(buttonStatus: buttonStatus)
.frame(width: 125, height: 45)
.animation(.easeInOut, value: foo)
.onTapGesture {
// withAnimation(.easeInOut(duration: 1)) {
buttonStatus = buttonStatus == .left ? .right : .left
// }
}
}
}
BarButton
struct BarButton: Shape {
private var buttonStatus: ButtonStatus
var animatableData: ButtonStatus {
get { return buttonStatus }
set { buttonStatus = newValue }
}
init(buttonStatus: ButtonStatus) {
self.buttonStatus = buttonStatus
}
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: buttonStatus == .left ? rect.minX + 12 : rect.minX + 18, y: .zero))
path.addLine(to: CGPoint(x: buttonStatus == .left ? rect.maxX - 15 : rect.maxX - 12, y: .zero))
path.addCurve(to: CGPoint(x: rect.maxX, y: buttonStatus == .left ? rect.minX + 13 : rect.minY + 14), control1: CGPoint(x: rect.maxX - 5, y: .zero), control2: CGPoint(x: rect.maxX, y: rect.minY + 7))
path.addCurve(to: CGPoint(x: buttonStatus == .left ? rect.maxX - 5 : rect.maxX, y: buttonStatus == .left ? rect.midY + 15 : rect.minY + 30), control1: CGPoint(x: rect.maxX, y: buttonStatus == .left ? rect.midY : rect.minY + 15), control2: CGPoint(x: buttonStatus == .left ? rect.maxX - 5 : rect.maxX, y: buttonStatus == .left ? rect.midY + 15 : rect.minY + 30))
path.addCurve(to: CGPoint(x: buttonStatus == .left ? rect.maxX - 20 : rect.maxX - 10, y: rect.maxY), control1: CGPoint(x: buttonStatus == .left ? rect.maxX - 8 : rect.maxX, y: buttonStatus == .left ? rect.maxY : rect.maxY - 5), control2: CGPoint(x: buttonStatus == .left ? rect.maxX - 10 : rect.maxX - 5, y: rect.maxY))
path.addLine(to: CGPoint(x: buttonStatus == .left ? rect.minX + 12 : rect.minX + 10, y: rect.maxY))
path.addCurve(to: CGPoint(x: .zero, y: buttonStatus == .left ? rect.midY + 10 : rect.maxY - 10), control1: CGPoint(x: buttonStatus == .left ? rect.minX + 5 : rect.minX + 4, y: rect.maxY), control2: CGPoint(x: .zero, y: rect.maxY - 5))
path.addLine(to: CGPoint(x: buttonStatus == .left ? .zero : rect.minX + 7, y: buttonStatus == .left ? rect.minY + 15 : rect.minY + 9))
path.addCurve(to: CGPoint(x: buttonStatus == .left ? rect.minX + 12 : rect.minX + 18, y: .zero), control1: CGPoint(x: buttonStatus == .left ? .zero : rect.minX + 9, y: buttonStatus == .left ? rect.minY + 7 : rect.minY + 4), control2: CGPoint(x: buttonStatus == .left ? rect.minX + 5 : rect.minX + 12, y: .zero))
path.closeSubpath()
return path
}
}
As #Asperi commented, animatableData works by interpolating its values, so it needs to be of a type that "can be interpolated", such as a CGFloat (e.g. Changing its value from 0.0 to 5.0, will interpolate all values in between).
In your case, you are defining animatableData as a ButtonStatus (I guess it is an enum). So it does have no clue on how to apply the interpolation when changing values.
However, you can define how interpolation should be handled for a given custom type by making it conform to VectorArithmetic.
Note that in your case, as the effect produced by changing the animatableData value varies from CGPoint to CGPoint in the path, you should probably extract some logic so these changes may be all defined by a single interpolation logic.
Here is an article in which VectorArithmetic is used to animate a path defined by multiple curves (Similar to what you want to achieve).
Note: Be aware you are applying an animation on foo change, which has nothing to do with the button's shape change, so make sure to also replace it with the value that actually defines its shape.

Custom coordinates and axis range on leaflet.js

I have a raster image with dimensions (in pixels) 16384-by-12288 which is successfully rendered in leaflet. I am using a my own CRS and I am placing point (0,0) at the bottomleft corner of the image point (16384, 12288) at its topright using the option: transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256).
The axes of my image, however, have range x:[6150, 1370] and y:[12987, 18457]
How can I tell leaflet to use my range as a system of coordinates please? Hence a marker at location (6150, 12987) will correspond and show up at the bottomleft corner: (0,0). I have done this manually using the function below:
var grid = {x0: 6150, // range of plot in Matlab
x1: 13751,
y0: 12987,
y1: 18457};
var img = [16384,
12288];
function project(p, img, grid) {
var x = p[0],
y = p[1];
xx = img[0] / (grid.x1 - grid.x0) * (x - grid.x0);
yy = img[1] / (grid.y1 - grid.y0) * (y - grid.y0);
return [xx, yy]
}
I was wondering however that there must a more streamlined and better way to do this. My code is:
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
var img = [
16384, // original width of image
12288 // original height of image
];
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256),
});
var bounds = L.latLngBounds([
xy(0, 0),
xy(img)
]);
var map = L.map('map', {
crs: L.CRS.MySimple,
maxBounds: bounds.pad(.5),
}).setView([img[1] / 2, img[0] / 2], 0);
L.tileLayer('myImage/{z}/{x}/{y}.png', {
bounds: bounds,
minZoom: 1,
maxZoom: 6
}).addTo(map);
L.marker([0, 0]).addTo(map).bindPopup("Zero");
L.marker([img[1] / 2, img[0] / 2]).addTo(map).bindPopup("[img[1] / 2, img[0] / 2]");
L.marker([img[1], img[0]]).addTo(map).bindPopup("img");
I think I did some progress. In case someone faces something similar in the future, here is my code: (comments more than welcome)
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
var img = [
16384, // original width of image
12288 // original height of image
];
var mapSW = [0, 16384],
mapNE = [12288, 0];
var roi = { //range of interest
x0: 6150,
x1: 13751,
y0: 12987,
y1: 18457
};
a = img[0] / (roi.x1 - roi.x0)
b = -img[0] / (roi.x1 - roi.x0) * roi.x0
c = img[1] / (roi.y1 - roi.y0)
d = -img[1] / (roi.y1 - roi.y0) * roi.y0
// This transformation maps a point in pixel dimensions to our user defined roi
var t = new L.Transformation(a, b, c, d);
// The transformation in this CRS maps the the bottom right corner to (0,0) and the topleft to (256, 256)
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256),
});
var bounds = L.latLngBounds([
xy(0, 0),
xy(img)
]);
var map = L.map('map', {
crs: L.CRS.MySimple,
maxBounds: bounds.pad(.5),
}).setView([img[1] / 2, img[0] / 2], 0);
L.tileLayer('map/{z}/{x}/{y}.png', {
bounds: bounds,
minZoom: 1,
maxZoom: 6,
}).addTo(map);
// map.setMaxBounds(new L.LatLngBounds(
// map.unproject(mapSW, map.getMaxZoom()),
// map.unproject(mapNE, map.getMaxZoom()),
// ));
L.marker([0, 0]).addTo(map).bindPopup("Zero");
L.marker([img[1] / 2, img[0] / 2]).addTo(map).bindPopup("[img[1] / 2, img[0] / 2]");
L.marker([img[1], img[0]]).addTo(map).bindPopup("img");
var marker = L.marker(xy([10000, 0]), {
draggable: true
}).addTo(map);
marker.bindPopup("");
marker.on("dragend", function(e) {
m = marker.getLatLng();
proj = map.project(m, map.getMaxZoom());
marker.getPopup().setContent('Clicked ' +m.toString() + '<br />' +
'Pixels ' + proj.toString())
.openOn(map);
})
L.control.scale({
imperial: false
}).addTo(map);
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(map);
}
map.on('click', onMapClick);
var p = t.transform(L.point(roi.x1, roi.y1));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(10000, 12987));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(13000, 12987));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(6150, 18000));
L.circleMarker(xy([p.x, p.y])).addTo(map);

Formula to translate xy movement with rotation

i have a canvas element that i rotate with context.rotate();
when i drag around the image in the canvas if i rotated lets say 90 degrees, and i move to the left, the image moves down,
is there a formula in which i can apply a movement of
for example
x+5 y+2 * degrees
and i get the real movement i need to do to move the rotated canvas in the direction i want? I would like to apply it to this function which works but with the undesired efect of moving left and the image moving down `
vm.canvasMouseMove = function (event) {
vm.delta = Date.now();
if (vm.mouseisdown && vm.delta - vm.now > (1000 / 60) && (event.clientX > 0 && event.clientY > 0)) {
vm.now = vm.delta
vm.snapshot.mouse.x -= event.clientX;
vm.snapshot.offsetSlider.value -= vm.snapshot.mouse.x;
if (vm.snapshot.offsetSlider.value < -160) {
vm.snapshot.offsetSlider.value = -160
}
else if (vm.snapshot.offsetSlider.value > 160) {
vm.snapshot.offsetSlider.value = 160
}
vm.snapshot.mouse.y -= event.clientY;
vm.snapshot.verticalOffsetSlider.value += vm.snapshot.mouse.y;
if (vm.snapshot.verticalOffsetSlider.value < -120) {
vm.snapshot.verticalOffsetSlider.value = -120
}
else if (vm.snapshot.verticalOffsetSlider.value > 120) {
vm.snapshot.verticalOffsetSlider.value = 120
}
vm.snapshot.mouse.x = event.clientX;
vm.snapshot.mouse.y = event.clientY;
}
};`
This draws
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.drawImage(image, vm.snapshot.offsetSlider.value - (canvas.width / 2), (vm.snapshot.verticalOffsetSlider.value * -1) - (canvas.height / 2), vm.scaledImageW * vm.snapshot.zoomSlider.value / 100, vm.scaledImageH * vm.snapshot.zoomSlider.value / 100);
ctx.rotate(-1 * vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
vm.donePicture = canvas.toDataURL();
This made the trick, passing the offset to the translate method, then draw the image only taking into account the canvas half translation
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate((canvas.width / 2) + (vm.snapshot.offsetSlider.value), (canvas.height / 2) - vm.snapshot.verticalOffsetSlider.value);
ctx.rotate(vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.drawImage(vm.canvasImage, 0 - (canvas.width/2), 0 - (canvas.height/2), vm.scaledImageW * vm.snapshot.zoomSlider.value / 100, vm.scaledImageH * vm.snapshot.zoomSlider.value / 100);
ctx.restore();

Zoom into group of elements with d3.js

I have an svg with multiple objects in it. On selecting a checkbox i would like to zoom into the appropriate objects. I've come to something like the code below but zooming and positions are still not accurate. Can some point me out how i could accomplish this?
d3.selectAll('g.active')
.each(function(d, i) {
var bounds = this.getBoundingClientRect();
boundsLeft.push(bounds.left);
boundsRight.push(bounds.right);
boundsTop.push(bounds.top);
boundsBottom.push(bounds.bottom);
});
var top = Math.min.apply(Math, boundsTop);
var bottom = Math.max.apply(Math, boundsBottom) ;
var right = Math.max.apply(Math, boundsRight) ;
var left = Math.min.apply(Math, boundsLeft);
var bounds = [[left, top], [right, bottom]],
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(0.6, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [(width / 2) - (scale * x), (height / 2) - (scale * y)];
vis.transition()
.duration(750)
.call(zoom.translate([((dx * -scale) + (width / 2)), ((dy * -scale) + height / 2)]).scale(scale).event);

Move SKSpriteNode until touches ended

I created this game in Spite Kit in Swift. I create a ship that I can determine its location be touching above or underneath the location of the ship. The ship will move towards the Y axis where I placed my finger. However at this point the ship will only move by a specific amount, in this case 30 or -30. How can I rewrite this code so the ship will keep on moving until my release of my finger?
Thanks!
func addShip() {
// Initializing spaceship node
ship = SKSpriteNode(imageNamed: "spaceship")
ship.setScale(0.45)
ship.zRotation = CGFloat(-M_PI/2)
// Adding SpriteKit physics body for collision detection
ship.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
ship.physicsBody?.categoryBitMask = UInt32(shipCategory)
ship.physicsBody?.affectedByGravity = false
ship.physicsBody?.dynamic = true
ship.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
//NEWLY ADDED
ship.physicsBody?.contactTestBitMask = coinCategory
ship.physicsBody?.collisionBitMask = 0
ship.name = "ship"
ship.position = CGPointMake(120, 160)
self.addChild(ship)
actionMoveUp = SKAction.moveByX(0, y: 30, duration: 0.2)
actionMoveDown = SKAction.moveByX(0, y: -30, duration: 0.2)
}
i don't like to use SKActions for these types of things because they're really meant for one off simple animations.. However, it may be appropriate depending your game's requirements..
if you insist on using SKActions then instead of doing moveBy, why don't you just use moveTo utilizing touch location? The only thing you'd have to do is come up with a ratio based on the distance your sprite needs to travel so that your animation duration is correct. example:
let curPoint = self.sprite.position
let destinationPoint = touch.position
let diffPoint = CGPoint(
x: curPoint.x - destinationPoint.x,
y: curPoint.y - destinationPoint.y
)
let distance = sqrt(pow(diffPoint.x, 2) + pow(diffPoint.y, 2))
let durationRatio = distance / CGFloat(600)
let moveAction = SKAction.moveTo(point, duration: NSTimeInterval(durationRatio))
self.sprite.runAction(moveAction, withKey: "move")
then when you want to cancel the movement. in touchesEnded you do
self.sprite.removeActionForKey("move")
using physics velocity
let curPoint = CGPoint(x: 10, y: 10)
let destinationPoint = CGPoint(x: 100, y: 100)
let diffPoint = CGPoint(
x: curPoint.x - destinationPoint.x,
y: curPoint.y - destinationPoint.y
)
let distance = sqrt(pow(diffPoint.x, 2) + pow(diffPoint.y, 2))
let normalizedVector = CGPoint(
x: diffPoint.x / distance,
y: diffPoint.y / distance
)
let speed = CGFloat(100)
let velocity = CGVector(
dx: normalizedVector.x * speed,
dy: normalizedVector.y * speed
)
self.sprite.physicsBody!.velocity = velocity
in touchesEnded you do
self.sprite.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)

Resources