I'm trying to spawn enemies just outside the bounds of a rectangle. Here's a picture:
That is, the grey area is the playing area that the user can see, and the green is outside the rendering bounds. I'm looking for a way to calculate a spawn position in this green area.
I have a tentative solution, but it's pretty long and involves a bunch of if statements. Is there a more efficient or elegant way of calculating this?
function calcEnemySpawnPos(r) {
const roll = Math.random();
const left = -r;
const right = canvas.width + r;
const top = -r;
const bottom = canvas.height + r;
if (roll <= 0.25) {
return { x: left, y: getRandomInt(top, bottom) };
} else if (roll <= 0.5) {
return { x: right, y: getRandomInt(top, bottom) };
} else if (roll < 0.75) {
return { x: getRandomInt(left, right), y: top };
} else {
return { x: getRandomInt(left, right), y: bottom };
}
}
I have a slight improvement, but still not amazingly elegant. This is pseudocode since I'm not sure of the js syntax:
const rollLeft = Math.random() - 0.5;
const rollTop = Math.random() - 0.5;
if (rollLeft > 0){
x = getRandomInt(-r, 0)
} else {
x = getRandomInt(canvas.width, canvas.width + r)
}
if (rollRight > 0){
y = getRandomInt(-r, 0)
} else {
y = getRandomInt(canvas.height, canvas.height + r)
}
return {x, y}
There are 200,000 possible positions. You can generate just one random number and map it to a valid coordinate. You can specify four valid ranges, defined by top-left and bottom-right corners, then use your random number to get a range (weighted by area) and then convert the number to a point in that range.
function startPos(ranges, totalSize) {
let n = Math.trunc(Math.random() * totalSize);
const {x: j, y: k, w} = ranges.find(r => n < r.size || void(n -= r.size));
const x = n % w, y = (n - x) / w; // remainder/quotient of dividing by width
return [x + j, y + k]; // translate to start of range
}
[x, y] = startPos([
{x: -100, y: -100, w: 600, h: 100, size: 600 * 100},
{x: 500, y: -100, w: 100, h: 400, size: 100 * 400},
{x: 0, y: 300, w: 600, h: 100, size: 600 * 100},
{x: -100, y: 0, w: 100, h: 400, size: 100 * 400},
], 200_000);
The ranges.find(...) predicate is a little hard to read. Could also be written like this:
ranges.find(({size}) => {
if (n < size) return true;
else n -= size;
});
Note that this algorithm gives every pixel equal probability of being the spawn point, in contrast with your solution where each quadrant has equal probability of containing the spawn point, so pixels on the shorter sides have higher probability than pixels on the longer sides.
Hi I am trying to create isometric graphic app with React, mostly base on the code here.
I achieved most of the functions (ie. zoom and scroll).
But hovering tiles after zooming gives me wrong mouse position (hover position).
You can see what I mean here.
You can zoom with scrolling vertically.
When it is not zoomed in or out, hovering tile works correctly (tile color changes where the mouse positions).
But after zooming out/in it is not working right.
Does anyone know how to get the mouse position or tile index correctly after zooming in/out?
Implemented code can be found on my Github repo here
Code snippet for getting target tile is below:
const handleHover = (x: number, y: number) => {
const { e: xPos, f: yPos } = ctx.getTransform()
const mouse_x = mouseRef.current.x - x - xPos
const mouse_y = mouseRef.current.y - y - yPos
const hoverTileX =
Math.floor(
mouse_y / Tile.TILE_HEIGHT + mouse_x / Tile.TILE_WIDTH
) - 1
const hoverTileY = Math.floor(
-mouse_x / Tile.TILE_WIDTH + mouse_y / Tile.TILE_HEIGHT
)
if (
hoverTileX >= 0 &&
hoverTileY >= 0 &&
hoverTileX < gridSize &&
hoverTileY < gridSize
) {
const renderX =
x + (hoverTileX - hoverTileY) * Tile.TILE_HALF_WIDTH
const renderY =
y + (hoverTileX + hoverTileY) * Tile.TILE_HALF_HEIGHT
renderTileHover(ctx)(renderX, renderY + Tile.TILE_HEIGHT)
}
}
I am not good at maths so I really need help...
Thank you.
I figured out how to achieve this.
I will leave it here so anyone who has the same issue wont waste a lot of time for this kind of issue.
My code is like this:
/**
* #param context canvas context 2d
* #param inputX mouse/touch input position x (ie. clientX)
* #param inputY mouse/touch input position y (ie. clientY)
* #returns {x, y} x and y position of inputX/Y which map scale and position are taken into account
*/
export const getTransformedPoint = (context: CanvasRenderingContext2D, inputX: number, inputY: number) => {
const transform = context.getTransform()
const invertedScaleX = DEFAULT_MAP_SCALE / transform.a
const invertedScaleY = DEFAULT_MAP_SCALE / transform.d
const transformedX = invertedScaleX * inputX - invertedScaleX * transform.e
const transformedY = invertedScaleY * inputY - invertedScaleY * transform.f
return { x: transformedX, y: transformedY }
}
/**
*
* #param startPosition position where map start rendered (Position2D has {x: number, y: number} type)
* #param inputX mouse/touch input position x (ie. clientX)
* #param inputY mouse/touch input position x (ie. clientY)
* #returns positionX, positionY: tile position x, y axis
*/
export const getTilePosition = (
startPosition: Position2D,
inputX: number,
inputY: number
): { positionX: number; positionY: number } => {
const positionX =
Math.floor((inputY - startPosition.y) / TILE_HEIGHT + (inputX - startPosition.x) / TILE_WIDTH) - 1
const positionY = Math.floor(
(inputY - startPosition.y) / TILE_HEIGHT - (inputX - startPosition.x) / TILE_WIDTH
)
return { positionX, positionY }
}
// usage
const onClick = (e: MouseEvent) => {
const { x: mouseX, y: mouseY } = getTransformedPoint(ctx, e.clientX, e.clientY)
const { positionX, positionY } = getTilePosition(startPositionRef.current, mouseX, mouseY)
// Do something with positionX and positionY...
// ie.
if (return positionX >= 0 && positionY >= 0 && positionX < GRID_SIZE && positionY < GRID_SIZE) {
// code when a user clicks a tile within the map
}
}
I referenced this for calculating the mouse position when the map is zoomed out/in.
Try below
const handleHover = (x: number, y: number) => {
// use the current scale of the canvas context
const { a: scale, e: xPos, f: yPos } = ctx.getTransform()
const mouse_x = (mouseRef.current.x - x - xPos) * scale
const mouse_y = (mouseRef.current.y - y - yPos) * scale
// rest of the code...
}
Note: This question has NOTHING to do with Three.js, it's only Tensorflow.js and Trigonometry.
I am trying to rotate a 3D object in Three.js by rotating my face. I have used this code by akhirai560 for rotating in X and Y axis.:
function normal(vec) {
let norm = 0;
for (const v of vec) {
norm += v * v;
}
return Math.sqrt(norm);
}
function getHeadAnglesCos(keypoints) {
// Vertical (Y-Axis) Rotation
const faceVerticalCentralPoint = [
0,
(keypoints[10][1] + keypoints[152][1]) * 0.5,
(keypoints[10][2] + keypoints[152][2]) * 0.5,
];
const verticalAdjacent = keypoints[10][2] - faceVerticalCentralPoint[2];
const verticalOpposite = keypoints[10][1] - faceVerticalCentralPoint[1];
const verticalHypotenuse = normal([verticalAdjacent, verticalOpposite]);
const verticalCos = verticalAdjacent / verticalHypotenuse;
// Horizontal (X-Axis) Rotation
const faceHorizontalCentralPoint = [
(keypoints[226][0] + keypoints[446][0]) * 0.5,
0,
(keypoints[226][2] + keypoints[446][2]) * 0.5,
];
const horizontalAdjacent = keypoints[226][2] - faceHorizontalCentralPoint[2];
const horizontalOpposite = keypoints[226][0] - faceHorizontalCentralPoint[0];
const horizontalHypotenuse = normal([horizontalAdjacent, horizontalOpposite]);
const horizontalCos = horizontalAdjacent / horizontalHypotenuse;
return [horizontalCos, verticalCos];
}
It calculates the rotation by finding the cos of these points (original image source):
I also want to calculate the cos of Z axis rotation. Thanks!
Am not fully clear of the end goal of the question, but if simply looking to rotate about the Z axis, the following snippet will assist.
center = { x: 50, y: 50 };
points = [
{ x: 60, y: 60 },
{ x: 50, y: 100 }
]
function rotateAboutZ( xyRotationPoint, points, zRotation ) {
let result = points.map( p => {
// Adjust the point based on the center of rotation.
let x = p.x - xyRotationPoint.x;
let y = p.y - xyRotationPoint.y;
// Calculate the radius and angle in preparation for rotation
// about the Z axis.
let radius = Math.sqrt( x * x + y * y );
let angle = Math.atan2( y, x );
// Adjust the angle by the requested rotation.
let angleRotated = angle + zRotation;
// Finally, calculate the new XY coordinates, and re-adjust
// based on the center of rotation.
let xRotated = radius * Math.cos( angleRotated ) + xyRotationPoint.x;
let yRotated = radius * Math.sin( angleRotated ) + xyRotationPoint.y;
return { x: xRotated, y: yRotated };
} );
return result;
}
// Let's rotate the points by 180 degrees around the
// center point of (50,50).
let result = rotateAboutZ( center, points, Math.PI );
console.log( result );
The following code generates random points(x,y) and then for each point it splits the canvas (one square) into four. With the next point in the iteration it searches for the square where the point is located and splits it into four smaller squares - up to a certain square size.
The problem is it is very fast to run in Chrome and extremely slow in Ps (for 11k points it takes 2 seconds in Chrome and 30 minutes in Ps! For 1k points it takes around 10 secs in Ps.
Is there any better rewriting to this? btw, Ps doesn't support ES5
var squares = [];
var canvaswidth = app.activeDocument.width.as("px");
var canvasheight = app.activeDocument.height.as("px");
squares.push([{
x: 0,
y: 0
}, {
x: canvaswidth,
y: 0
}, {
x: canvaswidth,
y: canvasheight
}, {
x: 0,
y: canvasheight
}])
vertices = [];
for (i = 0; i < 8000; i++) {
vertices.push({
x: Math.floor(Math.random() * canvaswidth),
y: Math.floor(Math.random() * canvasheight)
})
}
var t0 = new Date().getTime();
var minsquaresize = 24;
for (v = 0; v < vertices.length; v++) {
if (v > 0 && Math.abs(vertices[v].x - vertices[v - 1].x) > minsquaresize && Math.abs(vertices[v].y - vertices[v - 1].y) > minsquaresize) {
r = 2;
for (s = 0; s < squares.length; s++) {
var squares_s = squares[s];
if (squares_s != undefined && vertices[v].x >= squares_s[0].x && vertices[v].x <= squares_s[2].x && vertices[v].y >= squares_s[0].y && vertices[v].y <= squares_s[2].y && squares_s[1].x - squares_s[0].x > minsquaresize && squares_s[3].y - squares_s[0].y > minsquaresize) {
var s1p1 = {
x: Math.round(squares_s[0].x),
y: Math.round(squares_s[0].y)
};
var s1p2 = {
x: Math.round((squares_s[0].x + squares_s[1].x) / 2),
y: Math.round((squares_s[0].y + squares_s[1].y) / 2)
};
var s1p3 = {
x: Math.round(((squares_s[1].x - squares_s[0].x) / r) + squares_s[0].x),
y: Math.round(((squares_s[3].y - squares_s[0].y) / r) + squares_s[0].y)
}
var s1p4 = {
x: (squares_s[0].x + squares_s[3].x) / 2,
y: Math.round((squares_s[0].y + squares_s[3].y) / 2)
}
var s2p2 = {
x: squares_s[1].x,
y: squares_s[1].y
}
var s2p3 = {
x: Math.round((squares_s[1].x + squares_s[2].x) / 2),
y: Math.round((squares_s[1].y + squares_s[2].y) / 2)
}
var s3p3 = {
x: squares_s[2].x,
y: squares_s[2].y
}
var s3p4 = {
x: Math.round((squares_s[2].x + squares_s[3].x) / 2),
y: Math.round(Math.round((squares_s[2].y + squares_s[3].y) / 2))
}
var s4p4 = {
x: squares_s[3].x,
y: squares_s[3].y
}
//alert(s4p4.y)
delete squares[s];
squares.push([s1p1, s1p2, s1p3, s1p4])
squares.push([s1p2, s2p2, s2p3, s1p3])
squares.push([s1p3, s2p3, s3p3, s3p4])
squares.push([s1p4, s1p3, s3p4, s4p4])
break;
}
}
}
}
var t1 = new Date().getTime() - t0;
alert("time: "+t1)
Managed a significant performance increase by looping the squares in reverse.
So normally it was:
for(vertices length, v++){
for(squares length, s++){
if vertex is within square then delete square from square array, split square into 4 equal squares and add them to array
}
}
Vertices are collected from a path, so vertex 4 will probably be close to vertex 3 so probably in the area of the last squares created from vertex 3 - in the end of the squares array. So:
for(var s = squares.length; s--;){...}
This works much faster (maybe 10 times). Strange that it is also faster with randomly placed vertices.
I want to make a function that gives me a random point near the edges of a rectangle from a point. This is what I came up with so far, but I have absolutely no idea why it is not working.
function Point(x, y) {
this.x = x;
this.y = y;
}
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomPointNearEdges(rectPos, width, height, border) {
var point = new Point(rectPos.x, rectPos.y);
if (randomNumber(0, 1) == 0) {
point.x = randomNumber(rectPos.x, rectPos.x + border);
if (randomNumber(0, 1) == 0) {
point.y = randomNumber(rectPos.y, rectPos.y + border);
}
else {
point.y = randomNumber(rectPos.y + height, (rectPos.y + height) + border);
}
}
else {
point.y = randomNumber(rectPos.y, rectPos.y + border);
if (randomNumber(0, 1) == 0) {
point.y = randomNumber(rectPos.x, rectPos.x + border);
}
else {
point.y = randomNumber(rectPos.x + height, (rectPos.x + width) + border);
}
}
return point;
};
window.onload = function() {
canvas = document.getElementById("canvas");
canvas.width = 700;
canvas.height = 700;
var ctx = canvas.getContext("2d");
ctx.strokeRect(130, 130, 500, 500);
for (var i = 0; i < 30; i++) {
var point = getRandomPointNearEdges(new Point(130, 130), 500, 500, 100);
ctx.fillRect(point.x, point.y, 2, 2);
}
};
<canvas id="canvas"></canvas>
Just to clarify, the black region in this 'Not to scale' diagram is where I want to allow the point to generate. The width / height of that black region is the border property in the code snippet.
Why is my function not working?
Random with even distribution.
Just to point out that the answer by SimpleJ is statistical flawed with the distribution of random locations having a bias to the corners and then to the shorter sides, even though they cover much less area.
The ideal random location should be spread equally over the area in question, if the height of the box is less than the width then there is less chance of the the sides getting a point.
The example below provides a much faster and a much better distribution. I have added the given answers solution as well so you can compare.
The function that gets a random pos. The arguments x,y top left inside edge of rectangle, w,h inside width and height of the rectangle minDist, maxDist the min and max dist the random point can be from the inside edge of the box. You can also use negative values have the points outside the rectangle. Note that the distances are always from the inside edge of the box. The values are also floored when return (can easily be remove and still works)
function randomPointNearRect(x, y, w, h, minDist, maxDist) {
const dist = (Math.random() * (maxDist - minDist) + minDist) | 0;
x += dist;
y += dist;
w -= dist * 2
h -= dist * 2
if (Math.random() < w / (w + h)) { // top bottom
x = Math.random() * w + x;
y = Math.random() < 0.5 ? y : y + h -1;
} else {
y = Math.random() * h + y;
x = Math.random() < 0.5 ? x: x + w -1;
}
return [x | 0, y | 0];
}
Note there is a slight bias to the inside of the box. It can be removed with a little calculus with the bias rate of change f'(x) = 8*x 8 pixels per pixel inward and the anti derivative f(x)=4*(x**2) + c would directly relate to the distribution. Where x is dist from edge and c is related to perimeter length
Example to compare
The example has two canvases. Many random points are drawn. click the top canvas to add more points. Note how the bottom canvas sides and corners get darker due to the bias of the random points.
const ctx = canvas.getContext("2d");
canvas.onclick = ()=>{
getRandomPointsForBox(200, box,4, 18);
getRandomPoints(200);
}
const edgeClear = 30;
var box = {
x: edgeClear,
y: edgeClear,
w: canvas.width - edgeClear * 2,
h: canvas.height - edgeClear * 2,
edge: 4,
}
function drawBox(box) {
ctx.fillRect(box.x, box.y, box.w, box.h);
ctx.clearRect(box.x + box.edge, box.y + box.edge, box.w - box.edge * 2, box.h - box.edge * 2);
}
function drawPixel(x, y) {
ctx.fillRect(x, y, 1, 1);
}
function getRandomPointsForBox(count, box, min, max) {
min += box.edge;
max += box.edge;
while (count--) {
const [x, y] = randomPointNearRect(box.x, box.y, box.w, box.h, min, max);
drawPixel(x, y);
}
}
drawBox(box);
getRandomPointsForBox(200, box,4, 18);
ctx.font = "18px arial"
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText("Click to add more random points.",canvas.width / 2, canvas.height / 2);
function randomPointNearRect(x, y, w, h, minDist, maxDist) {
const dist = (Math.random() * (maxDist - minDist) + minDist) | 0;
x += dist;
y += dist;
w -= dist * 2
h -= dist * 2
if (Math.random() < w / (w + h)) { // top bottom
x = Math.random() * w + x;
y = Math.random() < 0.5 ? y : y + h -1;
} else {
y = Math.random() * h + y;
x = Math.random() < 0.5 ? x: x + w -1;
}
return [x | 0, y | 0];
}
/* The following is from the answer provided by SimpleJ https://stackoverflow.com/a/49581326/3877726 */
const ctx1 = canvas1.getContext('2d');
const rect = {
x: box.x, y: box.y,
width: box.w, height: box.h,
};
drawRect(rect);
ctx1.font = "18px arial"
ctx1.textAlign = "center"
ctx1.textBaseline = "middle"
ctx1.fillText("SimpleJ's method.",canvas1.width / 2, canvas1.height / 2);
ctx1.fillText("Note density of sides and corners.",canvas1.width / 2, canvas1.height / 2 + 20);
function getRandomPoints(count) {
while (count--) {
drawPoint(randomPointInRect(sample(rects)));
}
}
var rects = getBorderRects(rect, 10);
function getBorderRects(rect, distance) {
const { x, y, width, height } = rect;
return [
{x: x, y: y, width: width, height: distance}, // top
{x: x, y: y + height - distance, width: width, height: distance}, // bottom
{x: x, y: y, width: distance, height: height}, // left
{x: x + width - distance, y: y, width: distance, height: height}, // right
];
}
function sample(array) {
return array[Math.floor(Math.random() * array.length)];
}
function randomPointInRect({x, y, width, height}) {
return {
x: x + (Math.random() * width),
y: y + (Math.random() * height),
};
}
function drawRect({x, y, width, height}) {
ctx1.strokeRect(x, y, width, height);
}
function drawPoint({x, y}) {
ctx1.fillRect(x, y, 1,1);
}
getRandomPoints(200);
<canvas id="canvas" width="500" height="200"></canvas>
<canvas id="canvas1" width="500" height="200"></canvas>
If you think about the problem of getting a random point near an edge as getting a random point in one of four edge rectangles, this problem becomes much easier to break down:
Get edge rectangles.
Pick a random edge rectangle.
Generate a random point in the edge rectangle.
To generate edge rectangles, we need a max distance (how far from the edge can the point be?):
function getBorderRects(rect, distance) {
const { x, y, width, height } = rect;
return [
{x: x, y: y, width: width, height: distance}, // top
{x: x, y: y + height - distance, width: width, height: distance}, // bottom
{x: x, y: y, width: distance, height: height}, // left
{x: x + width - distance, y: y, width: distance, height: height}, // right
];
}
To pick a random rectangle from our array of edge rectangles, we can define a sample function:
function sample(array) {
return array[Math.floor(Math.random() * array.length)];
}
Then to pick a random point in a rectangle, we just need some Math.random:
function randomPointInRect({x, y, width, height}) {
return {
x: x + (Math.random() * width),
y: y + (Math.random() * height),
};
}
And putting everything together:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const rect = {
x: 10, y: 20,
width: 300, height: 200,
};
drawRect(rect);
drawPoint(
randomPointInRect(
sample(
getBorderRects(rect, 10)
)
)
);
function getBorderRects(rect, distance) {
const { x, y, width, height } = rect;
return [
{x: x, y: y, width: width, height: distance}, // top
{x: x, y: y + height - distance, width: width, height: distance}, // bottom
{x: x, y: y, width: distance, height: height}, // left
{x: x + width - distance, y: y, width: distance, height: height}, // right
];
}
function sample(array) {
return array[Math.floor(Math.random() * array.length)];
}
function randomPointInRect({x, y, width, height}) {
return {
x: x + (Math.random() * width),
y: y + (Math.random() * height),
};
}
function drawRect({x, y, width, height}) {
context.strokeRect(x, y, width, height);
}
function drawPoint({x, y}) {
context.arc(x, y, 1, 0, Math.PI * 2);
context.fill();
}
<canvas width="500" height="500"/>
For anybody here like me, looking for a short, simple solution, this post is closest I found that is not talking trigonometry .. An while what I came up with might not directly be a solution to OPs problem, maybe someone will find this useful..
The approach is fairly simple.
Math.random() a number between 0 & 800. Make use of modulus and divide what's left by 200 to get a random side and axis point. Push the random side all the way, assign the random value to the other axis and yeah, that's about it .. here's an ex:
let rndm = Math.floor(Math.random()*800-1);
let offset = rndm % 200;
let side = (rndm - offset) / 200; // 0:top 1:right 2:btm 3:left
let y = side % 2 > 0 ? offset+1 : 100 * side ;
let x = side % 2 < 1 ? offset+1 : 100 * (side - 1) ;
point.y = y - 100;
point.x = x - 100;
In my case, I needed both negative and positive values with an origin point.
And if you want to spawn a point inside a border, just do another random number spanning the width of the border.
Just remember to adjust the corners.
offset += rndmBorder * 2; // creates an inward line in the corners
point.x = x - 100 + rndmBorder; // still keeping the origin point nice and center
_____________
|\_________/| <-// inward line
| | | |
| | | |
All I was in need for is to offset some letters .. and most of what I found seemed like overkill .. This actually works fairly well, hope it helps.