Tensor field rotation
ProbableTrain opened this issue · comments
Mod 90 rather than 360 for tensor field rotation to avoid weird grid boundaries
Tensors can be improved in general - weirdness happens on the boundaries of radial and grid tensors where the major (/minor) eigenvector turns by 90 degrees, can't just be solved by mod-ing the field rotation when a grid surrounds a radial
Oooh I have an issue all to myself. Oh, I am jimmy_the_exploder from Reddit by the way. :)
Anyway, I have been looking at tensor.ts and basis_field.ts and it turns out your tensors had you half way there already: it is mod 180. Since we are using different terminology here I will try to explain myself in length just to avoid confusion.
In my demo I just use vectors, add direction vectors to each other. When you add vectors, you are adding/subtracting their magnitudes, and "kind of" averaging their angles in mod 360. The original direction vectors I am working on are these: For radials they point to the center of the radial, for grids they are [cos(theta),sin(theta)]. But if you directly use these vectors you get mod 360, naturally. So for each BasisField, I make identical vectors to these with 4 times the original angle. And this makes the vectors "mod 90" but the resulting total vectors can't be used directly now. Its angle not geometrically meaningful. But when I divide its angle by 4, I get a meaningful vector.
Your tensors are basically just vectors, where their theta property is holding half of the angle between matrix[0] matrix[1] (x and y basically). When you getMajor vector from a tensor you get a vector with half the angle of the tensor(just like the last operation I did on my mod 90 vectors). And when you are creating BasisField tensors you manually create the tensors with twice the angle of the vector(in case of Radials you do it without trigonometry which is a nice trick: [x**2-y**2, -2*x*y] has twice the angle of [x, y]). All this makes your tensors mod 180.
So the Tensor class encapsulates the "geometrically meaningless mod 180 vector" and only returns the meaningful vector (getMajor) right now. It should also make creation of that encapsulated vector easier. We should just give it the meaningful vectors, it should handle the angle*4 business (trust me this will simplify things greatly). BTW It also does addition "wrong", which causes the order of the BasisFields to affect their weight, which can be solved easily.
Also I might have an idea about this last issue you are talking about, I don't understand it by your description, but it might be the same thing that tripped me up for some time.
Hi!
It also does addition "wrong"
Feel free to change this, and also the decay function because mine isn't that great and doesn't allow for negative decay (maybe could be thought of as negative size?)
To better explain my last comment:
Currently streamlines stick to the 'major' or 'minor' vector, so when a radial field is inside a grid field, at some points the major vector of the grid will be perpendicular to the major vector of the radial, so lines integrated through the field will make a sudden sharp turn
Yep. I guessed right, it is the same problem with mine. Major and minor vector system of yours is causing you trouble here. You probably assume major and minor vectors are separate things and you will walk along major vectors to draw some roads and then walk along minor vectors to draw other roads. But you should be able to walk along majors, and when you are see a major that has more than 45 degree angle to your current direction, you should start walking along minor vectors. The way I solved this was like this: when I get a sample from the field I not only input the point at which I want the sample but also which direction I want. So the sample function is like this:
Field.sampleDirection(point:Vector, direction: Vector): Vector
It calculates the major vector, then does this:
while(angleBetween(major, direction) > 45) {
major.rotate(90);
}
return major;
(Since there is no "minor" it doesn't make sense to call it "major" so in my actual code that variable is just called "result".)
With your tensor system we can modify this to be like this:
We still get the tensor from the field:
Field.sampleTensor(point: Vector):Tensor
But we get rid of getMajor and getMinor functions and do the "rotate until you get the vector with <45 degree angle to the current direction" trick here:
Tensor.getDirection(direction: Vector)
Then it is a matter of changing your road drawing code a bit.
Sorry for the late replies, running up against a deadline
A change that would probably require the least amount of rewriting would be in impl/integrator.integrate and impl/integrator.sampleFieldVector
Currently they just return the specified major/minor depending on a boolean but they could take an optional argument currentDirection that returns the opposite major/minor if the angle is too large.
Something approximately like this:
sampleFieldVector(point, major, direction?) {
d = major ? tensor.getMajor() : tensor.getMinor()
if (direction && angleTooLarge between d and direction) return !major ? tensor.getMajor() : tensor.getMinor()
return d
Then streamlines.integrationStep would need to pass in the current direction. It already flips a vector around if it has a negative dot product, might make sense to instead do this in integrator.sampleFieldVector
Makes sense, I will do it that way then. I hope it doesn't break some other thing written with the assumption that major and minor vectors don't mix. I guess I will just try and see what happens.
Uh oh, that reminds me, lines are kept separate by making sure they don't come to close to lines with the same 'majorness', if that makes sense. Major lines stop if they come too close to an existing major line.
If a major line spawns and travels along some minor vectors, those points will only be seen by other major lines. If a new minor line appears, it won't have any knowledge of the part of the major line travelling along minor vectors and might have some weird overlapping visual artefacts.
I'm not sure if you've seen the paper I'm basing off or not but it's here, I'm using the same grid storage system (grid_storage.ts) which is currently separated by major/minor.
A possible way around this?:
- Integrator somehow signals that it is providing the opposite 'majorness'
- streamline stores sample in the correct grid_storage
Perhaps the integrator has a function, useMajor(point, currentDirection): boolean that tells streamlines.ts whether or not the next sample along the line should be major or minor, then it can store samples in the right grid
I knew it would be something about parallel roads getting too close to each other. 😄 I will look at the paper and try to parse what you are saying here. But I have a feeling size of this change might get out of hand. For now, I could just fix the mod 90, tensor addition and decay functions(hopefully just these changes don't break this mechanism). And we can come back to mixing major and minor vectors after that.
Sounds good, if you merge into the TensorImprovements branch I just created I can take a look at the parallel roads stuff after, then merge into master when it's all done