I have a 100k+ boxes that are added to a merged geometry. I need to remove some geometries occasionally from this merged geometry. Can I loop over the position attributes in steps of 108 or 72 vertices per cube to pull out the positions of these boxes or does merge also merge vertices?
function blockCubeAlter(grid, blockModel) {
function getGridElevation(n, e, grid) {
var y_grid = Math.floor((grid.metaData.yMax - n) / grid.metaData.yStep) + 1;
var x_grid = Math.floor((e - grid.metaData.xMin) / grid.metaData.xStep);
var array_pos = Math.round(y_grid * (grid.metaData.nCol + 1) + x_grid);
return isNaN(grid.elevations[array_pos]) ? Infinity : grid.elevations[array_pos];
}
var tmpBox = new THREE.BoxBufferGeometry(blockModel.x_step, blockModel.y_step, 2);
var myBlock = scene.getObjectByName('blockModel');
var pointsPerVertex = 3,
vertexPerFace = 4,// this might be 3 triangles?
facePerSide = 1, // this might be 2 triangles per face?
sidePerBox = 6;
var pointsPerCube = pointsPerVertex * vertexPerFace * facePerSide * sidePerBox;
for (var i = 0, j = myBlock.geometry.attributes.position.array.length; i < j; i += pointsPerCube) {
var above = false,
below = false;
for (var k = i; k < i + pointsPerCube; k += pointsPerVertex) {
var n = myBlock.geometry.attributes.position.array[k + 1];
var e = myBlock.geometry.attributes.position.array[k + 0];
var z = myBlock.geometry.attributes.position.array[k + 2];
if (z > getGridElevation(n + WEBGLyTranslate, e + WEBGLxTranslate, grid))
above = true;
else
below = true;
if (above && below) break; // intersect surface
}
if (above) {
if (below) {
var newBoxGeometry = tmpBox.clone();
newBoxGeometry.attributes.position.array = myBlock.geometry.attributes.position.array.slice(i, i + pointsPerCube);
for (var materialGroupIndex = 0, z = myBlock.geometry.groups.length; materialGroupIndex < z; materialGroupIndex++) {
var myGeometryGroup = myBlock.geometry.groups[materialGroupIndex];
if (i >= myGeometryGroup.start && i < myGeometryGroup.start + myGeometryGroup.count) {
var newMaterial = myBlock.material.materials[myGeometryGroup.materialIndex].clone();
var mesh = new THREE.Mesh(newBoxGeometry, newMaterial)
scene.add(mesh);
break;
}
}
}
for (var k = i; k < i + pointsPerCube; k++) {
myBlock.geometry.attributes.position.array[k] = undefined;
}
}
}
myBlock.geometry.attributes.position.needsUpdate = true;
}
I am getting pretty random results. how does merge set the position array, does it merge vertices or just append?
As seen in my comments the number of vertex between the tmpBox and the vertices per box in the merged blockModel were not the same. This change in vertices was because the orginal merge was made from boxGeometry, merged, and then converted to buffer geometry. this was how the merged mesh was converted.
finalGeometry = new THREE.BufferGeometry().fromGeometry(tmpGeometry);
I was able to resolve the issue by creating the tmpBox by using:
var tmpBox = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(blockModel.x_step, blockModel.y_step, 2));
seems odd that
new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(blockModel.x_step, blockModel.y_step, 2)) != new THREE.BoxBufferGeometry(blockModel.x_step, blockModel.y_step, 2);
But they have different number of vertices. I think one is using a 4 vertexed face and the other is using three vertices faces.
Related
I want to check if a point lies within a specific polygon. The polygon is:
polygon= [ [-73.89632720118, 40.8515320489962],
[-73.8964878416508, 40.8512476593594],
[-73.8968799791431, 40.851375925454],
[-73.8967188588015, 40.851660158514],
[-73.89632720118, 40.8515320489962] ]
The points I want to check are:
1 = [40.8515320489962,-73.89632720118]
2 = [40.8512476593594,-73.8964878416508]
3 = [40.851375925454,-73.8968799791431]
4 = [40.851660158514,-73.8967188588015]
5 = [40.8515320489962,-73.89632720118]
How can I tell if each of these points lies within this polygon?
The algorithm below does not work. I don't know why.
pt[lat,long]
function isPointInPoly(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1] < poly[i][1]))
&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
&& (c = !c);
return c;
}
I don't want to use a third party solution such as google maps API or this one https://github.com/mattwilliamson/Google-Maps-Point-in-Polygon.
My attempt is here:
http://jsfiddle.net/nvNNF/2/
There is a project on Github with code: https://github.com/substack/point-in-polygon (MIT license):
function inside(point, vs) {
// ray-casting algorithm based on
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html/pnpoly.html
var x = point[0], y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
Usage:
// array of coordinates of each vertex of the polygon
var polygon = [ [ 1, 1 ], [ 1, 2 ], [ 2, 2 ], [ 2, 1 ] ];
inside([ 1.5, 1.5 ], polygon); // true
The test function is here: https://github.com/substack/point-in-polygon/blob/master/index.js
Note: This code doesn't work reliably when the point is a corner of the polygon or on an edge. There is an improved version here: https://github.com/mikolalysenko/robust-point-in-polygon
Your polygon array looks like coordinates array in GeoJSON polygon structure (read more at https://macwright.org/2015/03/23/geojson-second-bite.html and http://geojson.org).
So maybe you can use libraries which are working with geoJSON data? Look at answer and comments to OP in Is it possible to determine if a GeoJSON point is inside a GeoJSON polygon using JavasScript?
In short, my day was saved by turf (https://github.com/turfjs/turf)
There is also d3 (https://github.com/d3/d3-geo#geoContains) but i had issues with it.
UPD:
I noticed turf is giving inconsistent results when point is on 'edge' of polygon. I created issue and i am waiting for answer from developers.
UPD2:
'Boundary points' issue is resolved by using latest version of turf (i used 3.0.14 instead of 4.6.1). It's all right now.
Here is the function I finally got working. I got it by adopting C code to javascript from here (with explanation).
function checkcheck (x, y, cornersX, cornersY) {
var i, j=cornersX.length-1 ;
var odd = false;
var pX = cornersX;
var pY = cornersY;
for (i=0; i<cornersX.length; i++) {
if ((pY[i]< y && pY[j]>=y || pY[j]< y && pY[i]>=y)
&& (pX[i]<=x || pX[j]<=x)) {
odd ^= (pX[i] + (y-pY[i])*(pX[j]-pX[i])/(pY[j]-pY[i])) < x;
}
j=i;
}
return odd;
}
Where cornersX = array with x or latitude vertices array, cornersY = array with y or longitude array. X, Y - latitude and longitude of tested point.
In my case i did following thing it's working fine for me
function isLatLngInZone(latLngs,lat,lng){
// latlngs = [{"lat":22.281610498720003,"lng":70.77577162868579},{"lat":22.28065743343672,"lng":70.77624369747241},{"lat":22.280860953131217,"lng":70.77672113067706},{"lat":22.281863655593973,"lng":70.7762061465462}];
vertices_y = new Array();
vertices_x = new Array();
longitude_x = lng;
latitude_y = lat;
latLngs = JSON.parse(latLngs);
var r = 0;
var i = 0;
var j = 0;
var c = 0;
var point = 0;
for(r=0; r<latLngs.length; r++){
vertices_y.push(latLngs[r].lat);
vertices_x.push(latLngs[r].lng);
}
points_polygon = vertices_x.length;
for(i = 0, j = points_polygon; i < points_polygon; j = i++){
point = i;
if(point == points_polygon)
point = 0;
if ( ((vertices_y[point] > latitude_y != (vertices_y[j] > latitude_y)) && (longitude_x < (vertices_x[j] - vertices_x[point]) * (latitude_y - vertices_y[point]) / (vertices_y[j] - vertices_y[point]) + vertices_x[point]) ) )
c = !c;
}
return c;
}
I modernised the function from Aaron's answer:
const getIsPointInsidePolygon = (point: number[], vertices: number[][]) => {
const x = point[0]
const y = point[1]
let inside = false
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
const xi = vertices[i][0],
yi = vertices[i][1]
const xj = vertices[j][0],
yj = vertices[j][1]
const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
if (intersect) inside = !inside
}
return inside
}
I'm creating/editing a lot (100s to 1000s) of SVG path elements, with integer coordinates, in real time in response to user input (dragging).
var pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var coords = [[0,0], [1,0], [1,1], [0,1]]; // In real case can be list of 1000s, dynamically generated
var d = '';
for (var i = 0; i < coords.length; ++i) {
d += (i == 0 ? 'M' : 'L') + coords[i][0] + ',' + coords[i][1];
}
d += 'z';
pathElement.setAttributeNS(null, 'd', d);
I can and do pool the path elements, so it minimises creation of objects + garbage in that respect. However, it seems to be that a lot of intermediate strings are created with the repeated use of +=. Also, it seems a bit strange to have the coordinates as numbers, convert them to strings, and then the system has to parse them back into numbers internally.
This seems a bit wasteful, and I fear jank since the above is repeated during dragging for every mousemouse. Can any of the above be avoided?
Context: this is part of a http://projections.charemza.name/
, source at https://github.com/michalc/projections, that can rotate the world map before applying the Mercator projection to it.
Try this:
var pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var coords = [[0,0], [1,0], [1,1], [0,1]]; // In real case can be list of 1000s, dynamically generated
var d = [];
for (var i = 0; i < coords.length; ++i) {
d.push((i == 0 ? 'M' : 'L') + coords[i][0] + ',' + coords[i][1]);
}
d.push('z');
pathElement.setAttributeNS(null, 'd', d.join(''));
There is a method, using Uint8Array and TextDecoder that seems faster than string concatenation in Firefox, but slower than string concatenation in Chrome: https://jsperf.com/integer-coordinates-to-svg-path/1.
Intermediate strings are not created, but it does create a Uint8Array (a view on an a re-useable ArrayBuffer)
You can...
Manually find the ASCII characters from a number
Set the characters in an Uint8Array
Use new TextDecoder.decode(.... to get a Javascript string from the buffer
As below
// Each coord pair is 6 * 2 chars (inc minuses), commas, M or L, and z for path
var maxCoords = 1024 * 5;
var maxChars = maxCoords * (2 + 6 + 1 + 1) + 1
var coordsString = new Uint8Array(maxChars);
var ASCII_ZERO = 48;
var ASCII_MINUS = 45;
var ASCII_M = 77;
var ASCII_L = 76;
var ASCII_z = 122;
var ASCII_comma = 44;
var decoder = new TextDecoder();
var digitsReversed = new Uint8Array(6);
function concatInteger(integer, string, stringOffset) {
var newInteger;
var asciiValue;
var digitValue;
var offset = 0;
if (integer < 0) {
string[stringOffset] = ASCII_MINUS;
++stringOffset;
}
integer = Math.abs(integer);
while (integer > 0 || offset == 0) {
digitValue = integer % 10;
asciiValue = ASCII_ZERO + digitValue;
digitsReversed[offset] = asciiValue;
++offset;
integer = (integer - digitValue) / 10;
}
for (var i = 0; i < offset; ++i) {
string[stringOffset] = digitsReversed[offset - i - 1];
++stringOffset
}
return stringOffset;
}
var pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var coordsStringOffset = 0;
var coords = [[0,0], [1,0], [1,1], [0,1]]; // In real case can be list of 1000s, dynamically generated
for (var i = 0; i < coords.length; ++i) {
coordsString[coordsStringOffset] = (i == 0) ? ASCII_M : ASCII_L;
++coordsStringOffset;
coordsStringOffset = concatInteger(coords[i][0], coordsString, coordsStringOffset);
coordsString[coordsStringOffset] = ASCII_comma
++coordsStringOffset;
coordsStringOffset = concatInteger(coords[i][1], coordsString, coordsStringOffset);
}
coordsString[coordsStringOffset] = ASCII_z;
++coordsStringOffset;
var d = decoder.decode(new Uint8Array(coordsString.buffer, 0, coordsStringOffset));
pathElement.setAttributeNS(null, 'd', d);
So basically, I want to draw a curved average line over a certain amount of points of a time-series line chart. Like this:
I want it to span the entire length of the chart but I can't figure out how to calculate the start and end points because the average would (I think) be a point in the middle of each section. Looking at a stock chart with moving average you can see what I want to acheive:
I calculate the averages first by splitting the data array up into chunks based on a period of time. So if I start with:
[
{ time: 1, value: 2 },
{ time: 2, value: 4 },
{ time: 3, value: 5 },
{ time: 4, value: 7 },
]
I get to:
var averages = [
{
x: 1.5,
y: 3,
},
{
x: 3.5 (the average time)
y: 6 (the average value)
},
]
This is what I've tried where I end up with an incomplete line, one that doesnt start at the beginning of the chart and doesnt stop at the end, but stars and ends inside the chart at the first average time:
ctx.moveTo((averages[0].x), averages[0].y);
for(var i = 0; i < averages.length-1; i ++)
{
var x_mid = (averages[i].x + averages[i+1].x) / 2;
var y_mid = (averages[i].y + averages[i+1].y) / 2;
var cp_x1 = (x_mid + averages[i].x) / 2;
var cp_x2 = (x_mid + averages[i+1].x) / 2;
ctx.quadraticCurveTo(cp_x1, averages[i].y ,x_mid, y_mid);
ctx.quadraticCurveTo(cp_x2, averages[i+1].y ,averages[i+1].x, averages[i+1].y);
}
ctx.stroke();
How would you do this?
To get a moving mean you need to just get the mean of n points either side of the current sample.
For example
// array of data points
const movingMean = []; // the resulting means
const data = [12,345,123,53,134,...,219]; // data with index representing x axis
const sampleSize = 5;
for(var i = sampleSize; i < data.length - sampleSize; i++){
var total = 0;
for(var j = i- sampleSize; j < i + sampleSize; j++){
total += data[j];
}
movingMean[i] = total / (sampleSize * 2);
}
This method does not pull the mean forward giving the most accurate mean for each data point.
The problem with this method is that you do not get a mean for the first n and last n samples, where n is the number of samples either side of the mean.
You can do an alternative that will pull the mean forward a little but by applying a weighted mean you can reduce the bias a little
for(var i = sampleSize; i < data.length + Math.floor(sampleSize / 4); i++){
var total = 0;
var count = 0;
for(var j = sampleSize; j > 0; j --){
var index = i - (sampleSize - j);
if(index < data.length){
total += data[index] * j; // linear weighting
count += j;
}
}
movingMean[i-Math.floor(sampleSize / 4)] = total / count;
}
This method keeps that mean closer to the current sample end.
The example show a random data set and the two types of means plotted over it. Click to get a new plot. The red line is the moving mean and the blue is the weighted mean. Note how the blue line tends to follow the data a little slow.
The green line is a weighted mean that has a sample range 4 times greater than the other two.
// helper functions
const doFor = (count, callback) => {var i = 0; while (i < count) { callback(i ++) } };
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randG = (dis, min, max) => {var r = 0; doFor(dis,()=>r+=rand(min,max)); return r / dis};
function getMinMax(data){
var min = data[0];
var max = data[0];
doFor(data.length - 1, i => {
min = Math.min(min,data[i+1]);
max = Math.max(max,data[i+1]);
});
var range = max-min;
return {min,max,range};
}
function plotData(data,minMax){
ctx.beginPath();
for(var i = 0; i < data.length; i++){
if(data[i] !== undefined){
var y = (data[i] - minMax.min) / minMax.range;
y = y *(ctx.canvas.height - 2) + 1;
ctx.lineTo(i/2,y);
}
}
ctx.stroke();
}
function getMovingMean(data,sampleSize){
const movingMean = []; // the resulting means
for(var i = sampleSize; i < data.length - sampleSize; i++){
var total = 0;
for(var j = i- sampleSize; j < i + sampleSize; j++){
total += data[j];
}
movingMean[i] = total / (sampleSize * 2);
}
return movingMean[i];
}
function getMovingMean(data,sampleSize){
const movingMean = []; // the resulting means
for(var i = sampleSize; i < data.length - sampleSize; i++){
var total = 0;
for(var j = i- sampleSize; j < i + sampleSize; j++){
total += data[j];
}
movingMean[i] = total / (sampleSize * 2);
}
return movingMean;
}
function getWeightedMean(data,sampleSize){
const weightedMean = [];
for(var i = sampleSize; i < data.length+Math.floor(sampleSize/4); i++){
var total = 0;
var count = 0;
for(var j = sampleSize; j > 0; j --){
var index = i - (sampleSize - j);
if(index < data.length){
total += data[index] * j; // linear weighting
count += j;
}
}
weightedMean[i-Math.floor(sampleSize/4)] = total / count;
}
return weightedMean;
}
const dataSize = 1000;
const sampleSize = 50;
canvas.width = dataSize/2;
canvas.height = 200;
const ctx = canvas.getContext("2d");
function displayData(){
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
var dataPoint = 100;
var distribution = Math.floor(rand(1,8));
var movement = rand(2,20);
const data = setOf(dataSize,i => dataPoint += randG(distribution, -movement, movement));
const movingMean = getMovingMean(data, sampleSize);
const weightedMean = getWeightedMean(data, sampleSize*2);
const weightedMean1 = getWeightedMean(data, sampleSize*8);
var minMax = getMinMax(data);
ctx.strokeStyle = "#ccc";
plotData(data,minMax);
ctx.strokeStyle = "#F50";
plotData(movingMean,minMax);
ctx.strokeStyle = "#08F";
plotData(weightedMean,minMax);
ctx.strokeStyle = "#4C0";
plotData(weightedMean1,minMax);
}
displayData();
document.onclick = displayData;
body { font-family : arial; }
.red { color : #F50; }
.blue { color : #0AF; }
.green { color : #4C0; }
canvas { position : absolute; top : 0px; left :130px; }
<canvas id="canvas"></canvas>
<div class="red">Moving mean</div>
<div class="blue">Weighted mean</div>
<div class="green">Wide weighted mean</div>
<div>Click for another sample</div>
Currently, I have a system that creates lockable points by using the flot plotclick event and the item that is created by the event. I store each item in an array initialized like so:
for (var i = 1; i < 5; i++){
lockedPoints["Series " + i] = [];
}
The graph supports three locked points per series. I am using jquery.flot.selection.js to work as a zooming feature. I modify the data when zooming so that the amount of visible points is infinite.
This is the function used to determine the data points to graph:
function createData(kElement, a0Element){
var data = [];
var k;
var a0;
var result;
k = kElement.value;
a0 = a0Element.value;
if (document.getElementById("logCheck").value == "On"){
logarithmic = true;
}
else{
logarithmic = false;
}
for (var i = minX; i <= maxX; i += (maxX - minX) / 1000){
result = a0 * Math.pow(Math.E, (-k * i));
if (logarithmic){
result = Math.log(result);
data.push([i, result]);
}
else{
data.push([i, result]);
}
if (result > maxValue){
maxValue = result;
}
if (result < minValue && result > -400){
minValue = result;
}
}
return data;
}
I use this function to perform the zoom effect:
$(this).bind("plotselected", function(event, ranges){
if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
ranges.xaxis.to = ranges.xaxis.from + 0.00001;
}
if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
ranges.yaxis.to = ranges.yaxis.from + 0.00001;
}
minX = ranges.xaxis.from;
maxX = ranges.xaxis.to;
dataSets = [];
dataSets = chart.getData();
var count = 0;
for (var i = 0; i < 4; i++){
var k = document.getElementById("kValue" + (i + 1));
var a0 = document.getElementById("a0Value" + (i + 1));
var onOff = document.getElementById("switch" + (i + 1));
if (onOff.name == "on"){
dataSets[count].data.push(createData(k, a0));
count++;
}
}
if (ranges.yaxis.from > ranges.yaxis.to){
maxValue = ranges.yaxis.from;
minValue = ranges.yaxis.to;
}
else{
maxValue = ranges.yaxis.to;
minValue = ranges.yaxis.from;
}
options = chart.getOptions();
options.xaxes[0].min = minX;
options.xaxes[0].max = maxX;
options.yaxes[0].min = minValue;
options.yaxes[0].max = maxValue;
if ($("#xAxisLabel").html() == "time (hours)"){ //adjust tickFormatter for different labels
options.xaxis.tickFormatter = function (value) {
return (value / 3600).toFixed(2);
};
}
else if ($("#xAxisLabel").html() == "time (minutes)"){
options.xaxis.tickFormatter = function (value) {
return (value / 60).toFixed(2);
};
}
else{
options.xaxis.tickFormatter = function (value) {
return (value).toFixed(2);
};
}
chart.setData(dataSets);
chart.setupGrid();
chart.draw();
chart.clearSelection(true);
relocateLockedTips(); //not functional. Goal is to make a function that can move tooltips accordingly
});
I append tooltips to the document body with the id of "lockedPoint" + item.dataIndex, and I allow the user to remove a locked point if the plotclick event fits these qualifications:
I check all values of i up to the amount of locked points in each series.
Math.abs(lockedPoints[item.series.label][i].pageX - item.pageX) < 10
&& Math.abs(lockedPoints[item.series.label][i].pageY - item.pageY) < 10
Is there a way that I can update my array of lockedPoints after zooming, so I can reposition my tooltips using pageX and pageY? The pageX and pageY both change when I redraw the plot, so I was wondering if there is some sort of method to determine if a point is highlighted (this would allow me to "relock" the points, storing new pageX and new pageY). I looked into the jquery.flot.js code, and I see they store highlight variables in an array, but the highlights stored are not the same type of object returned by a plotclick event.
Before zooming:
After zooming:
Notice how the tooltip remains in place after zoom.
Any help on this matter would be greatly appreciated. Thanks!
You can do something like this:
function relocateLockedTips(dataSets) {
// get coordinates from graph, replace "graph" with your id
var graphX = $('#graph').offset().left;
var graphY = $('#graph').offset().top;
// loop over all data series
for (var i = 0; i < lockedPoints.length; i++) {
// loop over locked points for this series
for (var j = lockedPoints[i].length -1; j >= 0; j--) {
var point = lockedPoints[i][j];
// if point is out of zoom range, remove it
if (point.datapoint[0] < minX ||
point.datapoint[0] > maxX ||
point.datapoint[1] < minValue ||
point.datapoint[1] > maxValue) {
$('#lockedPoint' + point.dataIndex).remove();
lockedPoints[i].splice(j);
}
else {
// move tooltip to new coordinates
var newCoords = {
left: graphX + dataSets[i].xaxis.p2c(point.datapoint[0]),
top: graphY + dataSets[i].yaxis.p2c(point.datapoint[1])
};
$('#lockedPoint' + point.dataIndex).offset(newCoords);
}
}
}
}
PS: Shouldn't the tooltip id also include the seriesIndex? As it is now the tooltip for a point from another series with the same dataIndex could overwrite an existing tooltip.
How do I generate objects on a map, without them occupying the same space or overlapping on a HTML5 Canvas?
X coordinate is randomly generated, to an extent. I thought checking inside the array to see if it's there already, and the next 20 values after that (to account for the width), with no luck.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var positiony = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var positionx = (Math.random() * 4000) + 500 - (points/100);
var duplicatetest = 21;
for (var d = 0; d < duplicatetest; d++) {
var duplicate = $(jQuery.inArray((positionx + d), platforms));
if (duplicate > 0) {
var duplicateconfirmed = true;
}
}
if (duplicateconfirmed) {
var positionx = positionx + 20;
}
var duplicateconfirmed = false;
platforms[i] = new Platform(positionx,positiony,type);
}
}();
I originally made a cheat fix by having them generate in an area roughly 4000 big, decreasing the odds, but I want to increase the difficulty as the game progresses, by making them appear more together, to make it harder. But then they overlap.
In crude picture form, I want this
....[]....[].....[]..[]..[][]...
not this
......[]...[[]]...[[]]....[]....
I hope that makes sense.
For reference, here is the code before the array check and difficulty, just the cheap distance hack.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var position = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
platforms[i] = new Platform((Math.random() * 4000) + 500,position,type);
}
}();
EDIT 1
after some debugging, duplicate is returning as [object Object] instead of the index number, not sure why though
EDIT 2
the problem is the objects are in the array platforms, and x is in the array object, so how can I search inside again ? , that's why it was failing before.
Thanks to firebug and console.log(platforms);
platforms = [Object { image=img, x=1128, y=260, more...}, Object { image=img, x=1640, y=260, more...} etc
You could implement a while loop that tries to insert an object and silently fails if it collides. Then add a counter and exit the while loop after a desired number of successful objects have been placed. If the objects are close together this loop might run longer so you might also want to give it a maximum life span. Or you could implement a 'is it even possible to place z objects on a map of x and y' to prevent it from running forever.
Here is an example of this (demo):
//Fill an array with 20x20 points at random locations without overlap
var platforms = [],
platformSize = 20,
platformWidth = 200,
platformHeight = 200;
function generatePlatforms(k) {
var placed = 0,
maxAttempts = k*10;
while(placed < k && maxAttempts > 0) {
var x = Math.floor(Math.random()*platformWidth),
y = Math.floor(Math.random()*platformHeight),
available = true;
for(var point in platforms) {
if(Math.abs(point.x-x) < platformSize && Math.abs(point.y-y) < platformSize) {
available = false;
break;
}
}
if(available) {
platforms.push({
x: x,
y: y
});
placed += 1;
}
maxAttempts -= 1;
}
}
generatePlatforms(14);
console.log(platforms);
Here's how you would implement a grid-snapped hash: http://jsfiddle.net/tqFuy/1/
var can = document.getElementById("can"),
ctx = can.getContext('2d'),
wid = can.width,
hei = can.height,
numPlatforms = 14,
platWid = 20,
platHei = 20,
platforms = [],
hash = {};
for(var i = 0; i < numPlatforms; i++){
// get x/y values snapped to platform width/height increments
var posX = Math.floor(Math.random()*(wid-platWid)/platWid)*platWid,
posY = Math.floor(Math.random()*(hei-platHei)/platHei)*platHei;
while (hash[posX + 'x' + posY]){
posX = Math.floor(Math.random()*wid/platWid)*platWid;
posY = Math.floor(Math.random()*hei/platHei)*platHei;
}
hash[posX + 'x' + posY] = 1;
platforms.push(new Platform(/* your arguments */));
}
Note that I'm concatenating the x and y values and using that as the hash key. This is to simplify the check, and is only a feasible solution because we are snapping the x/y coordinates to specific increments. The collision check would be more complicated if we weren't snapping.
For large sets (seems unlikely from your criteria), it'd probably be better to use an exclusion method: Generate an array of all possible positions, then for each "platform", pick an item from the array at random, then remove it from the array. This is similar to how you might go about shuffling a deck of cards.
Edit — One thing to note is that numPlatforms <= (wid*hei)/(platWid*platHei) must evaluate to true, otherwise the while loop will never end.
I found the answer on another question ( Searching for objects in JavaScript arrays ) using this bit of code to search the objects in the array
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
I also ended up rewriting a bunch of the code to speed it up elsewhere and recycle platforms better.
it works, but downside is I have fewer platforms, as it really starts to slow down. In the end this is what I wanted, but its no longer feasible to do it this way.
var platforms = new Array();
var nrOfPlatforms = 7;
platformWidth = 20,
platformHeight = 20;
var positionx = 0;
var positiony = 0;
var arrayneedle = 0;
var duplicatetest = 21;
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
function generatePlatforms(ind){
roughx = Math.round((Math.random() * 2000) + 500);
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var duplicate = false;
for (var d = 0; d < duplicatetest; d++) {
arrayneedle = roughx + d;
var result = search(platforms, arrayneedle);
if (result >= 0) {
duplicate = true;
}
}
if (duplicate = true) {
positionx = roughx + 20;
}
if (duplicate = false) {
positionx = roughx;
}
platforms[ind] = new Platform(positionx,positiony,type);
}
var generatedplatforms = function(){
for (var i = 0; i < nrOfPlatforms; i++) {
generatePlatforms(i);
};
}();
you go big data, generate all possibilities, store each in an array, shuffle the array,
trim the first X items, this is your non heuristic algorithm.