How to get index in an array from random rotation degrees? - swift

I'm new to SwiftUI and I'm still working on my spinning wheel. For now I wanna show people the results on screen by Text("(Array[index])"). the question is i know the random rotation degrees but i don't know how to transfer it to that "result angles" index :P ,Any help I will appreciate it !
enter image description here
I use Path to draw the wheel:
var slices: Array<OneChance.OneChanceData> {
let sum = Double(newChoices.count)
var endDeg: Double = 0.0
var tempSlices: [OneChance.OneChanceData] = []
for (color, text) in zip(colors, newChoices) {
let degrees: Double = Double(360 / sum)
tempSlices.append(OneChance.OneChanceData(startAngle: Angle(degrees: endDeg), endAngle: Angle(degrees: endDeg + degrees), text: text, color: color))
endDeg += degrees
}
return tempSlices
}
The wheel view like:
ForEach(0..<chanceVM.newChoices.count) { i in
EachSliceView(chanceData: self.chanceVM.slices[i])
}
I use onTapGesture to get random wheel spinning degrees:
.onTapGesture {
self.chanceVM.showResult = false
self.chanceVM.rotateDegree = 0.0
playSound(sound: "wheelSpinning13s", type: "mp3")
self.chanceVM.allowToTapAgain = false
withAnimation(Animation.easeOut(duration: 13.0)) {
self.chanceVM.rotateDegree = Double.random(in: 5400.0...7200.0)
}
print("\(chanceVM.rotateDegree)")
print("\((Int(chanceVM.rotateDegree) % 360))")
print("\((Int(chanceVM.rotateDegree) % 360)/(360/chanceVM.newChoices.count))")
self.chanceVM.delayText()
self.chanceVM.delayTap()
}
So how can i get the index after random spinning degrees?

Related

SceneKit: How to arrange buttons in ascending order using for in loop?

The task is to add 10 buttons (0...9) with labels using for in loop.
I created buttons based on class ButtonPrototype. I assigned label to each button via counter inside for in loop.
It works, but there is incorrect labels order:
I need another order:
How can I implement correct order?
Code:
func createButtons() {
for y in 0...1 {
for x in 0...4 {
counterForLoop += 1
self.button = ButtonPrototype(pos: .init( CGFloat(x)/7, CGFloat(y)/7, 0 ), imageName: "\(counterForLoop)")
parentNode.addChildNode(button)
parentNode.position = SCNVector3(x: 100,
y: 100,
z: 100)
}
}
}
The following approach perfectly makes the trick:
for y in 0...1 {
for x in 0...4 {
let textNode = SCNNode()
let ascendingOrder: String = "\(((x+1)+(y*5)) % 10)"
let geo = SCNText(string: ascendingOrder, extrusionDepth: 0.5)
geo.flatness = 0.04
geo.firstMaterial?.diffuse.contents = UIImage(named: ascendingOrder)
textNode.geometry = geo
textNode.position = SCNVector3(x*10, -y*10, 0)
sceneView.scene?.rootNode.addChildNode(textNode)
print(ascendingOrder)
}
}
You have at least two problems with your code. Your smallest button label is in the lower left and you want it to be in the lower right, and your labels go 0-9, and you want them to go from 1 to 10 (but display 10 as “0”).
To reverse the x ordering, change X to 10-x in your creation of a position, and change your imageName to “((counterForLoop+1)%10)”:
self.button = ButtonPrototype(
pos: .init(
CGFloat(10-x)/7,
CGFloat(y)/7,
0),
imageName: "\((counterForLoop+1)%10)")
By the way, you should add a SceneKit tag to your question. That seems more important than either the label tag or the loops tag.

Swift CoreGraphics UIBezierPath will not fill interior correctly

I'm trying to draw countries and fill the interior a certain color. My data source is a TopoJSON file, which, in a nutshell, is made up of shapes that reference an array of arcs to create a shape. I convert this into an array of paths, which I then iterate through to draw the country. As you can see in the below screenshot, I'm drawing the correct lines of the outline of the country (Afghanistan).
However, when I try to use path.fill(), I end up getting the following. Note how the black lines are correct, but the colors go outside and inside haphazardly.
Code
var mapRegion = MapRegion()
var path = mapRegion.createPath()
var origin: CGPoint = .zero
geometry.paths
.enumerated()
.forEach { (geoIndex, shape) in
shape
.enumerated()
.forEach { (shapeIndex, coord) in
guard let coordPoint = coord.double else { return }
let values = coordinatesToGraphics(x: coordPoint.x, y: coordPoint.y)
let point = CGPoint(x: values.x, y: values.y)
if origin == .zero {
origin = point
}
// Shape is about to be closed
if shapeIndex != 0 && path.contains(point) {
// Close, save path (2)
path.addLine(to: origin)
// (3) path.close()
mapRegion.savePath()
// Add to map, reset process
canvas.layer.addSublayer(mapRegion)
mapRegions.append(mapRegion)
mapRegion = MapRegion()
path = mapRegion.createPath()
}
else {
if shapeIndex == 0 {
path.move(to: point)
} else {
path.addLine(to: point)
}
}
}
}
I've tried exhaustively messing with usesEvenOddFillRule (further reading), but nothing ever changes. I found that Comment (1) above helped resolve an issue where borders were being drawn that shouldn't be. The function savePath() at (2) runs the setStroke(), stroke(), setFill(), fill() functions.
Update: path.close() draws a line that closes the path at the bottom-left corner of the shape, instead of the top-left corner where it first starts drawing. That function closes the "most recently added subpath", but how are subpaths defined?
I can't say for sure whether the problem is my logic or some CoreGraphics trick. I have a collection of paths that I need to stitch together and treat as one, and I believe I'm doing that. I've looked at the data points, and the end of one arc to the beginning of the next are identical. I printed the path I stitch together and I basically move(to:) the same point, so there are no duplicates when I addLine(to:) Looking at the way the simulator is coloring the region, I first guessed maybe the individual arcs were being treated as shapes, but there are only 6 arcs in this example, and several more inside-outside color switches.
I'd really appreciate any help here!
Turns out that using path.move(to:) creates a subpath within the UIBezierPath(), which the fill algorithm seemingly treats as separate, multiple paths (source that led to discovery). The solution was to remove the extra, unnecessary move(to:) calls. Below is the working code and happy result! Thanks!
var mapRegion = MapRegion()
var path = mapRegion.createPath()
path.move(to: .zero)
var pointsDictionary: [String: Bool] = [:]
geometry.paths
.enumerated()
.forEach { (geoIndex, shape) in
shape
.enumerated()
.forEach { (shapeIndex, coord) in
guard let coordPoint = coord.double else { return }
let values = coordinatesToGraphics(x: coordPoint.x, y: coordPoint.y)
let point = CGPoint(x: values.x, y: values.y)
// Move to start
if path.currentPoint == .zero {
path.move(to: point)
}
if shapeIndex != 0 {
// Close shape
if pointsDictionary[point.debugDescription] ?? false {
// Close path, set colors, save
mapRegion.save(path)
regionDrawer.drawPath(of: mapRegion)
// Reset process
canvas.layer.addSublayer(mapRegion)
mapRegions.append(mapRegion)
mapRegion = MapRegion()
path = mapRegion.createPath()
pointsDictionary = [:]
}
// Add to shape
else {
path.addLine(to: point)
}
}
}
}

Have Leaflet panTo not center

I have a trail on a map that the user can "follow" by mousing over a graph (time and speed). If the user zooms in a lot, part of the trail may not be visible. When the user wants to see the part of the trail that is not showing I use the panTo method...
The panTo method of leaflet is currently also centering. I don't want to center, I want the map to move just enough to show a point. (The problem with panTo is it causes excessive map scrolling and a harsh user experience.)
I have tried changing the bounds, but that has an (unwanted) side affect of sometimes zooming out.
Any way I can do a "minimal" panTo?
This is a (working but unpolished) solution; map is our own map wrapper utility class, lmap is a leaflet map object in typescript, and toxy() is a method to convert lat/longs to x/y values.
if (!this.lmap.getBounds().contains(latlng)) {
let target = this.map.toxy(latlng);
let nw = this.map.toxy(this.lmap.getBounds().getNorthWest());
let se = this.map.toxy(this.lmap.getBounds().getSouthEast());
let x = 0, y = 0;
let margin = 75;
if (target.y < nw.y)
y = (-1 * (nw.y - target.y)) - margin;
else if (target.y > se.y)
y = (target.y - se.y) + margin;
if (target.x < nw.x)
x = (-1 * (nw.x - target.x)) - margin;
else if (target.x > se.x)
x = (target.x - se.x) + margin;
this.lmap.panBy(new L.Point(x, y));
}
First, fetch the bounds of the map (measured in pixels from the CRS origin) with map.getPixelBounds(). Then, use map.project(latlng, map.getZoom()) to get the coordinates (in pixels from the CRS origin) of the point you're interested.
If you're confused about this "pixels from the CRS origin" thing, read the "Pixel Origin" section at http://leafletjs.com/examples/extending/extending-2-layers.html .
Once you have these pixel coordinates, it should be a simple matter of checking whether the point is inside the viewport, and if not, how far away on each direction it is.
http://jsfiddle.net/jcollin6/b131tobj/2/
Should give you what you want
function clamp(n,lower,upper) {
return Math.max(lower, Math.min(upper, n));
}
//Shamelessly stolen from https://gist.github.com/dwtkns/d5b9b60285b8b0067c53
function getNearestPointInPerimeter(l,t,w,h,x,y) {
var r = l+w,
b = t+h;
var x = clamp(x,l,r),
y = clamp(y,t,b);
var dl = Math.abs(x-l),
dr = Math.abs(x-r),
dt = Math.abs(y-t),
db = Math.abs(y-b);
var m = Math.min(dl,dr,dt,db);
return (m===dt) ? {x: x, y: t} :
(m===db) ? {x: x, y: b} :
(m===dl) ? {x: l, y: y} : {x: r, y: y};
}
L.ExtendedMap = L.Map.extend({
panInside: function (latlng, pad, options) {
var padding = pad ? pad : 0;
var pixelBounds = this.getPixelBounds();
var center = this.getCenter();
var pixelCenter = this.project(center);
var pixelPoint = this.project(latlng);
var sw = pixelBounds.getBottomLeft();
var ne = pixelBounds.getTopRight();
var topLeftPoint = L.point(sw.x + padding, ne.y + padding);
var bottomRightPoint = L.point( ne.x - padding, sw.y - padding);
var paddedBounds = L.bounds(topLeftPoint, bottomRightPoint);
if (!paddedBounds.contains(pixelPoint)) {
this._enforcingBounds = true;
var nearestPoint = getNearestPointInPerimeter(
sw.x + padding,
ne.y + padding,
ne.x - sw.x - padding * 2,
sw.y - ne.y - padding * 2,
pixelPoint.x,
pixelPoint.y
);
var nearestPixelPoint = L.point(nearestPoint.x,nearestPoint.y)
var diffPixelPoint = nearestPixelPoint.subtract(pixelPoint);
var newPixelCenter = pixelCenter.subtract(diffPixelPoint);
var newCenter = this.unproject(newPixelCenter);
if (!center.equals(newCenter)) {
this.panTo(newCenter, options);
}
this._enforcingBounds = false;
}
return this;
}
});
Use this way
map.panTo([lat, lng]);
map.setZoom(Zoom);

Following a path in Spritekit EXC_BAD_ACCESS

I have a simple Snake game where the head draws a UIBezier path. That part works fine:
func addLineToSnakePath(snakeHead: SnakeBodyUnit) {
//add a new CGPoint to array
activeSnakePathPoints.append(CGPoint(x: snakeHead.partX, y: snakeHead.partY))
let index = activeSnakePathPoints.count-1
if (index == 1) {
path.moveToPoint(activeSnakePathPoints[index-1])
}
path.addLineToPoint(activeSnakePathPoints[index])
shapeNode.path = path.CGPath
}
The path generates with swipes as the Head moves around the screen. Now I add a body unit to follow the UIBezier path and I get a bad access error.
func addBodyPart() {
let followBody = SKAction.followPath(path.CGPath, asOffset: true, orientToPath: false, duration: 1.0)
snakePart.runAction(followBody)
}
Crash at:
0 SKCFollowPath::cpp_willStartWithTargetAtTime(SKCNode*, double)
Thread 1 EXC_BAD_ACCESS
Looking at the code I would rather strengthen my code this way:
func addLineToSnakePath(snakeHead: CGPoint) {
let count = activeSnakePathPoints.count
if count == 0 { return } // The snake don't have an head..
//add a new CGPoint to array
activeSnakePathPoints.append(CGPoint(x: snakeHead.x, y: snakeHead.y))
guard count > 1 else {
// There are only two element (the head point and the new point) so:
path = UIBezierPath.init()
path.moveToPoint(activeSnakePathPoints[count-1])
shapeNode.path = path.CGPath
return
}
// There are some elements:
path.addLineToPoint(activeSnakePathPoints[count-1])
shapeNode.path = path.CGPath
}
Next, when you make the followPath action I've seen you use offset value to true ( I don't know if this is wanted from you..):
asOffset : If YES, the points in the path are relative offsets to the
node’s starting position. If NO, the points in the node are absolute
coordinate values.
In other words true means the path is relative from the sprite's starting position, false means absolute
Finally in the line:
snakePart.runAction(followBody)
I don't know what is snakePart but you use shapeNode in your function
Update:
Thinking about your crash, seems snakePart is not a valid SKNode (like when you try to use a sprite or shape without initialize itself)

Keep relative positions of SKSpriteNode from SKShapeNode from CGPath

I have an array of array of coordinates (from a shapefile), which I'm trying to make into a series of SKSpriteNodes.
My problem is that I need to keep the relative positions of each of the shapes in the array. If I use SKShapeNodes, it works, as they are just created directly from the path I trace, but their resources consumption is quite high, and, in particular I cannot use lighting effects on them.
If I use SKSpriteNodes with a texture created from the shape node, then I lose their relative positions.
I tried calculating the center of each shapes, but their positions are still not accurate.
Here's how I draw them so far:
override func didMoveToView(view: SKView)
{
self.backgroundColor = SKColor.blackColor()
let shapeCoordinates:[[(CGFloat, CGFloat)]] = [[(900.66563867095, 401.330302084953), (880.569690215615, 400.455067051099), (879.599839322167, 408.266821560754), (878.358968429675, 418.182833936104), (899.37522863267, 418.54861454054), (900.66563867095, 401.330302084953)],
[(879.599839322167, 408.266821560754), (869.991637153925, 408.122907880045), (870.320569111933, 400.161243286459), (868.569953361733, 400.11339198742), (864.517810669155, 399.54973007215), (858.682258706015, 397.619367903278), (855.665753299048, 395.808813873244), (853.479452218432, 392.811211835046), (847.923492419877, 394.273974470316), (834.320860167515, 397.859104108813), (826.495867917475, 399.921507079808), (829.86572598778, 404.531781837208), (835.898936154083, 409.178035013947), (840.887737516875, 411.839958392806), (847.191868005112, 414.441797809335), (854.251943938193, 416.198384209245), (860.095769038325, 417.277496957155), (866.21091316512, 417.954970608037), (873.27118845149, 418.182833936104), (878.358968429675, 418.182833936104), (879.599839322167, 408.266821560754)],
[(931.018881691707, 402.151689416542), (910.610746904717, 401.600140235583), (910.380693886848, 411.576056681467), (930.79710181083, 411.750012342223), (931.018881691707, 402.151689416542)],
[(880.569690215615, 400.455067051099), (870.320569111933, 400.161243286459), (869.991637153925, 408.122907880045), (879.599839322167, 408.266821560754), (880.569690215615, 400.455067051099)]]
for shapeCoord in shapeCoordinates
{
let path = CGPathCreateMutable()
var center:(CGFloat, CGFloat) = (0, 0)
for i in 0...(shapeCoord.count - 1)
{
let x = shapeCoord[i].0
let y = shapeCoord[i].1
center.0 += x
center.1 += y
if i == 0
{
CGPathMoveToPoint(path, nil, x, y)
}
else
{
CGPathAddLineToPoint(path, nil, x, y)
}
}
center.0 /= CGFloat(shapeCoord.count)
center.1 /= CGFloat(shapeCoord.count)
let shape = SKShapeNode(path: path)
let texture = self.view?.textureFromNode(shape)
let sprite = SKSpriteNode(texture: texture)
sprite.position = CGPointMake(center.0, center.1)
//self.addChild(shape)
self.addChild(sprite)
}
}
Is it feasible or should I switch to another technology / method?