I'm having a strange bug in the following code. Scroll down to see images for reference to this question. The program found in the images below is drawn in canvas using javascript, so please be aware that the x and y coordinates are all positive, so the y coordinates are upside down relative to the graph.
let rotateVectors = (vs, t) => {
return sortVectors(Object.keys(vs).map(v => {
console.log(vs[v].vector.direction);
let rateOfRotation = -.01 * (Math.random() * (5-1)+1);
let vector = vs[v].vector
let p = vector.magnitude;
let c = vector.coords;
let x = c.x*Math.cos(rateOfRotation) - c.y*Math.sin(rateOfRotation);
let y = c.x*Math.sin(rateOfRotation) + c.y*Math.cos(rateOfRotation);
return vector(cartesian2dCoordinate(x,y))
}));
}
The above method is called on a loop to slowly rotate vectors by some random amount, the map function returns a new vector which takes an x,y coordinate, or the 2d cartesian pair describing the rise and run of the vector.
rotateVectors returns a sorted list of all vectors (sorted by angle in degrees) in the vs object.
let vectorDirection = (c) => {
//THE ZERO VECTOR
if(c.x === 0 && c.y === 0) return 0;
//cardinal directions, vertical and horizontal
else if(c.x === 0) return c.y > 0 ? .5 : 1.5;
else if(c.y === 0) return c.x > 0 ? 0:Math.PI;
//q3
else if(c.x< 0 && c.y < 0) return (1+ (Math.atan(-1*((c.y * -1) / c.x))));
//q2
else if(c.x < 0 && c.y > 0) return (Math.PI / 180)*(180 + toDegrees(Math.atan((c.y * -1)/c.x)));
//q4
else if(c.y < 0 && c.x > 0) return (Math.PI / 180)*(toDegrees(Math.atan((c.y * -1)/c.x)));
//q1
else return Math.atan(c.y*-1/c.x);
}
This function returns a radian of the angle of a vector based on its quadrant.
Traversal zone 1
In image one, any given vector will rotate from the x plane marked in red, all the way around to the -y vertical column. S->E.
traversal zone 2, reversed direction
In image two, the vector magically teleports from the E plane to the S plane, and travels in the opposite direction. S->E, whereupon the vector teleports back to the -X plane "S" in image1.
I'm relatively new to drawing stuff with vectors. I remember a bit of trig from school, but I haven't used it in a very long time. Does anyone have an idea wwhat may be happening here? Why are my vectors teleporting, changing direction, and why do no vectors travers the null zone to the left of S of image 2, and down from S of image 1?
Thanks to samgak for the answer in the comments. I replaced the original vectorDirection function with this code. It works as expected. Vectors rotate in the direction expected, and at the rate expected.
let vectorDirection = (c) => {
//THE ZERO VECTOR
if(c.x === 0 && c.y === 0) return undefined;
else return Math.atan2(-1*c.y,c.x)
}
Related
I'm currently working on a game which is based on a svg canvas as shown
and what I'm trying to do now is to allow the ball to move at random once the game starts, but I'm having difficulties coding out the random movement.
function createBall() {
const svg = document.getElementById("canvas")!,
ball = new Elem(svg, 'circle') #create the ball
.attr("cx",300).attr("cy",300)
.attr("r",8)
.attr('fill','grey');
Observable.interval(10).takeUntil(Observable.interval(4000)) #10 milliseconds until 4000 milliseconds
.subscribe( () => ball.attr("cx", 2 + Number(ball.attr("cx")))); #I'm having issue here when i subscribe as i can only allow the ball to move to the right at the moment, aside from being random
}
I think you would need a constant velocity towards a certain direction in x and y coordinates.
My suggestion would be to create two random integer values for x_velocity and y_velocity. You could try to use Math.random() and Math.floor():
function getRandomInt(min, max) {
return Math.floor((Math.random() + min) * Math.floor(max));
}
Then you will need to determine the direction, if it is negative (go left) or positive (go right):
function getDirection() {
return this.getRandomInt(0, 2) === 0? -1 : 1;
}
Use these two functions to set your x_velocity and y_velocity. The ball should now be able to go to the left, right, up, or down:
directionX = this.getDirection();
directionY = this.getDirection();
x_velocity = directionX * this.getRandomInt(1,8); // the number will be between -8 and 8 excluding 0
y_velocity = directionY * this.getRandomInt(1,8); // same here
Observable.interval(10).takeUntil(Observable.interval(4000))
.subscribe( () => {
ball.attr("cx", x_velocity + Number(ball.attr("cx"))); // the ball should go towards the left or the right
ball.attr("cy", y_velocity + Number(ball.attr("cy"))); // the ball should go up or down
);
Happy Coding! :)
I'm creating a simulator with the Pixijs engine.
I have a function that is is to be used to draw a wall using the mouse. But I just can't seem to get it right. This is probably more of a math issue than programming.
Anyway, it should work like this:
User clicks on a square tile (start position is set)
Tink library for Pixi returns the (x,y) position just clicked on
relative to the canvas
User clicks on second square (in same row or column) and the
destination point is set
Please take a look at this Fiddle.
https://jsfiddle.net/ensf32e0/18/
I can get it to draw from left to right and from top to bottom. But right to left and bottom to top fail me.
I'm using an object with booleans to keep track of whether the user is putting down a start position or an end position. I'm not sure this isa good implementation.
let wallsObj={
start:{
x:0,
y:0,
done:false
},
end:{
x:1,
y:1,
done:false
}
};
drawTile draws a single tile and drawWallLine is the function with the problem. It takes the start and end positions and draws a tiled line between them:
function drawWallLine (obj,size) {
// Determine whether line is to be drawn horizontally or vertically
// if abs(x2-x1) is larger than abs(y2-y1) then horizontal else vertical
// assign len the the actual length of line
let len = Math.abs(obj.end.x - obj.start.x) > Math.abs(obj.end.y - obj.start.y)
? obj.end.x - obj.start.x
: obj.end.y - obj.start.y;
console.log('drawWallLine', len);
// same as above. If direction is horizontal, mx = 1 and my = 0 and vice versa
// this to be used to determine the polarity of size
let mx = Math.abs(obj.end.x - obj.start.x) > Math.abs(obj.end.y - obj.start.y) ? 1 : 0;
let my = Math.abs(obj.end.x - obj.start.x) < Math.abs(obj.end.y - obj.start.y) ? 1 : 0;
console.log("mx, my", mx, my);
// Get polarity of size. +size is going down or right while -size is going up or left
if (mx === 1) {
size = obj.end.x - obj.start.x >= 0 ? size : size * -1;
}
if (my === 1) {
size = obj.end.y- obj.start.y >= 0 ? size : size * -1;
}
console.log('size', size);
// If going down or right then
if (size >=0 ) {
for (let i = 0; i < Math.abs(len); i+=size) {
drawTile({
len: rs,
x: obj.start.x - obj.start.x%rs - .5 + i * mx,
y: obj.start.y - obj.start.y%rs - .5 + i * my,
line:{
width:1,
color:0xC2C2C2,
alpha:1
},
fill:{
color:0xFFFFFF,
alpha:1
}
});
}
} else { // if going up or left
for (let i = Math.abs(len); i > 0; i+=size) {
drawTile({
len: rs,
x: obj.start.x - obj.start.x%rs - .5 + i * mx,
y: obj.start.y - obj.start.y%rs - .5 + i * my,
line:{
width:1,
color:0xC2C2C2,
alpha:1
},
fill:{
color:0xFFFFFF,
alpha:1
}
});
}
}
}
This is my first time doing something like this so please bear with me. I feel like there's an obvious solution but i'm failing to see it.
Well, a simple fix to your problem is just to make sure that the start x/y is always the lower then the end x/y value. So I added the following code to the beginning of the drawWallLine function:
function drawWallLine (obj, size) {
if(obj.start.x > obj.end.x){
var temp = obj.start.x;
obj.start.x = obj.end.x;
obj.end.x = temp;
}
if(obj.start.y > obj.end.y){
var temp = obj.start.y;
obj.start.y = obj.end.y;
obj.end.y = temp;
}
This basically makes sure that the start value is always the lower value by swapping between start and end if start is bigger than end.
Here is the updated working fiddle: https://jsfiddle.net/ensf32e0/24/
So, I am currently reinventing the wheel (and learning a lot) by trying my hand at making a simple physics engine for my game engine. I have been searching the internet, trying (and failing) to fix my current problem. There are a lot of resources out there on the subject, but none of those that I have found seem to apply to my case.
THE PROBLEM IN SHORT: The collision resolution does not work as intended on some of the corners when two rectangles are colliding. How it fails varies based on the dimensions of the rectangles. What I am looking for is a "shortest overlap" kind of resolution for the collision or another fairly simple solution (I am open for suggestions!). (Scroll down for a better explaination and illustrations).
WARNING: The following code is probably not very efficient...
First of all, here is my physics loop. It simply loops through all of the game entities and checks if they collide with any other game entities. It is not efficient (n^2 and all of that), but it works for now.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target);
break;
}
}
}
}
}
}
}
},
Here is the code for the actual collision detection. This also seems to work alright.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is where the problems start to pop up. I want the entity to simply be "pushed" out of the target entity and have the velocity set to 0. This works fine as long as both the entity and the target are perfect squares. If let's say the entity (the player figure in the gif) is a rectangle, then the collision will "slipp" when colliding the longest sides (the X axis) with the target (the square). If I swap the player dimensions so that it is short and wide, then the same problem appears for the Y axis instead.
resolveTestTest: function(entity, target) {
var normalizedX = (target.getMidX() - entity.getMidX());
var normalizedY = (target.getMidY() - entity.getMidY());
var absoluteNormalizedX = Math.abs(normalizedX);
var absoluteNormalizedY = Math.abs(normalizedY);
console.log(absoluteNormalizedX, absoluteNormalizedY);
// The collision is comming from the left or right
if (absoluteNormalizedX > absoluteNormalizedY) {
if (normalizedX < 0) {
entity.pos.x = target.getRight();
} else {
entity.pos.x = target.getLeft() - entity.dim.width;
}
// Set velocity to 0
entity.vel.x = 0;
// The collision is comming from the top or bottom
} else {
if (normalizedY < 0) {
entity.pos.y = target.getBottom();
} else {
entity.pos.y = target.getTop() - entity.dim.height;
}
// Set velocity to 0
entity.vel.y = 0;
}
},
Collision on the Y axis works with these shapes
Collision on the X axis slips with these shapes
What can I do to fix this slipping problem? I have been bashing my head against this for the last 5 days, so I would be immensely grateful if some one could help push me in the right direction!
Thank you :)
-- EDIT: --
The slipping also happens if only moving in one direction along the left or right side.
-- EDIT 2 WORKING CODE: --
See my answer below for an example of the working code!
The important logical error you have made is this line:
if (absoluteNormalizedX > absoluteNormalizedY) {
This only works if both entities are square.
Consider a near-extremal case for your X-slipping example: if they almost touch at the corner:
Although the diagram is a little exaggerated, you can see that absoluteNormalizedX < absoluteNormalizedY in this case - your implementation would move on to resolve a vertical collision instead of the expected horizontal one.
Another error is that you always set the corresponding velocity component to zero regardless of which side the collision is on: you must only zero the component if is it in the opposite direction to the collision normal, or you won't be able to move away from the surface.
A good way to overcome this is to also record the collided face(s) when you do collision detection:
collideEntity: function(entity, target) {
// adjust this parameter to your liking
var eps = 1e-3;
// no collision
var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
if (!(coll_X && coll_Y)) return 0;
// calculate bias flag in each direction
var bias_X = entity.targetX() < target.getMidX();
var bias_Y = entity.targetY() < target.getMidY();
// calculate penetration depths in each direction
var pen_X = bias_X ? (entity.getRight() - target.getLeft())
: (target.getRight() - entity.getLeft());
var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
: (target.getBottom() - entity.getUp());
var diff = pen_X - pen_Y;
// X penetration greater
if (diff > eps)
return (1 << (bias_Y ? 0 : 1));
// Y pentration greater
else if (diff < -eps)
return (1 << (bias_X ? 2 : 3));
// both penetrations are approximately equal -> treat as corner collision
else
return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},
updatePhysics: function(step) {
// ...
// pass collision flag to resolver function
var result = Vroom.collideEntity(entity, target);
if (result > 0) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target, result);
break;
}
}
// ...
}
Using a bit flag instead of a boolean array for efficiency. The resolver function can then be re-written as:
resolveTestTest: function(entity, target, flags) {
if (!!(flags & (1 << 0))) { // collision with upper surface
entity.pos.y = target.getTop() - entity.dim.height;
if (entity.vel.y > 0) // travelling downwards
entity.vel.y = 0;
}
else
if (!!(flags & (1 << 1))) { // collision with lower surface
entity.pos.y = target.getBottom();
if (entity.vel.y < 0) // travelling upwards
entity.vel.y = 0;
}
if (!!(flags & (1 << 2))) { // collision with left surface
entity.pos.x = target.getLeft() - entity.dim.width;
if (entity.vel.x > 0) // travelling rightwards
entity.vel.x = 0;
}
else
if (!!(flags & (1 << 3))) { // collision with right surface
entity.pos.x = target.getRight();
if (entity.vel.x < 0) // travelling leftwards
entity.vel.x = 0;
}
},
Note that unlike your original code, the above also allows corners to collide - i.e. for velocities and positions to be resolved along both axes.
MY WORKING CODE
So with some help and guidance from the amazing #meowgoesthedog I finally got on the right track and found what I was looking for. The problem (as #meowgoesthedog pointed out) was that my code was really only going to work with squares. The solution was to check the intersection of the colliding bodies and solve based on the shortest intersection. Note: this will probably not be a suitable solution if you need accurate physics with small and fast moving objects. The code for finding the intersection depth is based on this: https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs which is related to this project: https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx.
Here is my working code:
My physics loop has not been changed much, except for some better names for some functions.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveDisplace(entity, target);
break;
}
}
}
}
}
}
}
},
The collision detection remains the same as well.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is the code that basically fixes the problem. The comments in the code should explain what it does pretty well.
getIntersectionDepth: function(entity, target) {
// Calculate current and minimum-non-intersecting distances between centers.
var distanceX = entity.getMidX() - target.getMidX();
var distanceY = entity.getMidY() - target.getMidY();
var minDistanceX = entity.halfDim.width + target.halfDim.width;
var minDistanceY = entity.halfDim.height + target.halfDim.height;
// If we are not intersecting at all, return 0.
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
return {
x: 0,
y: 0,
};
}
// Calculate and return intersection depths.
var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return {
x: depthX,
y: depthY,
};
},
Here is the updated resolving function. It now takes intersection depth in to account when determining axis of collision and then uses the sign of the intersection depth for the colliding axis when determining the direction to resolve.
resolveDisplace: function(entity, target) {
var intersection = Vroom.getIntersectionDepth(entity, target);
if (intersection.x !== 0 && intersection.y !== 0) {
if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
// Collision on the X axis
if (Math.sign(intersection.x) < 0) {
// Collision on entity right
entity.pos.x = target.getLeft() - entity.dim.width;
} else {
// Collision on entity left
entity.pos.x = target.getRight();
}
entity.vel.x = 0;
} else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
// Collision on the Y axis
if (Math.sign(intersection.y) < 0) {
// Collision on entity bottom
entity.pos.y = target.getTop() - entity.dim.height;
} else {
// Collision on entity top
entity.pos.y = target.getBottom();
}
entity.vel.y = 0;
}
}
},
Thank you all for your help!
The problem may be that you're correcting both X and Y collision based on the same position:
Player is at a certain position. Let's check collision.
Player's bottom right corner overlaps top left corner of object.
X position is corrected: Player is moved to the left.
Player's bottom right corner overlaps top left corner of object.
Y position is corrected: Player is moved up.
End result: The player is moved up and to the left.
You probably need to "get" the player's position again, between checks.
I have a 2D voxel map for a game, which is a 2D array where 1 means ground and 0 means sky.
Example: all 1's in the array (ground) are green boxes
The algorithm starts at the leftmost ground voxel that touches the sky (red box in picture).
It will explore 8 neighbours of the current position to check if one of them is a ground voxel and also touches a sky voxel. This means it should be added to the groundline.
Example of the algorithm working (it's able to go in 'caves' too)
On this map it figured it out and returned a line across the ground.
In some situations it suddenly stops though, like on this map:
After about 10 loops it stopped creating the line.
Here's the code, with some explanatory comments in there:
voxelToLine() {
let voxels = this.voxels.length,//this.voxels is the 2d array
lineGround = [],
checkedVoxels = [],
nowChecking,
toCheck = [],
otherPaths = [],
done = false;
for (let y = 1; y < voxels - 1; y++)//sets first coordinate for line
if (this.voxels[0][y] && (!this.voxels[0][y - 1] || !this.voxels[1][y] || !this.voxels[0][y + 1])) {
lineGround[0] = [0, y / voxels];
nowChecking = [1, y];//search starts from this point
}
let looped = 0;
while (!done) {//continues search untill right side is located, or it got stuk (max 10*voxelmap width loops)
toCheck = nowChecking.neighbours(8, (n) => n[0] > 0 && n[0] < voxels - 1);//gets 8 neighbour points around current point, neighbours between 1 and (voxelwidth -1) get returned
let foundNew = false;
for (let i = 0; i < toCheck.length; i++) {//check every neighbour
let x = toCheck[i][0],
y = toCheck[i][1],
index = y * voxels + x;
if (!checkedVoxels.includes(index)) {
if (this.voxels[x][y] && (!this.voxels[x][y - 1] || !this.voxels[x + 1][y] || !this.voxels[x - 1][y] || !this.voxels[x][y + 1])) {
//if the neighbour is a floor voxel, and touches a skyvoxel this neighbour is added to the line
checkedVoxels.push(index);
if (foundNew) {//if a valid neighbour is already found, this means there are 2 possible paths from the current point
otherPaths.push([x, y]);
} else {
lineGround.push([x / voxels, y / voxels]);
nowChecking = [x, y];
//valid point gets added to the line and currently explored point get updated
foundNew = true;
}
if (x >= voxels) done = true;
}
} else if (i == toCheck.length - 1 && !foundNew) {
if (otherPaths.length > 0) {
nowChecking = otherPaths.pop();
//if none of the neighbours are correct an alternative path gets explored
foundNew = true;
}
}
}
if (!foundNew || looped++ > voxels * 10) {
//if it never found a valid neighbour, or it's looped too often break from the whileloop
console.log('loops: ', looped);
break;
}
}
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1) lineGround.push([1, lineGround[lineGround.length - 1][1]]);
//x=0 and x=1 have to exist, so if they don't exist yet, add them
return lineGround;
}
You can also test it here: game. If you click you remove (set to 0) a few voxels within a radius of where you clicked. Also the line gets recalculated.
I'm stuck on this, because I have no idea why the line stops in some situations.
All code is here. The relevant file is js/Level.js
There are more problems than the one you raised. I played a bit on your site and there are many patterns where things go wrong.
I tried to follow the logic of your code, but got lost in details. So I rewrote most of the code. The main idea is that you should keep record of which direction (slope) you are travelling along the ground in order to know in which order you should look among the neighbours for one that is part of the ground.
Let's say the neighbours are numbered as follows, from 0 to 7:
+---+---+---+
| 7 | 0 | 1 |
+---+---+---+
| 6 | * | 2 |
+---+---+---+
| 5 | 4 | 3 |
+---+---+---+
The cell marked with * is the last cell you found to be on ground level. Now let's say the previous one found was at 6, then the search among the neighbours should start at 7, then 0, 1, 2, ... 5. The first one that is found to be solid, should be the next cell added to ground level.
Another example: if the previous one found was at 4 (we're going upward), then the neighbours should be searched starting at 5, then 6, 7, 0, 1, 2 and 3.
The first neighbour that is found to be solid (ground) is the one you want to add to your ground line. This way you will follow every curve, into "caves", upward or downward, left or right.
Of course, things can still go weird if you start on an island. But I did not attempt to solve that particular case.
I've implemented the above idea in the following version of your method:
voxelToLine() {
let voxels = this.voxels.length, x, y, i;
// neighbors' relative coordinates listed in clockwise order
const neighbor = [ [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1] ];
for (y = 0; y < voxels; y++) //sets first coordinate for line.
if (this.voxels[0][y]) break; // found ground, don't look further down
let lineGround = [[0, y / voxels]];
let [curX, curY] = [0, y]; //search starts here
let direction = 0; // upward
let looped = 0;
do {// Continues search until right side is located,
// or it got stuk (max 10*voxelmap width loops)
for (i = 0; i < 8; i++) {//check every neighbour, starting at `direction`
[x, y] = [curX + neighbor[direction][0], curY + neighbor[direction][1]];
// if we found ground, then pick that cell as the next one on the line
if (x>=0 && x<voxels && y>=0 && y<voxels && this.voxels[x][y]) break;
direction = (direction + 1) % 8; // turn clockwise to get next neighbour
}
//if it never found a valid neighbour
if (i === 8) break;
lineGround.push([x / voxels, y / voxels]);
// prepare for next round
[curX, curY] = [x, y];
direction = (direction + 5) % 8;
} while (looped++ <= voxels*10 && curX < voxels - 1);
//x=0 and x=1 have to exist, so if they don't exist yet, add them
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1)
lineGround.push([1, lineGround[lineGround.length - 1][1]]);
return lineGround;
}
Looks like it's skipping over the voxel right below the last legitimate ground voxel because it's already been "checked" (added to the checkedVoxels array).
Interestingly, this would prevent your ground path to ever turn 90 degrees (you'll notice your example picture doesn't have such a voxel pattern).
I am trying to figure out a way to have a fixed scale for the:
https://en.wikipedia.org/wiki/Diamond-square_algorithm
I see that the algorithm requires a power of 2 (+1) size of the array.
The problem I am having is that I would like to have the same heightmap produced regardless of the resolution. So if I have a resolution of 512 it would look the same as with the resolution 256 but just have less detail. I just can't figure out how to do this with.
My initial thought was to always create the heightmap in a certain dimension e.g. 1024 and downsample to the res I would like. Problem is I would like the upper resolution to be quite high (say 4096) and this severely reduces the performance at lower resolutions as we have to run the algo at the highest possible resolution.
Currently the algorithm is in javascript here is a snippet:
function Advanced() {
var adv = {},
res, max, heightmap, roughness;
adv.heightmap = function() {
// heightmap has one extra pixel this is ot remove it.
var hm = create2DArray(res-1, res-1);
for(var x = 0;x< res-1;x++) {
for(var y = 0;y< res-1;y++) {
hm[x][y] = heightmap[x][y];
}
}
return hm;
}
adv.get = function(x,y) {
if (x < 0 || x > max || y < 0 || y > max) return -1;
return heightmap[x][y];
}
adv.set = function(x,y,val) {
if(val < 0) {
val = 0;
}
heightmap[x][y] = val;
}
adv.divide = function(size) {
var x, y, half = size / 2;
var scale = roughness * size;
if (half < 1) return;
for (y = half; y < max; y += size) {
for (x = half; x < max; x += size) {
adv.square(x, y, half, Math.random() * scale * 2 - scale);
}
}
for (y = 0; y <= max; y += half) {
for (x = (y + half) % size; x <= max; x += size) {
adv.diamond(x, y, half, Math.random() * scale * 2 - scale);
}
}
adv.divide(size / 2);
}
adv.average = function(values) {
var valid = values.filter(function(val) {
return val !== -1;
});
var total = valid.reduce(function(sum, val) {
return sum + val;
}, 0);
return total / valid.length;
}
adv.square = function(x, y, size, offset) {
var ave = adv.average([
adv.get(x - size, y - size), // upper left
adv.get(x + size, y - size), // upper right
adv.get(x + size, y + size), // lower right
adv.get(x - size, y + size) // lower left
]);
adv.set(x, y, ave + offset);
}
adv.diamond = function(x, y, size, offset) {
var ave = adv.average([
adv.get(x, y - size), // top
adv.get(x + size, y), // right
adv.get(x, y + size), // bottom
adv.get(x - size, y) // left
]);
adv.set(x, y, Math.abs(ave + offset));
}
adv.generate = function(properties, resolution) {
Math.seedrandom(properties.seed);
res = resolution + 1;
max = res - 1;
heightmap = create2DArray(res, res);
roughness = properties.roughness;
adv.set(0, 0, max);
adv.set(max, 0, max / 2);
adv.set(max, max, 0);
adv.set(0, max, max / 2);
adv.divide(max);
}
function create2DArray(d1, d2) {
var x = new Array(d1),
i = 0,
j = 0;
for (i = 0; i < d1; i += 1) {
x[i] = new Array(d2);
}
for (i=0; i < d1; i += 1) {
for (j = 0; j < d2; j += 1) {
x[i][j] = 0;
}
}
return x;
}
return adv;
}
Anyone ever done this before ?
Not quite sure if I understand your question yet but I'll provide further clarification if I can.
You've described a case where you want a diamond-square heightmap with a resolution of 256 to be used at a size of 512 without scaling it up. I'll go through an example using a 2x2 heightmap to a "size" of 4x4.
A diamond-square heightmap is really a set of vertices rather than tiles or squares, so a heightmap with a size of 2x2 is really a set of 3x3 vertices as shown:
You could either render this using the heights of the corners, or you might turn it into a 2x2 set of squares by taking the average of the four surrounding points - really this is just the "square" step of the algorithm without the displacement step.
So in this case the "height" of the top-left square would be the average of the (0, 0), (0, 1), (1, 1) and (1, 0) points.
If you wanted to draw this at a higher resolution, you could split each square up into a smaller set of 4 squares, adjusting the average based on how close it is to each point.
So now the value of the top-left-most square would be a sample of the 4 sub-points around it or a sample of its position relative to the points around it. But really this is just the diamond square algorithm applied again without any displacement (no roughness) so you may as well apply the algorithm again and go to the larger size.
You've said that going to the size you wish to go to would be too much for the processor to handle, so you may want to go with this sampling approach on the smaller size. An efficient way would be to render the heightmap to a texture and sample from it and the position required.
Properly implemented diamond & square algorithm has the same first N steps regardless of map resolution so the only thing to ensure the same look is use of some specified seed for pseudo random generator.
To make this work you need:
set seed
allocate arrays and set base randomness magnitude
Diamond
Square
lower base randomness magnitude
loop #3 until lowest resolution hit
If you are not lowering the randomness magnitude properly then the lower recursion/iteration layers can override the shape of the result of the upper layers making this not work.
Here see how I do it just add the seed:
Diamond-square algorithm not working
see the line:
r=(r*220)>>8; if (r<2) r=2;
The r is the base randomness magnitude. The way you are lowering it will determine the shape of the result as you can see I am not dividing it by two but multiplying by 220/256 instead so the lower resolution has bigger bumps which suite my needs.
Now if you want to use non 2^x+1 resolutions then choose the closer bigger resolution and then scale down to make this work for them too. The scaling down should be done carefully to preserve them main grid points of the first few recursion/iteration steps or use bi-cubic ...
If you're interested take a look on more up to date generator based on the linked one:
Diamond&Square Island generator