First of all - please do not remove this post.
It's not a duplicate.
I know it covers a problem that was mentioned here multiple times but this time it's not "how to detect collisions" because as you will see later, it's already done. It's more about "how to write" this in as much optimized way as possible, because below detection will be triggered multiple times in a short delay of time.
Here's my fiddle: http://jsfiddle.net/slick/81y70h1f/
I generate random squares and detect if they collide with each other.
HTML is generated using below way. No rocket science:
<?php for ($i=1; $i<=$amount; $i++) { ?>
<div id="square_<?= $i; ?>" class="square" style="top: <?= rand(0, 800); ?>px; left: <?= rand(0, 800); ?>px;">
<div>square_<?= $i; ?></div>
</div>
<?php } ?>
In the fiddle, $amount is set to 16. As you can imagine, the possible amount of unique pair combination is equal to:
In the fiddle you will see that I perform the uniqueness calculation twice. Second time just for squares that don't collide.
var squares_without_collision = $(squares).not(garbage).get();
pairs_cleaned = get_unique_pairs(squares_without_collision);
The pairs_cleaned is my final array when I will perform the secret operation that is not a part of this problem. This array will be always slightly reduced with unnecessary crap.
When I will increase $amount to 100 I will get 4950 possible combination. When I refresh page it still works fine but I can observe the speed drops down. I even didn't try to set it to 200 because I don't want my browser to crash.
Question - is here still any space of the improvement and optimization? Because now I will reveal that these squares will be Google Map markers and my collision calculation will be triggered on events when:
Tiles are loaded
Map is dragged
Zoom is changed
In the final version, instead of changing background from green to red, I will be showing or hiding markers. I'm worried, that with more markers I will do a turtle script. I would like to keep it extra fast.
Ok had a look and you have way over complicated it. No need to find the pairs, you are querying the DOM way to often. You should only touch the DOM once for each element. The garbage array is redundant use a semaphore. Never use each() in time critical code as it is very slow.
Always keep variables in function scope (inside the main function) because leaving them in global scope will half the access speed.
Arrays are slow and should be avoided at all costs. Reuse array items if you can. Always ask do you really need a new array? is there a way not to use an array?
Dont test where not needed. You have some garbage but you retest those squares.
Avoid function calls inside loops of time critical code. Calling a function is CPU intensive it is way better to have code inline.
Avoid indexing into arrays. Reference the array item once and use the reference.
Avoid JQuery unless you have a clear and justified reason. JQuery is VERY slow and encourages excessive DOM manipulation.
Think that's it. Below is your Fiddle modified that will run a lot faster.
$(function () {
var squares = []; // keep arrays in function scope as out side the function
var pairs_cleaned = []; // they are in global scope and run at half the speed.
var x1,y1;
squares = $('.square'); // get the squares
var len = squares.length;
console.log('----- Squares away ' + len + '------');
console.log(squares);
var width = 80+10; // you can do this get the size and padding from the first square
var height = 80+10; // if each square is a different size then you will have to change the code a little
for(var i = 0; i < len; i += 1){ // itterate them. Avoid using Each in time critical code as it is slow
var div = squares[i];
squares[i] = { // replace the existing array with a new object containing all we will need. This reuses the array and avoids overheads when growing an array.
square:div, // save the square. Not sure if you need it?
garbage:false, // flage as not garbage
x: x1 = Number(div.offsetLeft), // get the squares location
y: y1 = Number(div.offsetTop), // and ensure all values are Numbers
b: y1 + height, // I have only included the static height and width.
r: x1 + width,
};
}
var s1,s2;
for (var i = 0; i < len; i++) { // instead of calling the function to get an array of pairs, just pair them on the fly. this avoid a lot of overhead.
s1 = squares[i]; // reference the item once outside the loop rather than many times inside the next loop
for (var j = i + 1; j < len; j++) {
if(!squares[j].garbage){ // ignore garbage
s2 = squares[j];
// do the test inside the loop rather than call a function. This avoids a lot of overhead
if (s1.x > s2.r || s1.y > s2.b || s1.r < s2.x || s1.b < s2.y){ // do test
pairs_cleaned.push([s1,s2]); // if passed save unique pairs
}else{
s2.square.style.backgroundColor = '#ff0040'; // this should not be here is Very very slowwwwwwwww
s2.garbage = true; // garbage
}
}
}
}
console.log('----- all pairs without garbage ------');
console.log(pairs_cleaned);
});
OK. Hope that helps. It's been run and works on chrome. You will need to look at the querying of the elements for position and size but I did not think it important for this example.
There are other optimizations you can do but this should see you to around 1000 squares in realtime if you get rid of the s2.square.style.backgroundColor = '#ff0040'; from the inner loop. It is the slowest part of the whole collision test loop. DOM is death for fast code requirements. Always keep all DOM contact out of critical code sections.
One last thing. To get the best performance always use strict mode, it will give you 20%+ increased performance on most code.
You may consider implementing a simple collision grid for the task. That is, take a conceptual 2D grid spanning over the whole collision field, where each grid cell has a size greater than or equal to the maximum size of the colliding nodes, and bin the center points of each collision node in a data structure representing the grid.
From there, for each given collision node, you only need to check for collisions against other nodes placed in any of the adjacent grid cells to the current collision node's grid cell.
For example:
Say the width and height of your map is 1000px, and the collision nodes are represented in squares of 50x50 pixels. You choose to implement a 100px by 100px grid.
So you would first create a data structure that consists of a 2D array where each cell holds an array that will store collision objects:
var gridSize = { w: 1000, h: 1000 }; // The predefined grid size
var blockSize = { w: 100, h: 100 }; // The predefined block size
var collisionGrid = [];
// Initialize a grid of blockSize blocks to fill the gridSize
var x, y, gridX, gridY;
for (x = 0; x < gridSize.w; x += blockSize.w) {
gridX = x/blockSize.w;
collisionGrid[gridX] = [];
for (y = 0; y < gridSize.h; y += blockSize.h) {
gridY = x/blockSize.h;
collisionGrid[gridX][gridY] = [];
}
}
Then, as you learn about the locations of collision nodes (fetched data from some API, for instance), you would populate the data structure with references to each of the collision nodes according to where it's center point is placed on the grid.
So a square collision node with { x: 726, y:211, w: 50, h:50 } would be placed like this:
var placeNode = function(node) {
var mid = {
x: node.x + node.w/2,
y: node.y + node.h/2
};
var cell = {
x: Math.floor(mid.x/blockSize.w),
y: Math.floor(mid.y/blockSize.h)
};
collisionGrid[cell.x][cell.y].push(node);
};
var node = { x: 726, y:211, w: 50, h:50 } // ...fetched from some API
placeNode(node);
After a few hundred or thousand nodes are placed in the grid (which takes very little overhead for each - just a division or two and pushing a reference to an array), checking for collisions for a given node is greatly reduced, since you only need to check for collisions against nodes in the current node's cell as well as the 8 adjacent cells.
In this example, nodes only within a 300x300px block will be checked against for a given node, but as the collision field size increases and the grid size/collision node sizes decrease, this technique can really shine.
In this blog post, I lightly explained the implementation of this kind of grid for collision for a game I was working on: http://blog.cheesekeg.com/prototype-just-the-basics-v0-2/
One thing to note is that there is a trade off here - when collision nodes move around, their corresponding references to moved from grid cell to grid cell as they travel over the grid. However, in the post I linked above, this fact doesn't cause any noticeable problems with performance when hundreds of collision nodes are moving about the grid.
As Brandon mentioned you are best to create some kind of grid to decrease the number of collisions that you actually detect.
I would suggest using plain javascript rather than jQuery for this if you really want the most performance but here is a jQuery solution I created.
var gridDimensions = {
x: 800,
y: 800
};
var boxDimensions = {
x: 80,
y: 80
};
var hashes = hashSquares($('.square'), gridDimensions, boxDimensions);
function hashSquares($squares, dimensions, squaresDimensions) {
var squaresHash = [];
for (var i = 0; i < Math.floor(dimensions.x / squaresDimensions.x); i++) {
var yHashes = Array(Math.floor(dimensions.y / squaresDimensions.y));
for (var j = 0; j < yHashes.length; j++) {
yHashes[j] = [];
}
squaresHash.push(yHashes);
}
$squares.each(function() {
var $this = $(this);
squaresHash[Math.floor($this.position().left / squaresDimensions.x)][Math.floor($this.position().top / squaresDimensions.y)].push($this);
});
return squaresHash;
}
function checkSameSquare(x, y, hash) {
//if they are both in the same hash square they definitely overlap
if (hash[x][y].length > 1) {
$.each(hash[x][y], function(i, $el) {
//skip the first element
if (i !== 0) {
$el.addClass('collided');
}
});
}
}
function checkSquareBelow(x, y, hash) {
$.each(hash[x][y], function(i, $el) {
$.each(hash[x][y + 1], function(i2, $el2) {
if (detectCollision($el, $el2)) {
$el2.addClass('collided');
}
});
});
}
function checkSquareRight(x, y, hash) {
$.each(hash[x][y], function(i, $el) {
$.each(hash[x + 1][y], function(i2, $el2) {
if (detectCollision($el, $el2)) {
$el2.addClass('collided');
}
});
});
}
function checkSquareDiagonalRightBelow(x, y, hash) {
$.each(hash[x][y], function(i, $el) {
$.each(hash[x + 1][y + 1], function(i2, $el2) {
if (detectCollision($el, $el2)) {
$el2.addClass('collided');
}
});
});
}
function detectCollision($div1, $div2) {
var x1 = $div1.offset().left;
var y1 = $div1.offset().top;
var h1 = $div1.outerHeight(true);
var w1 = $div1.outerWidth(true);
var b1 = y1 + h1;
var r1 = x1 + w1;
var x2 = $div2.offset().left;
var y2 = $div2.offset().top;
var h2 = $div2.outerHeight(true);
var w2 = $div2.outerWidth(true);
var b2 = y2 + h2;
var r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
for (var i = 0; i < hashes.length; i++) {
for (var j = 0; j < hashes[i].length; j++) {
checkSameSquare(j, i, hashes);
if (j < hashes[i].length - 1) {
checkSquareRight(j, i, hashes);
}
if (i < hashes.length - 1) {
checkSquareBelow(j, i, hashes);
}
if (j < hashes[i].length - 1 && i < hashes.length - 1) {
checkSquareDiagonalRightBelow(j, i, hashes);
}
}
}
body {
margin: 10px;
font-family: Arial, sans-serif;
}
#container {
background-color: #cccccc;
height: 880px;
position: relative;
width: 880px;
}
.square {
background-color: lawngreen;
height: 80px;
position: absolute;
width: 80px;
z-index: 10;
}
.square > div {
font-size: 12px;
padding: 5px;
}
.square:hover {
background-color: forestgreen;
z-index: 11;
cursor: pointer;
}
.collided {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<div id="square_1" class="square" style="top: 31px; left: 141px;">
<div>square_1</div>
</div>
<div id="square_2" class="square" style="top: 56px; left: 726px;">
<div>square_2</div>
</div>
<div id="square_3" class="square" style="top: 555px; left: 391px;">
<div>square_3</div>
</div>
<div id="square_4" class="square" style="top: 725px; left: 330px;">
<div>square_4</div>
</div>
<div id="square_5" class="square" style="top: 398px; left: 642px;">
<div>square_5</div>
</div>
<div id="square_6" class="square" style="top: 642px; left: 794px;">
<div>square_6</div>
</div>
<div id="square_7" class="square" style="top: 521px; left: 187px;">
<div>square_7</div>
</div>
<div id="square_8" class="square" style="top: 621px; left: 455px;">
<div>square_8</div>
</div>
<div id="square_9" class="square" style="top: 31px; left: 549px;">
<div>square_9</div>
</div>
<div id="square_10" class="square" style="top: 677px; left: 565px;">
<div>square_10</div>
</div>
<div id="square_11" class="square" style="top: 367px; left: 120px;">
<div>square_11</div>
</div>
<div id="square_12" class="square" style="top: 536px; left: 627px;">
<div>square_12</div>
</div>
<div id="square_13" class="square" style="top: 691px; left: 312px;">
<div>square_13</div>
</div>
<div id="square_14" class="square" style="top: 93px; left: 757px;">
<div>square_14</div>
</div>
<div id="square_15" class="square" style="top: 507px; left: 720px;">
<div>square_15</div>
</div>
<div id="square_16" class="square" style="top: 251px; left: 539px;">
<div>square_16</div>
</div>
</div>
http://jsfiddle.net/81y70h1f/13/
Notice you only have to test collisions against the same square the square immediately to the right to the bottom right and below as all other collisions are already handled as you move along the grid.
Related
So I have elements that are position: absolute and then I use Math.random() to set their left:#random and top:#random position.
However a very weird thing is happening. It should be completely random, thus they should be placed completely randomly. However time and time again, they are placed very closely together. Instead of being spread apart.
however you can clearly see, their positions are indeed random:
Here is the code I use to generate them:
const Clouds = function(props) {
const clouds = []
for (let i = 0; i < props.cloudNum; i++) {
const style = {
position: 'absolute',
height: 50 * props.cloudSize + 'px',
top: Math.random() * 100 + '%',
left: Math.random() * 100 + '%',
}
clouds.push(<Cloud key={i} style={style} />)
}
return <div className={props.side}>{clouds}</div>
}
is there a temporal component to Math.random, and because they are generated in sequence their random numbers are similar?
In fact, although they look like similar numbers they are not (remember that you are multiplying by 100), this means that your space of random numbers goes from 0 to 100 (since the decimals in the drawing barely have value, as is the case that you ask).
Keep in mind that if your space is 100 clouds only generating 13 clouds there is more than a 50% probability that two clouds occupy the same position by the birthday problem.
https://en.wikipedia.org/wiki/Birthday_problem
It's a coincidence that you get similar value. Try as many times as you want with my snippet to test it your own.
Note that my objects are much smaller than yours, not having elements overlapping give a better sense of randomness. IMHO, if you are generating clouds (depends on purposes) it could be better to use perlin noise
const container = document.getElementById('container');
const Clouds = function() {
for (let i = 0; i <10; i++) {
let myCloud = document.createElement("div");
myCloud.style.height = '15px';
myCloud.style.width = '15px';
myCloud.style.position = 'absolute';
myCloud.style.background = '#fff';
myCloud.style.border = '1px solid #ccc';
myCloud.style.left = Math.random()*100+'%';
myCloud.style.top = Math.random()*100+'%';
container.appendChild(myCloud);
}
}
function generate() {
container.innerHTML = '';
Clouds();
}
#container {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: red;
}
button {
position: absolute;
z-index: 999;
}
<div id="container"></div>
<button onClick="generate()">Generate</button>
There's no temporal component - it's just generated by the system. Here's a good thread explaining it. The random algorithm depends on the JavaScript engine (there's a V8 answer in that thread) but the function always produces a floating-point number between 0 and 1 inclusive. It's an incredibly large coincidence that your code yielded two numbers that close.
Edit The following HTML and CSS are just an example, the real use-case involves a complex DOM, and should be generic enough to work on different webpages. Only valid assumption is that all elements are rectangular.
Given the following:
HTML
<div class="a" id="a">
A
</div>
<div class="b">
B
</div>
<div class="c">
C
<div class="d">
D
</div>
</div>
CSS
.a,.b,.c,.d{
border: solid 1px black;
opacity: 0.5;
font-family: arial;
position: absolute;
font-size: 20px;
}
.a{
width:300px;
height:250px;
top:30px;
left:20px;
background:green;
}
.b{
width:300px;
height:145px;
top:10px;
left:20px;
background:blue;
}
.c{
width:150px;
height:300px;
top:30px;
left:60px;
background:red;
}
.d{
margin:10px;
background:yellow;
width:100px;
height:200px
}
produces the following result:
I'm trying to detect the percentage of the "A" DIV that is not obscured by other elements, IE: 25% in the given example.
I've written the following JS (fiddle) that scans the rect area of the "A" DIV and collects the obscuring elements.
let el = document.getElementById("a");
let rect = el.getBoundingClientRect();
let right = rect.x + rect.width;
let bottom = rect.y + rect.height;
let elements = [];
for (let i = rect.x; i < right; i += 10) {
for (let j = rect.y; j < bottom; j += 10) {
let sampled = document.elementFromPoint(i, j);
if (sampled && sampled !== el && elements.indexOf(sampled) === -1) {
elements.push(sampled);
}
}
}
Now I'm trying to find the most efficient way to do the calculations. I tried another approach of scanning the rect pixel by pixel and count all pixels that are not part of the main DIV, however this approach appeared to be slow for an accepted solution.
Any help would be appreciated.
UPDATE
After doing some research, I'm starting to think I will need the sweep-line algorithm, still not exactly sure how to modify the code to fit my problem.
Update 2
When using pointer-events: none;, the document.elementFromPoint approach will not work, so I'm looking for a better solution.
You can use the Intersection Observer to get the visible percentage of an element within a parent..
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
It’s experimental though!
I think i would try different approach.
Assumptions:
assuming all elements are rectangles
assuming all elements have z-index higher than A
I would:
get context element rectangle (that is, the DOM element that is the first parent of both: A element and all of those that might obscure A)
list all its children that could obscure A (so, all but A)
get bounding rectangle for each of them
sort those that have z-index higher than A element by: top, right, bottom & left coordinate
once you have top,right,bottom,left - most coordinates of obscuring elements you can subtract those from your A rectangle coordinates and calculate area
for many potentially obscuring elements you can further optimize by removing sorting step but instead, keep track of top/right/bottom/left -most coordinates as you iterate obscuring elements and update them if currently iterated one is more in either direction.
You could loop over all dom elements and collect the obscuring ones. This would give a complexity of O(n) where n = number of elements instead of O(x * y) where x and y are the pixel width and height of the div, respectively.
let el = document.getElementById("a");
let rect = el.getBoundingClientRect();
let right = rect.x + rect.width;
let bottom = rect.y + rect.height;
let baseDepth = parseFloat(el.style.zIndex);
if (baseDepth === NaN) {
baseDepth = 0;
}
let all = document.getElementsByTagName("*");
let obscuringElements = [];
for (var i=0, max=all.length; i < max; i++) {
let sample = all[i];
if (sample !== el && elements.indexOf(sample) === -1) {
let subRect = sample.getBoundingClientRect();
if (subRect.x <= right && subRect.x + subRect.width >= rect.x
&& subRect.y <= bottom && subRect.y + subRect.height >= rect.y) {
obscuringElements.push(sample);
}
}
}
Now you can use the bounding rects of obscuringElements to figure out how much of el is visible.
Tweaking your initial solution, the idea was to remove the pointer-events style while using the elementFromPoint method.
function addCSS(){
let style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '* {pointer-events: all !important;}';
document.getElementsByTagName('head')[0].appendChild(style);
Array.prototype.filter.call(document.querySelectorAll("[style]"),(node)=>{
return node.style.pointerEvents;
}).forEach((node)=>{
node.style.pointerEvents = "all";
})
}
see this fiddle
Hi I am using a hashmap that allows me to efficiently detect objects in the given coordinates. However it is working perfectly , the problem lies with using the mouse to gather the position of the mouse within the canvas down to the pixel. I have been using the offsetX and offsetY methods for the event to gather some offset but it seems there is an offset I am unaware of and may have something to do with either:
1.using scaling on the canvas , Note: ive tried to fix this by division of the renderscale, this works with everything else so should be fine here.
mouseoffset is not accounting for parts of the page or is missing pixels at a low level (maybe 20) but divided by the render scale thats massive.
3.I am using a cartesian coordinate system to simplify things for the future , so the game map is in cartesian and may have to do with the problem.
I will not be supplying all the code because it is allot of work to go through it all so i will supply the following :
the html/css canvas code
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Game</title>
</head>
<body onload="jsEngine = new JsEngine(24, 24, .1); " >
<div class ="wrapper">
<canvas id="canvas" width="1920" height="1080"></canvas>
</div>
<style>
.wrapper {
position: relative;
width: auto;
height: 900px;
}
.wrapper canvas {
position: absolute;
left: 90px;
top: 50px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 90%;
height: 90%;}
.GUI{
top: -315px;
left: -302px;
position: absolute;
width: 300px;
height: 300px;
background-color: cadetblue;
opacity: .5;
word-wrap: break-word;}
img{
image-rendering: optimize-contrast;
}
</style>
<div id = GUI class = "GUI"></div>
<!-- Libraries -->
<script src="../myapi/JSONE.js"></script>
<script src="../myapi/engine/SpacialHash.js"></script>
</body>
</html>
2.the javascript click function
//Click on objects
let onClick = function(event){
let canvas_ctx = document.getElementById("canvas").getContext("2d");
let canvasOffsetX = canvas_ctx.canvas.width/2;
let canvasOffsetY = canvas_ctx.canvas.height/2;
let mousePosX = event.clientX;
let mousePosY = event.clientY;
let mouseX =jsEngine.cameraFocus.x-canvasOffsetX/jsEngine.renderScale+(mousePosX)/jsEngine.renderScale;
let mouseY = jsEngine.cameraFocus.y+(canvasOffsetY)/jsEngine.renderScale+((-mousePosY)/jsEngine.renderScale);
console.log("sum to",mouseX,mouseY);
//My hashMap to place the mouse coordinates on the game map
let clickPosition = hm.find({x:mouseX,y:mouseY,width:1,height:1});
if(clickPosition.length===1){
let gameObject = jsEngine.gameObjects[clickPosition[0].range.id];
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY);
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
}else if(clickPosition.length>1) {
for (let i = 0; i < clickPosition.length; i++) {
let gameObject = jsEngine.gameObjects[clickPosition[i].range.id];
if (gameObject instanceof PlayerShip|| gameObject instanceof Bullet)
continue;
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY)
}
}
};
// Listeners
//Click on objects
document.getElementById("canvas").addEventListener("click", onClick);
the making of the map and scale :Note: this is done via onPreRender
function drawBackground(canvas_ctx, renderScale, imageResource) {
let img = imageResource.mapBackground;
let mapWidth = 1000000;
let mapHeight= 1000000;
let zoom = 1;
mapWidth *= renderScale / zoom;
mapHeight *= renderScale / zoom;
// Render the Background
canvas_ctx.fillStyle = canvas_ctx.createPattern(img, 'repeat');
canvas_ctx.scale(zoom, zoom);
canvas_ctx.fillRect(-mapWidth / 2, - mapHeight / 2, mapWidth, mapHeight);
//if (jsEngine.cameraFocus.x > 1000000) {}
canvas_ctx.scale(1/zoom, 1/zoom);
}
The rendering method used for playership
renderGameObject(gameObject) {
let x = gameObject.transform.x * this.renderScale;
let y = -(gameObject.transform.y * this.renderScale);
let rotation = Math.radians(gameObject.transform.rotation);
let width = gameObject.transform.width;
width *= this.renderScale;
let height = gameObject.texture.height;
height *= this.renderScale;
// Render the gameObject
this.canvas_ctx.translate(x, y);
this.canvas_ctx.rotate(rotation);
this.canvas_ctx.drawImage(gameObject.texture, 0, 0, width / this.renderScale, height / this.renderScale, // Make sure the image is not cropped
-width/2 , // X
-height/2 , // Y
width, height); // width and height
this.canvas_ctx.rotate(-rotation);
this.canvas_ctx.translate(-x, -y);
}
the issue to solve is to make it so that when you click on any given quadrant of the canvas it will return -+ for top left, -- bottom left , -+ topright, +- bottomright, as well as being applied to the render scale which at the moment is .1 so just divide your mouse and canvas coords like shown above and you should be able to get the same results.
Things to keep in mind :
the jsEngine.cameraFocus is set to the playerships x and y coordinates(which are set to the 0,0 posiiton on the map) (which are also in the middle of the ship)
the top left of the canvas is still 0,0 and ++ is still toward the bottom right so theoretically minusing half the canvas width/height then adding the offsets X and Y. this should be working but at my map coordinate -4000,-4000 i get ~-3620,-3295 and at +4000,+4000 I get 3500,3500. (The reason why the canvas 0,0 is not where the ship is , is to make the ship in the middle of the screen)
If you have questions about anything based on code that needs to be supplied please ask via comment . Please note if you have problems with the format of the code supplied I have nothing to say about it . all I need is the click function working on the canvas model i set up in cartesian format.
ps: jQuery is not a solution its a problem please use vanilla js.
I found out why it was off , my canvas has a offset of 90 px and 50 px as well as the main problem that the canvas is only 90% of its origonal size (also in css). If anyone can give me help for how to adjust to these issues please reply in comment . until then I beleieve I have solved my own issue .
How can i create something like this in QML using javascript?
Actually I know how to create rectangles in QML but want to do something like this. QML canvas can be of any size but whenever QML section is loaded multiple squares are generated with random sizes and colors without overlapping. When I'm trying to do this rectangles are generated in a list form.
I'm a web developer(ruby on rails oriented) but new to such javascript stuff. Any help will be appreciated.
As #ddriver already noticed, the simpliest decision is to loop through all children to find a room to a new rectangle.
Rectangle {
id: container
anchors.fill: parent
property var items: [];
Component {
id: rect
Rectangle {
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
border.width: 1
border.color: "#999"
width: 50
height: 50
}
}
Component.onCompleted: {
var cnt = 50;
for(var i = 0;i < cnt;i ++) {
for(var t = 0;t < 10;t ++) {
var _x = Math.round(Math.random() * (mainWindow.width - 200));
var _y = Math.round(Math.random() * (mainWindow.height - 200));
var _width = Math.round(50 + Math.random() * 150);
var _height = Math.round(50 + Math.random() * 150);
if(checkCoord(_x,_y,_width,_height)) {
var item = rect.createObject(container,{ x: _x, y: _y, width: _width, height: _height });
container.items.push(item);
break;
}
}
}
}
function checkCoord(_x,_y,_width,_height) {
if(container.items.length === 0)
return true;
for(var j = 0;j < container.items.length;j ++) {
var item = container.children[j];
if(!(_x > (item.x+item.width) || (_x+_width) < item.x || _y > (item.y+item.height) || (_y+_height) < item.y))
return false;
}
return true;
}
}
Yes, this is not so wise solution but it still can be improved.
If you want efficiency, it will come at the cost of complexity - you will have to use some space partition algorithm. Otherwise, you could just generate random values until you get enough that are not overlapping.
Checking whether two rectangles overlap is simple - if none of the corners of rectangle B is inside rectangle A, then they don't overlap. A corner/point is inside a rectangle if its x and y values are in the range of the rectangle's x and width and y and height respectively.
In JS Math.random() will give you a number between 0 and 1, so if you want to make a random value for example between 50 and 200, you can do that via Math.random() * 150 + 50.
Have an array, add the initial rectangle value to it, then generate new rectangle values, check if they overlap with those already in the array, if not - add them to the array as well. Once you get enough rectangle values, go ahead and create the actual rectangles. Since all your rectangles are squares, you can only go away with 3 values per square - x, y and size.
I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
The problem I am having is that because it is completely random, sometimes the "characters" appear overlapped with one another. Is there a way to organize them so that they appear in set places in the container and don't overlap when they appear.
Here I have the code that maps the divs to the container..
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom('char' + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(100, function () {
setTimeout(function () {
$('#' + id).fadeOut(100);
window.cont++;
}, 1000);
});
I have a fiddle if it helps.. http://jsfiddle.net/pUwKb/8/
As #aug suggests, you should know where you cannot place things at draw-time, and only place them at valid positions. The easiest way to do this is to keep currently-occupied positions handy to check them against proposed locations.
I suggest something like
// locations of current divs; elements like {x: 10, y: 40}
var boxes = [];
// p point; b box top-left corner; w and h width and height
function inside(p, w, h, b) {
return (p.x >= b.x) && (p.y >= b.y) && (p.x < b.x + w) && (p.y < b.y + h);
}
// a and b box top-left corners; w and h width and height; m is margin
function overlaps(a, b, w, h, m) {
var corners = [a, {x:a.x+w, y:a.y}, {x:a.x, y:a.y+h}, {x:a.x+w, y:a.y+h}];
var bWithMargins = {x:b.x-m, y:b.y-m};
for (var i=0; i<corners.length; i++) {
if (inside(corners[i], bWithMargins, w+2*m, h+2*m) return true;
}
return false;
}
// when placing a new piece
var box;
while (box === undefined) {
box = createRandomPosition(); // returns something like {x: 15, y: 92}
for (var i=0; i<boxes.length; i++) {
if (overlaps(box, boxes[i], boxwidth, boxheight, margin)) {
box = undefined;
break;
}
}
}
boxes.push(box);
Warning: untested code, beware the typos.
The basic idea you will have to implement is that when a random coordinate is chosen, theoretically you SHOULD know the boundaries of what is not permissible and your program should know not to choose those places (whether you find an algorithm or way of simply disregarding those ranges or your program constantly checks to make sure that the number chosen isn't within the boundary is up to you. the latter is easier to implement but is a bad way of going about it simply because you are entirely relying on chance).
Let's say for example coordinate 50, 70 is selected. If the picture is 50x50 in size, the range of what is allowed would exclude not only the dimensions of the picture, but also 50px in all directions of the picture so that no overlap may occur.
Hope this helps. If I have time, I might try to code an example but I hope this answers the conceptual aspect of the question if that is what you were having trouble with.
Oh and btw forgot to say really great job on this program. It looks awesome :)
You can approach this problem in at least two ways (these two are popped up in my head).
How about to create a 2 dimensional grid segmentation based on the number of questions, the sizes of the question panel and an array holding the position of each question coordinates and then on each time frame to position randomly these panels on one of the allowed coordinates.
Note: read this article for further information: http://eloquentjavascript.net/chapter8.html
The second approach follow the same principle, but this time to check if the panel overlap the existing panel before you place it on the canvas.
var _grids;
var GRID_SIZE = 20 //a constant holding the panel size;
function createGrids() {
_grids = new Array();
for (var i = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Then on a separate function to create the collision check. I've created a gist for collision check in Actionscript, but you can use the same principle in Javascript too. I've created this gist for inspirational purposes.
Just use a random number which is based on the width of your board and then modulo with the height...
You get a cell which is where you can put the mole.
For the positions the x and y should never change as you have 9 spots lets say where the mole could pop up.
x x x
x x x
x x x
Each cell would be sized based on % rather then pixels and would allow re sizing the screen
1%3 = 1 (x)
3%3 = 0 (y)
Then no overlap is possible.
Once the mole is positioned it can be show or hidden or moved etc based on some extended logic if required.
If want to keep things your way and you just need a quick re-position algorithm... just set the NE to the SW if the X + width >= x of the character you want to check by setting the x = y+height of the item which overlaps. You could also enforce that logic in the drawing routine by caching the last x and ensuring the random number was not < last + width of the item.
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX); if(newX > lastX + characterWidth){ /*needful*/}
There could still however be overlap...
If you wanted to totally eliminate it you would need to keep track of state such as where each x was and then iterate that list to find a new position or position them first and then all them to move about randomly without intersecting which would would be able to control with just padding from that point.
Overall I think it would be easier to just keep X starting at 0 and then and then increment until you are at a X + character width > greater then the width of the board. Then just increase Y by character height and Set X = 0 or character width or some other offset.
newX = 0; newX += characterWidth; if(newX + chracterWidth > boardWidth) newX=0; newY+= characterHeight;
That results in no overlap and having nothing to iterate or keep track of additional to what you do now, the only downside is the pattern of the displayed characters being 'checker board style' or right next to each other (with possible random spacing in between horizontal and vertical placement e.g. you could adjust the padding randomly if you wanted too)
It's the whole random thing in the first place that adds the complexity.
AND I updated your fiddle to prove I eliminated the random and stopped the overlap :)
http://jsfiddle.net/pUwKb/51/