leftiness / hex_math Goto Github PK
View Code? Open in Web Editor NEWLicense: MIT License
License: MIT License
The implementation for travel() would be cleaner if I could say x += Direction.x, y += Direction.y...
If I do this:
assert_eq!(set.len(), 1);
assert(set.contains("foo"));
... then the error message is less useful. Something like "0 != 1". Well, yeah. Okay. But why? In most cases, I have other assertions. I'd rather see "set does not contain foo", so make that assertion first.
Instead of always doing foo(point, otherPoint), it would maybe be nice to be able to point.foo(otherPoint) where self is used as the first point reference.
Instead of saying something like strength: i32 = prism.east_wall_strength
, do something more like this:
match prism.east {
Unbreakable => println!("can't break"),
Breakable(strength) => println!("{}", strength),
}
That does a better job of explaining the idea than setting the strength to -1 or something.
Travel() is the easiest function to consider. Distance() isn't so bad. Range() is getting harder. As these functions get more complex, they're harder to visualize. Trying to determine if there is a bug in the logic which draws line of sight will be very difficult without writing some code for visualization.
It doesn't have to be complex. I'm thinking that I could write something which takes an input set of prisms and renders it as ascii art.
fn main() {
let point: Point = Point::new(1, 2, 5);
let other: Point = Point::new(3, 4, 5);
let line: HashSet<Point> = line(&point, &other);
render(&line):
}
Height differences could be handled in different ways. The easiest would probably be to put a range of height numbers inside the drawn hexes. So it would be drawn from the top down, and a point which is the only drawn point at that QR point would have its QRT coordinate values: (1, 2, 5). The way to convey a coordinate with multiple hexes at different heights would be with QR[T]: (1, 2, [3,4,5]). That's interpreted as an array of QRT values: [ (1, 2, 3), (1, 2, 4), (1, 2,5) ].
I was thinking that eventually I'd like to include images in my docs. Those would be game images eventually. Rendered as proper sprites. In the meantime, maybe I could use something like this ascii visualizer because, again, it's hard to understand a series of point values without a picture.
a: Point
, and b: HashSet<Point>
says which points block his vision, and he has a visible range of c: i32
, which d: HashSet<Point>
can he see?While I would first and foremost need a function to find which points are visible, it may also be useful to have a function for determining which pixels are visible. As the resources show, what you choose to show and hide can make for a neat graphical effect.
Knowing the QRT coordinates of Point A and Point B, calculate the 2D bearing and 3D bearing. Specifically, if A is below B, 2D bearing will not take this into account, but 3D bearing calculation might determine that a line drawn from A to B would go through the bottom of B.
Also impl From<T>
for enums::Direction where T is the type of the bearing... maybe f32?
Basically, I'll return whatever kind of set you want so long as it works and I know how to convert my points into it. Then you can give me a set of YourObj when you call functions like ray(), and I'll give you those back.
#8 fix will allow adding referenced points. #7 fix will use generic HasValues trait on functions. From<> won't be used, and it's a test failure anyway.
---- prism::Prism_0 stdout ----
<anon>:9:24: 9:34 error: the trait bound `hex_math::Point: std::convert::From<hex_math::Prism>` is not satisfied [E0277]
<anon>:9 let other: Point = From::from(prism);
^~~~~~~~~~
<anon>:9:24: 9:34 note: required by `std::convert::From::from`
error: aborting due to previous error(s)
thread 'prism::Prism_0' panicked at 'Box<Any>', ../src/librustc/session/mod.rs:161
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
prism::Prism_0
test result: FAILED. 33 passed; 1 failed; 0 ignored; 0 measured
error: test failed
Modulo by 6. Then 0 is east, 1 is southeast... 5 is northeast. Then I can use that functionality in ring::ring_2d.
It's good to have one basic example that demonstrates how the function works (point = 1,2,5; set.contains(point);
), but then maybe also write one that's more realistic. You know like when I say "If I fireball is shot at this spot, will the player be in range?" and stuff like that.
The docs right now just say like "calculate the distance between two points." Well, maybe that one is simple enough to not need much more explanation... For example, the doc string for ray is kind of verbose. "Find unblocked points within range in a line through two points."
Maybe I'll eventually even add pictures. :)
Conflicting thoughts:
impl HasValues<i32> for Point<i32>
, impl HasValues<f32> for Point<f32>
impl HasValues<i32> for Point
, impl HasValues<f32> for FloatPoint
use HasValues<f32>
, FloatPoint::round() -> Point
use HasValues<f32>
, HasValues<f32>::round() -> Point
At any rate, I want a struct to hold f32 points for use in line(), and I want to get the round() function out of line().
a: Point
, and valid points are stored in b: HashSet<Point>
, and he wants to move to c: Point
, what is the best route to return in d: HashSet<Point>
?This would be impacted by a decision to use walls as edges (#2).
Needs a trait like HasValues... HasWalls?
Walls could be stored as edges instead of full hexagon points.
By using an extra property (and perhaps a separate HashSet<Wall>
), walls could be stored as edges instead of full hexagon points. This would impact calculations like field of view and pathfinding.
Notes:
It's like... I've got this HasValues<T>. I was thinking... I could
impl From<FooStruct> for Point instead? Do that on the FooStruct file.
Not on the Point file. So you would write this FooStruct in hex_render
or something, and that struct would impl From<FooStruct> for Point.
If I do it that way, I can run inputs of various functions (line,
distance, etc) through From::from() in order to get a Point which I
can use.
It doesn't solve the matter of trying to return an arbitrary
FooStruct. I guess I'll still have to return a Point despite accepting
a FooStruct as an argument. That's not really solved yet, anyway. The
best idea I have is to write a trait that enforces the existence of a
HasUpdate::update(point: &Point) function which would create a new
instance of FooStruct with an updated Point inside.
It's just... I don't really like that I'm doing a bunch of
Point::from(hasValues.values()) all over. fooStruct.into() would be
way nicer. I really should go through my code and see if there are
places where I can use fooStruct.into() instead of
BarStruct::from(fooStruct).
I thought I wouldn't need this, but it's actually needed for #41.
Basically, I have two QRT f32 points. One is in the center of the hex ("round"), and the other is a step along the line ("found"). I want to calculate the angle in order to determine which wall the line will exit on the side. However, (found.q - round.q, found.r - round.r) isn't really the (base, height aka adjacent, opposite) of the triangle on which I can use trig to find the angle.
So I need those XY coordinates from hex-to-pixel. Then I'll be able to use the X and Y difference as the triangle side lengths.
My current approach multiplies step_size by number_of_steps and adds that to starting_point in a for loop: round(take_steps(number_of_steps, step_size, starting_point));
take_steps() is a multiplication and an addition for each coordinate QRST for each step.
If I keep track of the previous step that I took, I can round(take_step(previous_step, step_size))
. take_step() will be only an addition for each coordinate QRST for each step.
Use that instead of impl From<u32> for Direction
for iteration. Maybe use the From<u32>
to convert from 360 degrees to a direction.
Writing these flood tests is awful. Ideas for functions:
// Add a point/prism if it doesn't exist, add a wall at that point
map.update_wall(&Point, &Direction, strength)
// Add a point/prism if it doesn't exist, set all walls to unbreakable, set eastern wall on western neighbor as unbreakable as well (etc) because I want nobody to enter or exit this point
map.set_impassable(&Point)
I'll need to use a HashMap<Point, HasWalls> instead of a HashSet. Rename opaque to walls. Use #39 to determine which wall would be passed through when I'm on a line from A to B. Then break if I find a point along the line where a strong enough wall exists.
There's no need to have an &&Point.
utils
mod that exists in some files. Make that a range_generic()
(or something) (for example)src/
folder and into somewhere else.I could probably clean up some of my thoughts on these issues and turn them into posts on idk Medium or something. I'm already writing things down as I work through problems. Is it valuable to keep that somewhere instead of sending it to the void of closed issues?
Maybe? I mean... I'm not even sure if the rotate function will be useful in the first place. Rotating on an arbitrary axis... Idk. Just a thought.
RIght now, I've got a util::get_step_direction() in line.rs. It works by subtracting p1 - p0, getting the positive/negative sign of the QRT, and making a basic guess about direction.
I want a better one.
Using p0, p1, and origin (0, 0, 0), I can do some trig to determine what the direction of p0>p1 is properly. Remember to convert to PixelPoint before doing the trig.
Maybe do something to decide whether to call the direction "up" or "east." If it's really, really far up and one hex east, then that p0>p1 direction should be called up. Once you reach a place where they're about equal, then you can alternate between east and up. I guess favor east just because you have to favor one or the other.
This function should probably be like Direction::from((&Point, &Point)).
Right now, rotate calculates the point rotated around 0,0,0. That's... actually not really useful. I should be able to provide a point to rotate around.
FloatPoint addressed the problem, but it's very copy-paste in comparison to Point. I want to make this generic somehow.
I don't know about threads really, but these functions are sometimes doing things that could be broken down into chunks, where chunk A doesn't really care about what happens in chunk B because the only thing that matters is that the return value is A + B.
Ah... I hastily pushed that. Right when I went to use it in line(), I realized... The pixel math from red blob is for 2d hexagons, so I can't just add my T to the height.
T would be represented by one pixel in height. That's the vertical side. The horizontal sides would be skewed for the pseudo-3D presentation. In the math, those sides are the same length as the height, but in pixels... it's complicated.
I don't actually need to calculate pseudo-3d pixel position right now. In fact, I might not need to at all. I could just draw the hexes on the screen and listen for mouse click events. Whatever tool I use would probably tell me, "Hey. He clicked on this hex sprite. This one. It has these axial coordinates." So I don't think I'd have to convert pixel to hex in most cases.
So... remove that bad math from the From<Point>
bit in PixelPoint.
CubePoint can have generic coordinate types so he can also replace FloatPoint::s(). Then I'll do something like this:
let CubePoint(q, r, s, t) = my_point.into();
Instead of this:
let FloatPoint(q, r, t) = my_float;
let s = my_float.s();
I'm currently doing something like this for #7:
pub fn distance_2d<T: HasValues>(point: &T, other: &T) -> i32 {
let diff: Point = &point.to_point() - &other.to_point();
let (q, r, s) = diff.values_cube_2d();
let distance = (q.abs() + r.abs() + s.abs()) / 2;
distance
}
I'd like to do let diff: Point = &point - &other;
or let (q, r, t) = &point - &other;
Maybe the second one is better since it doesn't require a conversion to Point unless I'm actually looking for that... in which case I'll use the new From::from or Point::from_values stuff.
Anyway, this HasValues - HasValues bit wasn't working. If I had it, then I could also get rid of the Add/Sub implementation on Point.
I wrote a HasValues#into_vec(). I'm not so sure that's the proper solution here.
For example, the work I'm doing for ray() has a step where I need to do A + B * C where A: i32, B: f32, C: f32 for each element of three structs (A, A, A, A), (B, B, B, B), (C, C, C, C). So A0 + B0 * C0, A1 + B1 * C1, etc.
I can't use HasValues#into_vec() here even if I wanted to. This isn't a HasValues. It's a tuple of f32.
Here's some primitive work:
pub fn merge<T, U, V, W>(
a: (T, T, T, T),
b: (U, U, U, U),
op: V,
) -> (W, W, W, W) where V: Fn(T, U) -> W {
(op(a.0, b.0), op(a.1, b.1), op(a.2, b.2), op(a.3, b.3))
}
pub fn map<T, U, V>(
a: (T, T, T, T),
op: U,
) -> (V, V, V, V) where U: Fn(T) -> V {
(op(a.0), op(a.1), op(a.2), op(a.3))
}
pub fn main() {
let a = (1, 1, 1, 1);
let b = (2.5f32, 1f32, 2.5f32, 2.5f32);
let op = |x, y| x as f32 == y;
let result = merge(a, b, op);
println!("{:?}", result);
println!("{:?}", map(a, |x| x as f32 + 1.5f32));
}
I don't like that either. I'd have to write a lot of functions, and I'd have to duplicate the work for tuples of three, tuples of four... Just no.
So maybe a HasValues should be different. It could be a struct which is backed by a private vector. I don't want someone adding or removing or even altering values in this vector. If I give someone access to it, I would just provide an iterator and let them do map(), collect(), etc.
This HasValues would need to be HasValues<T>
. It would impl From<(T, T, T, T)>
.
Maybe it wouldn't even be the HasValues trait but maybe more of a struct. It could still implement HasValues. So... maybe this work would be done on the Point struct. With the ability to provide a function for mapping, I could iterate over a Point<f32>
, convert from f32 to i32, and collect back into a Point<i32>
.
Idk though. I kind of appreciate the idea of having a Point.q property, but at the same time I've been using point.values() and stuff like that. I'm not even using that Point.q property.
The compiled tests (not in a /// doc ) are way faster, but I want the docs to have those examples. Can I maybe use an annotation to put a compiled test into the doc? Maybe even from a separate test file?
#39 will be used to make ray() aware of walls, but that's only necessary because we don't know the direction we're moving in. With Flood(), we do. It's terribly inefficient to run trig for each zig zag in a flood when we already know where we're going. It's going in each direction in a for loop, so I just have to check for walls blocking movement in that direction.
A Prism contains a Point. If I ever need to get a Point from a Prism, it's easy. What is the value of the HasValues? It lets me say that I'll take a <T: HasValues>
instead of a <T: Into<Point>>
? That's no real benefit. It lets me calculate the s coordinate? No. That should be a function on the Point which the Prism wraps. Seems overcomplicated for no value.
And that my_point.values().into()
approach to copying the Point into a new Point? What? Ok. I didn't want to derive Copy on Point because it felt wrong to use Copy... but sometimes I'm copying those primitives, and values().into()
isn't good. It could be &my_point.into()
, which copies the primitives.
Those traits were needed because I wasn't doing it right. :)
See this test:
use std::collections::HashSet;
use hex_math::{line, Point};
let point: Point = Point::new(1, 2, 5);
let other: Point = Point::new(3, 4, 10);
let set: HashSet<Point> = line(&point, &other);
assert!(set.contains(&Point::new(1, 2, 5)));
assert!(set.contains(&Point::new(2, 2, 6)));
assert!(set.contains(&Point::new(2, 3, 8)));
assert!(set.contains(&Point::new(3, 3, 9)));
assert!(set.contains(&Point::new(3, 4, 10)));
assert_eq!(set.len(), 5);
Wrong. The manhattan distance from (1, 2, 5) to (2, 2, 6) is 2. There should not be a step with a distance of 2.
This bug causes other problems as well. I wrote the code to make ray aware of walls (#41), and I think it might need different tests if this bug didn't exist. The wall finding code is based on the idea that each step is manhattan distance 1 away from the previous step.
I didn't implement everything on that page yet, and the guy has a blog. There's probably more good stuff.
fn ray_with_strength(point, i32 range, map<point, f32 strength>);
flood with strength
The idea is that the ray/flood would be able to pass through certain semi-opaque points. However, passing through a point with strength 0.5 would reduce the range by an extra 0.5.
Could be useful to have these. For example:
point + point requires type Point. Type &Point doesn't work, so I've been using point.clone() to get around that. There's apparently a better way.
... same for subtraction.
Some fn foo(point: Point)
could instead be fn foo<T>(point: T) where Point: From<T>
. Then instead of directly using point, I would first let point: Point = From::from(point);
. After that, I'll have a proper Point to use for distance() or whatever.
I just added hex_math::Prism, but there would maybe be others... especially when I eventually need to start keeping track of things like which player is on which hex.
I would then be able to directly use structs like Prism in my functions.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.