random plancement issue three.js with for-loop - javascript

I am trying to make a function that randomizes the placement of some clouds I imported, but the problem is, it generates the amount of clouds, but in the same position! I randomized the position, but when the code runs I have like 10 clouds in the same position. What can I do? this is the code:
loader.load('/clouds/clouds1/scene.gltf', function (clouds1) {
var clouds1array = []
function addClouds1(){
for (var i = 1; i < 10; i++) {
const clouds1Mesh = clouds1.scene
const clouds1Position = clouds1Mesh.position
clouds1Position.x = Math.random() * 10
clouds1Position.y = Math.random() * 10
clouds1Position.z = (Math.random() - 0.5 ) * 300
clouds1Mesh.scale.setX(0.05)
clouds1Mesh.scale.setY(0.05)
clouds1Mesh.scale.setZ(0.05)
scene.add(clouds1Mesh)
clouds1array.push(clouds1Mesh)
}
}
addClouds1()
})
edit: clouds1.scene structure is this:
I don't know why it has this amount of children, I tried to solve with the answer, but it still does not work. The 3rd child in the end contains the mesh, and I tried using that for it to work, but it says that I cannot use it in the scene.add() function
edit: I solved the problem! I just had to put the for loop outside the load function
for(let i = 0; i < 30; i+= 3)
loader.load('/clouds/clouds1/scene.gltf', function (clouds1) {
const cloud = clouds1.scene
const child1 = clouds1.scene.children[0].children[0].children[0].children[2].children[0]
child1.material = new THREE.MeshStandardMaterial({ emissive: 'white', emissiveIntensity: 0.5})
cloud.scale.set(0.05, 0.05, 0.05)
cloud.position.x = (Math.random() - 0.5) * 500
cloud.position.y = (Math.random() + ((Math.random() + 20 ) + 70))
cloud.position.z = (Math.random() - 1) * 500
cloud.rotation.x = Math.random()
cloud.rotation.y = Math.random()
cloud.rotation.z = Math.random()
scene.add(cloud)
})

The GLTFLoader results, which you have as clouds1 is a generic object, from which you properly extract clouds1.scene. However, clouds1.scene is also a single Scene object. If you have 10 clouds in the GLTF model you loaded, then clouds1.scene will have 10 children, and you will need to loop through them like this:
loader.load('/clouds/clouds1/scene.gltf', function (clouds1) {
var clouds1array = []
const clouds1Children = clouds1.scene.children
for (var i = 1; i < 10; i++) {
const clouds1Mesh = clouds1Children[i]
const clouds1Position = clouds1Mesh.position
clouds1Position.x = Math.random() * 10
clouds1Position.y = Math.random() * 10
clouds1Position.z = (Math.random() - 0.5 ) * 300
clouds1Mesh.scale.setX(0.05)
clouds1Mesh.scale.setY(0.05)
clouds1Mesh.scale.setZ(0.05)
scene.add(clouds1Mesh)
clouds1array.push(clouds1Mesh)
}
})

Related

Why does this javascript while loop hang?

I was trying a very simple thing with javascript, to create a minesweeper grid.
gridsize=9;
//grid initialisation
var grid=Array(gridsize).fill(Array(gridsize).fill(null));
// this comes from <https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range>
function randint(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
nbombs=20;
insertedbombs=0;
while (insertedbombs<nbombs){
rx=randint(0,gridsize-1);
ry=randint(0,gridsize-1);
if (grid[rx][ry] == null){
insertedbombs++;
grid[rx][ry]='b';
}
}
The while loop hangs, both in Chrome and Firefox consoles, all the values of the grid are filled, not just 20, i've no idea why, i guess i have some wrong understanding of javascript language, because the same code in python works.
Working python code:
grid= [[None for i in range(9)]for i in range(9)]
nbombs=20;
insertedbombs=0;
while (insertedbombs< nbombs):
rx=random.randint(0,8)
ry=random.randint(0,8)
if (grid[rx][ry]== None):
grid[rx][ry]='b'
insertedbombs+=1
The issues is that Array#fill doesn't do what you think it does.
In your example you create a SINGLE array Array(gridsize).fill(null) and then you put that array at each index of the outer array. Meaning that your grid actually contains the same array, 9 times over. So when you assign grid[0][0] you actually assign grid[0][0], grid[1][0], grid[2][0], grid[3][0], etc... all at once (ish).
const gridSize = 9;
//grid initialisation
const grid = Array(gridSize).fill(Array(gridSize).fill(null));
console.log(grid[0] === grid[1]);
grid[0][0] = 10;
console.log(grid[0][0], grid[0][0] === grid[1][0]);
What you want to do, is first fill the array with some dummy value, like null. Then map over the array and replace each entry with its own copy of Array(gridsize).fill(null).
const gridSize = 9;
//grid initialisation
const grid = Array(gridSize)
.fill(null)
.map(() => Array(gridSize)
.fill(null)
);
const randInt = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min;
const nbombs = 20;
let insertedbombs = 0;
while (insertedbombs < nbombs){
const rx = randInt(0, gridSize - 1);
const ry = randInt(0, gridSize - 1);
if (grid[rx][ry] === null){
insertedbombs += 1;
grid[rx][ry] = 'b';
}
}
console.log(grid);

Increase array amount without resetting it (Javascript/Canvas)

In my project, I want my array to increase in size every 5 seconds. I tried to use setInterval to call a function to do this, but it resets my array every 5 seconds with an increased amount rather than naturally growing. Is there a way to increase the amount without having to reset the array each time?
These are the functions I am using to call my "plants":
var myPlants = new Array();
var plantSpawn = 0;
function createPlants() {
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants() {
plantSpawn += 5;
for(var i=0; i<plantSpawn; i++){
var rr = Math.round(Math.random() * 150);
var gg = Math.round(Math.random() * 255);
var bb = Math.round(Math.random() * 150);
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'rgba('+rr+','+gg+','+bb+', 1)';
myPlants[i] = plant;
}
}
You are explicitly reseting all the values of the array when you do this:
for(var i=0; i < plantSpawn; i++){... myPlants[i] = plant; ...}
Note that plantSpawn will hold the new array size, so you are looping over all the old indexes plus the new ones and re-assigning the values on they.
So, instead you can add 5 new elements to the array with Array.push() this way:
var myPlants = new Array();
var plantsInc = 5;
function createPlants()
{
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants()
{
// Push N new plants on the array.
for (var i = 0; i < plantsInc; i++)
{
var rr = Math.round(Math.random() * 150);
var gg = Math.round(Math.random() * 255);
var bb = Math.round(Math.random() * 150);
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'rgba('+rr+','+gg+','+bb+', 1)';
// Push a new plant on the array.
myPlants.push(plant);
}
}
And as a suggestion, you can even wrap the logic to create a new plant inside a method, like this:
var myPlants = new Array();
var plantsInc = 5;
function createPlants()
{
reproducePlants();
setInterval(reproducePlants, 5000);
}
function createPlant()
{
var rr = Math.round(Math.random() * 150);
var gg = Math.round(Math.random() * 255);
var bb = Math.round(Math.random() * 150);
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'rgba('+rr+','+gg+','+bb+', 1)';
return plant;
}
function reproducePlants()
{
// Push N new plants on the array.
for (var i = 0; i < plantsInc; i++)
{
myPlants.push(createPlant());
}
}
You are overriding all the existing values so instead of using myPlants[i] = plant; use myPlants.push(plant)
You'll want to modify your function to add a plant onto an existing array, rather than looping over your array and assigning it new values.
if you want to add a new value to your array every 5 seconds, something like this should work:
var myPlants = new Array();
var plantSpawn = 0;
function createPlants() {
reproducePlants();
setInterval(reproducePlants, 5000);
}
function reproducePlants() {
var rr = Math.round(Math.random() * 150);
var gg = Math.round(Math.random() * 255);
var bb = Math.round(Math.random() * 150);
var plant = new Object();
plant.x = Math.random() * canvas.width;
plant.y = Math.random() * canvas.height;
plant.rad = 2;
plant.skin = 'rgba('+rr+','+gg+','+bb+', 1)';
myPlants.push(plant);
}
This just adds a new plant to the end of your array rather than assigning new values to your current array each time you call your function. From here you should be able to put that into a for loop if you wanted to add more plants than 1 at a time. each iteration of the loop will only add a single plan to your array.

Function that clones a group of objects in ThreeJS?

I am trying to create a scene for my class where a group of objects (a model) has to be cloned and displayed in random positions. I created a function for this, but unfortunately, it only transfers the same object from one place to another. I need to add more objects instead of moving the same one, but sadly I cannot find any information about it. I tried to clone, but I failed :/ Later on, I will have to remove these models one by one, so if anybody could give advice on that too, I would appreciate it.
Here's my code:
this.addmodel = function() {
scene.add(model);
model.name = "model-" + scene.children.length;
model.position.x= -20 + Math.round((Math.random() * 40));
model.position.y= Math.round((Math.random() * 5));
model.position.z= -17.5 + Math.round((Math.random() * 35));
this.numOfObjects = scene.children.length;
}
Sample object:
function Model(){
this.mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1),new THREE.MeshBasicMaterial({color:"red"}));
this.addModel = function(){
var newModel = this.mesh.clone();
newModel.name = "model-" + scene.children.length;
newModel.position.x= -20 + Math.round((Math.random() * 40));
newModel.position.y= Math.round((Math.random() * 5));
newModel.position.z= -17.5 + Math.round((Math.random() * 35));
scene.add(newModel);
}
};
then create an instance and call its method:
var cubeModel = new Model();
for(var i = 0; i < 10; i++){
cubeModel.addModel();
}
jsfiddle example

How to reduce a data graph but keeping the extremes

I have a database that has got a month full of datasets in 10min intervals. (So a dataset for every 10min)
Now I want to show that data on three graphs: last 24 hours, last 7 days and last 30 days.
The data looks like this:
{ "data" : 278, "date" : ISODate("2016-08-31T01:51:05.315Z") }
{ "data" : 627, "date" : ISODate("2016-08-31T01:51:06.361Z") }
{ "data" : 146, "date" : ISODate("2016-08-31T01:51:07.938Z") }
// etc
For the 24h graph I simply output the data for the last 24h, that's easy.
For the other graphs I thin the data:
const data = {}; //data from database
let newData = [];
const interval = 7; //for 7 days the interval is 7, for 30 days it's 30
for( let i = 0; i < data.length; i += interval ) {
newData.push( data[ i ] );
};
This works fine but extreme events where data is 0 or differs greatly from the other values average, can be lost depending on what time you search the data. Not thinning out the data however will result in a large sum of data points that are sent over the pipe and have to be processed on the front end. I'd like to avoid that.
Now to my question
How can I reduce the data for a 7 day period while keeping extremes in it? What's the most efficient way here?
Additions:
In essence I think I'm trying to simplify a graph to reduce points but keep the overall shape. (If you look at it from a pure image perspective)
Something like an implementation of Douglas–Peucker algorithm in node?
As you mention in the comments, the Ramer-Douglas-Peucker (RDP) algorithm is used to process data points in 2D figures but you want to use it for graph data where X values are fixed. I modified this Javascript implementation of the algorithm provided by M Oehm to consider only the vertical (Y) distance in the calculations.
On the other hand, data smoothing is often suggested to reduce the number of data points in a graph (see this post by csgillespie).
In order to compare the two methods, I made a small test program. The Reset button creates new test data. An algorithm can be selected and applied to obtain a reduced number of points, separated by the specified interval. In the case of the RDP algorithm however, the resulting points are not evenly spaced. To get the same number of points as for the specified interval, I run the calculations iteratively, adjusting the espilon value each time until the correct number of points is reached.
From my tests, the RDP algorithm gives much better results. The only downside is that the spacing between points varies. I don't think that this can be avoided, given that we want to keep the extreme points which are not evenly distributed in the original data.
Here is the code snippet, which is better seen in Full Page mode:
var svgns = 'http://www.w3.org/2000/svg';
var graph = document.getElementById('graph1');
var grpRawData = document.getElementById('grpRawData');
var grpCalculatedData = document.getElementById('grpCalculatedData');
var btnReset = document.getElementById('btnReset');
var cmbMethod = document.getElementById('cmbMethod');
var btnAddCalculated = document.getElementById('btnAddCalculated');
var btnClearCalculated = document.getElementById('btnClearCalculated');
var data = [];
var calculatedCount = 0;
var colors = ['black', 'red', 'green', 'blue', 'orange', 'purple'];
var getPeriod = function () {
return parseInt(document.getElementById('txtPeriod').value, 10);
};
var clearGroup = function (grp) {
while (grp.lastChild) {
grp.removeChild(grp.lastChild);
}
};
var showPoints = function (grp, pts, markerSize, color) {
var i, point;
for (i = 0; i < pts.length; i++) {
point = pts[i];
var marker = document.createElementNS(svgns, 'circle');
marker.setAttributeNS(null, 'cx', point.x);
marker.setAttributeNS(null, 'cy', point.y);
marker.setAttributeNS(null, 'r', markerSize);
marker.setAttributeNS(null, 'fill', color);
grp.appendChild(marker);
}
};
// Create and display test data
var showRawData = function () {
var i, x, y;
var r = 0;
data = [];
for (i = 1; i < 500; i++) {
x = i;
r += 15.0 * (Math.random() * Math.random() - 0.25);
y = 150 + 30 * Math.sin(x / 200) * Math.sin((x - 37) / 61) + 2 * Math.sin((x - 7) / 11) + r;
data.push({ x: x, y: y });
}
showPoints(grpRawData, data, 1, '#888');
};
// Gaussian kernel smoother
var createGaussianKernelData = function () {
var i, x, y;
var r = 0;
var result = [];
var period = getPeriod();
for (i = Math.floor(period / 2) ; i < data.length; i += period) {
x = data[i].x;
y = gaussianKernel(i);
result.push({ x: x, y: y });
}
return result;
};
var gaussianKernel = function (index) {
var halfRange = Math.floor(getPeriod() / 2);
var distance, factor;
var totalValue = 0;
var totalFactor = 0;
for (i = index - halfRange; i <= index + halfRange; i++) {
if (0 <= i && i < data.length) {
distance = Math.abs(i - index);
factor = Math.exp(-Math.pow(distance, 2));
totalFactor += factor;
totalValue += data[i].y * factor;
}
}
return totalValue / totalFactor;
};
// Ramer-Douglas-Peucker algorithm
var ramerDouglasPeuckerRecursive = function (pts, first, last, eps) {
if (first >= last - 1) {
return [pts[first]];
}
var slope = (pts[last].y - pts[first].y) / (pts[last].x - pts[first].x);
var x0 = pts[first].x;
var y0 = pts[first].y;
var iMax = first;
var max = -1;
var p, dy;
// Calculate vertical distance
for (var i = first + 1; i < last; i++) {
p = pts[i];
y = y0 + slope * (p.x - x0);
dy = Math.abs(p.y - y);
if (dy > max) {
max = dy;
iMax = i;
}
}
if (max < eps) {
return [pts[first]];
}
var p1 = ramerDouglasPeuckerRecursive(pts, first, iMax, eps);
var p2 = ramerDouglasPeuckerRecursive(pts, iMax, last, eps);
return p1.concat(p2);
}
var internalRamerDouglasPeucker = function (pts, eps) {
var p = ramerDouglasPeuckerRecursive(data, 0, pts.length - 1, eps);
return p.concat([pts[pts.length - 1]]);
}
var createRamerDouglasPeuckerData = function () {
var finalPointCount = Math.round(data.length / getPeriod());
var epsilon = getPeriod();
var pts = internalRamerDouglasPeucker(data, epsilon);
var iteration = 0;
// Iterate until the correct number of points is obtained
while (pts.length != finalPointCount && iteration++ < 20) {
epsilon *= Math.sqrt(pts.length / finalPointCount);
pts = internalRamerDouglasPeucker(data, epsilon);
}
return pts;
};
// Event handlers
btnReset.addEventListener('click', function () {
calculatedCount = 0;
clearGroup(grpRawData);
clearGroup(grpCalculatedData);
showRawData();
});
btnClearCalculated.addEventListener('click', function () {
calculatedCount = 0;
clearGroup(grpCalculatedData);
});
btnAddCalculated.addEventListener('click', function () {
switch (cmbMethod.value) {
case "Gaussian":
showPoints(grpCalculatedData, createGaussianKernelData(), 2, colors[calculatedCount++]);
break;
case "RDP":
showPoints(grpCalculatedData, createRamerDouglasPeuckerData(), 2, colors[calculatedCount++]);
return;
}
});
showRawData();
div
{
margin-bottom: 6px;
}
<div>
<button id="btnReset">Reset</button>
<select id="cmbMethod">
<option value="RDP">Ramer-Douglas-Peucker</option>
<option value="Gaussian">Gaussian kernel</option>
</select>
<label for="txtPeriod">Interval: </label>
<input id="txtPeriod" type="text" style="width: 36px;" value="7" />
</div>
<div>
<button id="btnAddCalculated">Add calculated points</button>
<button id="btnClearCalculated">Clear calculated points</button>
</div>
<svg id="svg1" width="765" height="450" viewBox="0 0 510 300">
<g id="graph1" transform="translate(0,300) scale(1,-1)">
<rect width="500" height="300" stroke="black" fill="#eee"></rect>
<g id="grpRawData"></g>
<g id="grpCalculatedData"></g>
</g>
</svg>

Converting Javascript Map function to Swift 2 Map function

I am rewriting this question because my first one was quite vague. I am trying to conver the following javascript function using the map function into Swift 2.
Here is the javascript function.
function compute_correlations(timeseries, test_frequencies, sample_rate)
{
// 2pi * frequency gives the appropriate period to sine.
// timeseries index / sample_rate gives the appropriate time coordinate.
var scale_factor = 2 * Math.PI / sample_rate;
var amplitudes = test_frequencies.map
(
function(f)
{
var frequency = f.frequency;
// Represent a complex number as a length-2 array [ real, imaginary ].
var accumulator = [ 0, 0 ];
for (var t = 0; t < timeseries.length; t++)
{
accumulator[0] += timeseries[t] * Math.cos(scale_factor * frequency * t);
accumulator[1] += timeseries[t] * Math.sin(scale_factor * frequency * t);
}
return accumulator;
}
);
return amplitudes;
}
And here is my Swift function. I am getting an error and am not even sure I am doing it correctly. Error is noted in the code.
func compute_correlations(timeseries:[Double], test_frequencies:[NoteInfo], sample_rate:Double) -> [Double]
{
// 2pi * frequency gives the appropriate period to sine.
// timeseries index / sample_rate gives the appropriate time coordinate.
let scale_factor = 2 * pi / sample_rate;
let amplitudes: [Double] = test_frequencies.map { f in
let frequency = f.getFrequency()
// Represent a complex number as a length-2 array [ real, imaginary ].
var accumulator: [Double] = [ 0.0, 0.0 ]
for (var t = 0; t < timeseries.count; t++)
{
accumulator[0] += timeseries[t] * cos(scale_factor * frequency * Double(t))
accumulator[1] += timeseries[t] * sin(scale_factor * frequency * Double(t))
}
return accumulator //ERROR Cannot convert return expression of type '[Double]' to return type 'Double'
}
return amplitudes;
}
And if needed here is the NoteInfo class
class NoteInfo {
var frequency:Double!
var note_name:String!
init(theFrequency:Double, theNoteName:String){
frequency = theFrequency
note_name = theNoteName
}
func getFrequency()-> Double {
return frequency
}
func getNoteName()-> String {
return note_name
}
}
Here is where I am populating the test_frequencies
for (var i = 0; i < 30; i++)
{
let note_frequency = C2 * pow(2.0, Double(i) / 12.0)
let note_name = notes[i % 12]
let note = NoteInfo(theFrequency: note_frequency, theNoteName: note_name)
test_frequencies.append(note)
}
Your accumulator is a [Double], and so the result of your map becomes [[Double]]. You then try to assign it to a [Double].
You should either declare amplitudes accordingly:
let amplitudes: [[Double]] = test_frequencies.map { f in
or (depending on your needs) return only one of the accumulator fields inside your map, e.g.
return accumulator[0]

Categories

Resources