I'm trying to do something a little bit different then the usual.
I have a 3D gridmap node setup and I'm trying to autogenerate the dots and connections using A*
Instead of creating obstacles tiles, I'm creating walls in between the tiles, so the tiles are still walkable, you just cannot pass through a wall . I figure it that out all already
but I have no idea how to code how to connect the points in a easy way and not connect points that has walls in between...
I'm using a RaycastCast Node to detect the wall, and his position as it walk through every gridtile
but I can't figure it out a nested loop to find the neighbors points to connect
this is what I tried to do (obviously get_closest_point() is not working the way I wanted).
If I could get a point using only Vector3 Coordinates, I think I could make it work.
EXTRA: if you guys can show me a way to clean the code, especially on the "FORs" syntaxes, because I kind don't know what I'm doing
Any other clean code recommendations would be amazing and very much welcomed
At the end has a visual draw(image) of the logic of the idea.
onready var rc = $RayCast
onready var aS = AStar.new()
var floor_size = Vector3(12,0,12)
var origin = Vector3(-5.5, 0.5, -2.5)
var FRONT = Vector3(1,0,0)
var RIGHT = Vector3(0,0,1)
var BACK = Vector3(-1,0,0)
var LEFT = Vector3(-1,0,0)
func set_walkable():
var value = 0
var value2 = 0
var i = 0
for _length in range (origin.x, origin.x + floor_size.x + 1):
value += 1
value2 = 0
for _width in range(origin.z, origin.z + floor_size.z):
i += 1
value2 += 1
aS.add_point(i, Vector3(origin.x + value -1 , 0.5, origin.z + value2 -1) , 1.0)
value = 0
for _u in range(origin.x, origin.x + floor_size.x + 1):
value += 1
value2 = 0
for _v in range(origin.z, origin.z + floor_size.z):
value2 += 1
var from = aS.get_closest_point(Vector3(origin.x + value ,0.5, origin.z + value2) ) # Current
rc.translation = Vector3(origin.x + value -1 ,0.5, origin.z + value2 -1)
draw_points()
print(rc.translation)
rc.cast_to = FRONT
var to = aS.get_closest_point(rc.translation) # Front
if from != -1 and !rc.is_colliding():
aS.connect_points(from, to)
draw_connections(Vector3(rc.translation.x + 0.5,rc.translation.y,rc.translation.z))
rc.cast_to = BACK
to = aS.get_closest_point(rc.translation) # back
if from != -1 and !rc.is_colliding():
aS.connect_points(from, to)
draw_connections(Vector3(rc.translation.x + -0.5,rc.translation.y,rc.translation.z))
rc.cast_to = RIGHT
to = aS.get_closest_point(rc.translation) # right
if from != -1 and !rc.is_colliding():
aS.connect_points(from, to)
draw_connections(Vector3(rc.translation.x,rc.translation.y,rc.translation.z + 0.5))
rc.cast_to = LEFT
to = aS.get_closest_point(rc.translation) # left
if from != -1 and !rc.is_colliding():
aS.connect_points(from, to)
draw_connections(Vector3(rc.translation.x + 0.5,rc.translation.y,rc.translation.z + -0.5))
func draw_points(): # Make points visible
var cube = MeshInstance.new()
cube.mesh = CubeMesh.new()
cube.translation = rc.translation
cube.scale = Vector3(0.25,0.25,0.25)
add_child(cube)
print("Cubo adicionado")
func draw_connections(position): # Make connections visible
var line = MeshInstance.new()
line.mesh = PlaneMesh.new()
line.scale = Vector3(0.03,0.03,0.03)
line.translation = position
add_child(line)
print("Cubo adicionado")
Convert between Coordinates and Point ids
Let us establish a mapping between coordinates and point ids. Given that we have a floor_size, this is easy:
func vector_to_id(vector:Vector3, size:Vector3) -> int:
return int(int3(vector).dot(dimension_size(size)))
func id_to_vector(id:int, size:Vector3) -> Vector3:
var s:Vector3 = dimension_size(size)
var z:int = int(id / s.z)
var y:int = int((id % int(s.z)) / s.y)
var x:int = id % int(s.y)
return Vector3(x, y, z)
func int3(vector:Vector3) -> Vector3:
return Vector3(int(vector.x), int(vector.y), int(vector.z))
func dimension_size(size:Vector3) -> Vector3:
return Vector3(1, int(size.x + 1), int(size.x + 1) * int(size.y + 1))
Possible optimizations:
Store dimension_size(floor_size) and use that directly.
Skip calling int3 on the condition that the values you pass to vector_to_id are guaranteed to be integer.
We will need a function to get the total number of points:
func total_size(size:Vector3) -> int:
return int(size.x + 1) * int(size.y + 1) * int(size.z + 1)
Explanation
Let us start at 1D (one dimension). We will only have one coordinate. So we have an hypothetical Vector1 that has an x property. We are simply putting things in a line.
Then the mapping is trivial: to convert from the coordinates to the id, we take id = int(vector.x), and if we want the coordinate we simply do vector = Vector1(id).
Now, let us move to 2D. We have Vector2 with x and y. Thankfully we have a size (there are ways to do the mapping when the size is not known, but having a size is convenient).
Thus, we will be doing a 2D grid, with some width and height. The y coordinate tells us the row in which we are, and x tells us the position in the row.
Then if we have some id, we need to figure out how many rows we need to get there, and then in what position in that row we are. Figuring out the row is easy, we divide by the width of the grid. And the position in the row is the reminder. One caveat: We are measuring from 0 (so a width of 0 actually means 1 element per row).
We have:
func id_to_vector(id:int, size:Vector2) -> Vector2:
var y:int = int(id / (size.x + 1))
var x:int = id % int(size.x + 1)
return Vector2(x, y)
How about going the other way around? Well, we multiply y for the length of a row (the width), and add x:
func vector_to_id(vector:Vector2, size:Vector2) -> int:
return int(vector.x) + int(vector.y) * int(size.x + 1)
Notice:
We didn't need size.y.
We need size.x + 1 in both functions.
vector_to_id looks very similar to a dot product.
Thus, let us make a new function that returns the vector with which we would be making the dot product:
func dimension_size(size:Vector2) -> Vector2:
return Vector2(1, int(size.x + 1))
And use it:
func vector_to_id(vector:Vector2, size:Vector2) -> int:
return int(vector.dot(dimensional_size(size)))
func id_to_vector(id:int, size:Vector2) -> Vector2:
var s = dimensional_size(size)
var y:int = int(id / int(s.y))
var x:int = id % int(s.y)
return Vector2(x, y)
Note If there is no guarantee that vector only has integers in vector_to_id, the fractional part in the dot product make lead to a wrong result. Which is why I have a function to make it have only integer.
Time for 3D. We have Vector3 with x, y and z. We are making a 3D grid. Now the z will tell us the layer, and each layer is a 2D grid.
Let us review dimensional_size, We had:
func dimension_size(size:Vector2) -> Vector2:
return Vector2(1, int(size.x + 1))
That is the size of an element (1), the size of a row(size.x + 1), we need to add the size of a layer. That is, the size of the 2D grid, which is just width times height.
func dimension_size(size:Vector3) -> Vector3:
return Vector3(1, int(size.x + 1), int(size.x + 1) * int(size.y + 1))
And how do we get z from the id? We divide by the size of a grid (so we know on what grid we are). Then from the reminder of that division we can find y:
func vector_to_id(vector:Vector3, size:Vector3) -> int:
return int(vector.dot(dimensional_size(size)))
func id_to_vector(id:int, size:Vector3) -> Vector3:
var s = dimensional_size(size)
var z:int = int(id / int(s.z))
var y:int = int(int(id % int(s.z)) / int(s.y))
var x:int = id % int(s.y)
return Vector2(x, y, z)
In fact, technically, all these coordinates are computed on the same form:
func id_to_vector(id:int, size:Vector3) -> Vector3:
var s = dimensional_size(size)
var tot = total_size(size)
var z:int = int(int(id % int(tot)) / int(s.z))
var y:int = int(int(id % int(s.z)) / int(s.y))
var x:int = int(int(id % int(s.y)) / int(s.x))
return Vector2(x, y, z)
Except, there is no need to take the reminder with the total size because id should always be less than that. And there is no need to divide by s.x because the size of a single element is always 1. And I also removed some redundant int casts.
What is total_size? The next element of dimensional_size, of course:
func dimension_size(size:Vector3) -> Vector3:
return Vector3(1, int(size.x + 1), int(size.x + 1) * int(size.y + 1))
func total_size(size:Vector3) -> int:
return int(size.x + 1) * int(size.y + 1) * int(size.z + 1)
Checking connectivity
And a way to check connectivity:
func check_connected(start:Vector3, end:Vector3) -> bool:
rc.transform.origin = start
rc.cast_to = end
rc.force_update_transform()
rc.force_raycast_update()
return !raycast.is_colliding()
And you had the right idea with FRONT, RIGHT, BACK and LEFT but put them in an array:
var offsets = [Vector3(1,0,0), Vector3(0,0,1), Vector3(-1,0,0), Vector3(-1,0,0)]
Note I'm calling force_update_transform and force_raycast_update because are doing multiple raycast checks on the same frame.
Populating AStar
Alright, enough setup, we can now iterate:
for id in total_size(floor_size):
pass
On each iteration, we need to get the vector:
for id in total_size(floor_size):
var vector = id_to_vector(id, floor_size)
Posible optimization: Iterate over the vector coordinates directly to avoid calling id_to_vector.
We can add the vector to AStar:
for id in total_size(floor_size):
var vector = id_to_vector(id, floor_size)
aS.add_point(id, vector)
Next we need the adjacent vectors:
for id in total_size(floor_size):
var vector = id_to_vector(id, floor_size)
aS.add_point(id, vector)
for offset in offsets:
var adjacent = vector + offset
Let us add them to AStar too:
for id in total_size(floor_size):
var vector = id_to_vector(id, floor_size)
aS.add_point(id, vector)
for offset in offsets:
var adjacent = vector + offset
var adjacent_id = vector_to_id(adjacent, floor_size)
aS.add_point(adjacent_id, adjacent)
Possible optimizations:
Do not add if has_point returns true.
If the id of the adjacent vector is lower, do not process it.
Modify offsets so that you only check adjacent position that are yet to be added (and thus preventing the prior two cases).
Let us check connectivity:
for id in total_size(floor_size):
var vector = id_to_vector(id, floor_size)
aS.add_point(id, vector)
for offset in offsets:
var adjacent = vector + offset
var adjacent_id = vector_to_id(adjacent, floor_size)
aS.add_point(adjacent_id, adjacent)
if check_connected(vector, adjacent):
pass
And tell the AStar about the connectivity:
for id in total_size(floor_size):
var vector = id_to_vector(id, floor_size)
aS.add_point(id, vector)
for offset in offsets:
var adjacent = vector + offset
var adjacent_id = vector_to_id(adjacent, floor_size)
aS.add_point(adjacent_id, adjacent)
if check_connected(vector, adjacent):
connect_points(id, adjacent_id)
I have value of a field coming from server lfrom 1 to 100 which is to show progress of that field. But I checked that we can provide value to progress bar from 0 to1. How can i convert this. I did something like this but didn't work
Int Field = 12;
Decimal d = Field / 100;
Decimal dc = Math.Round(d,1); //to round to one decimal place
return dc;
This is returning 0.
I tried this too:
double d = (double)(Progress / 100);
double dc = Math.Round(d, 1);
return dc;
This is also returning 0.
if you want maximum precision, you can convert an old range of values in a new range maintaining the ratio with this formula:
var OldRange = (OldMax - OldMin);
var NewRange = (NewMax - NewMin);
//i'm using round here has you requested, but its better to dont use it to achieve best results
var NewValue = Math.Round(((OldValue - OldMin) * NewRange) / OldRange) + NewMin, 1);
In your case, taking for example the number 12, this will be:
var OldRange = 99 //(100 - 1);
var NewRange = 1 //(1 - 0);
var NewValue = Math.Round(((12 - 1) * NewRange) / OldRange) + 0, 1);
Concluding the number 12 in the old range is 0.1 in the new range.
Or if you dont care that the old range starts from 1 and the new from 0, you can just divide by 100 and round the value:
Int field = 12;
Decimal d = field / 100;
Decimal dc = Math.Round(d,1); //to round to one decimal place
return dc;
Please note that in c# the divide operator is / and not % (wich is the modulus)
Turns out "/" operator doesn't work in C#
NO, "/" operator do work in C#.
You get a zero because Field / 100; is int/int, the result is 0;
Progress / 100 is the same, int/int get 0;
To make your code work. You can define the field as type Decimal :
Decimal Field = 12;
Decimal d = Field / 100;
Decimal dc = Math.Round(d, 1);
Or cast the 100 to Decimal:
int Field = 12;
Decimal d = Field /(Decimal)100;
Decimal dc = Math.Round(d, 1);
You can see detailed answer in these two threads: why-does-integer-division-in-c-sharp-return-an-integer-and-not-a-float
and
how-can-i-divide-two-integers-to-get-a-double
Before I upgraded to Swift 3 (Xcode 8) my random position start looked like this:
func randomStartPosition(range: Range<Int> = 45...(Int(self.frame.size.height)-45)) -> Int {
let min = range.startIndex
let max = range.endIndex
return Int(arc4random_uniform(UInt32(max - min))) + min }
Which stopped working after the conversion because it was updated to this:
func randomSmallObjectsStartPosition(_ range: Range<Int> = 25...(Int(self.frame.size.height)-25)) -> Int {
let min = range.lowerBound
let max = range.upperBound
return Int(arc4random_uniform(UInt32(max - min))) + min }
Which was worked out because I ended up learning about GamePlayKit:
Start by:
import GameplayKit
Then you can easily make new random variables using GamePlayKit:
You can read up on the apple site but basically this gives you more "hits" in the middle:
lazy var randomStartPosition = GKGaussianDistribution(randomSource: GKARC4RandomSource(), lowestValue: 0, highestValue:100)
And this will cycle through the random values until they are all consumed, then start again.
lazy var randomShuffle = GKShuffledDistribution(randomSource: GKARC4RandomSource(), lowestValue: 0, highestValue:100)
And lastly the totally random value generator:
lazy var totallyRandom = GKRandomDistribution(lowestValue: 1, highestValue: 100)
An example of how to use:
MyObject.position = CGPoint(x: self.frame.size.width + 20, y: CGFloat(totallyRandom.nextInt()) )
This will put my object off the screen on the far right and put it at a completely random position on the Y axis.
I want my Double to display as an Int, if the value is an integer - otherwise as a Double.
Example;
var Value = Double()
.
Value = 25.0 / 10.0
Now I want Value to display 2.5 (when inserted to label)
.
Value = 20.0 / 10.0
Now I want Value to display 2 - and NOT 2.0
One approach is to obtain the fractional part using % operator, and check if it is zero:
let stringVal = (Value % 1 == 0)
? String(format: "%.0f", Value)
: String(Value)
One classic way is to establish a value for epsilon which represents your tolerance for considering a value close enough to an Int:
// How close is close enough to be considered an Int?
let kEPSILON = 0.0001
var val = 1.9999
var str: String
if abs(val - round(val)) < kEPSILON {
str = String(Int(round(val)))
} else {
str = String(val)
}
print(str) // "2"
I like dasblinkenlight's and vacawama's answers, but also want to contribute another one: Using NSNumberFormatter
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.alwaysShowsDecimalSeparator = false
let string0 = formatter.stringFromNumber(25.0/10.0)!
let string1 = formatter.stringFromNumber(20.0/10.0)!
print(string0)
print(string1)
result:
2.5
2
The most important advantage: It is localized. On german devices it will show 2,5 instead of 2.5, just as it would be expected by a german speaking user.
To display numbers as text, use NSNumberFormatter(). You can set its minimumFractionDigits
property to zero:
let fmt = NSNumberFormatter()
fmt.minimumIntegerDigits = 1
fmt.maximumFractionDigits = 4
fmt.minimumFractionDigits = 0
print(fmt.stringFromNumber(25.0 / 10.0)!) // 2,5
print(fmt.stringFromNumber(20.0 / 10.0)!) // 2
print(fmt.stringFromNumber(2.0 / 7.0)!) // 0,2857
If you want a decimal period, independent of the user's locale,
then add
fmt.locale = NSLocale(localeIdentifier: "en_US_POSIX")
Swift 3:
let fmt = NumberFormatter()
// Optional:
fmt.locale = Locale(identifier: "en_US_POSIX")
fmt.minimumIntegerDigits = 1
fmt.maximumFractionDigits = 4
fmt.minimumFractionDigits = 0
print(fmt.string(from: 25.0 / 10.0 as NSNumber)!) // 2,5
print(fmt.string(from: 20.0 / 10.0 as NSNumber)!) // 2
print(fmt.string(from: 2.0 / 7.0 as NSNumber)!) // 0,2857
Working on a calculator on Swift 4, I treated the number variables as String so I could display them on screen and converted them to Double for the calculations, then convert them back to String to display the result. When the result was an Int I didn't want the .0 to be displayed as well so I worked this out and it was pretty simple
if result.truncatingRemainder(dividingBy: 1) == 0{
screenLabel.text = String(Int(result))
}
else{
screenLabel.text = String(result)
}
so result is the variable in Double format, if divided by 1 it gives us 0 (perfect division means its an Int), I convert it in Int.
I have tried this with CGFloat and i am getting the following error: Can not invoke stride with an argument list of type '(CGFloat by: CGFloat)'
for var min:CGFloat = 0.0; min<=45.0; min = min+value {
print("\(min)")
}
to:
for min:CGFloat in 0.stride(CGFloat(55.0), by: min+value) {
print("\(min)")
}
Below is the latest overload for stride. You can use cast the number to CGFloat for the stride.
for min in (0 as CGFloat).stride(to: 55, by: value) {
print("\(min)")
}
However, stride returns a Striable when the for-loop begin. The by value does not update with the iteration of for-loop. A while-loop would be better for this case,
var min : CGFloat = 0
while (min < 55) {
print("\(min)")
min += value
}
import Foundation
let fromValue = CGFloat(0.0)
let toValue = CGFloat(10.0)
let distance = CGFloat(3.3)
// stride in interval from 0.0 ..< 10.0, with distance 3.3
let sequence = fromValue.stride(to: toValue, by: distance)
print(sequence.dynamicType)
/*
StrideTo<CGFloat>
*/
// StrideTo conforms to SequenceType protocol, so we can use for-in
for element in sequence {
print("\(element)")
}
/*
0.0
3.3
6.3
9.9
*/