JavaScript externe class or array visible into onmessage worker - javascript

With p5.js in a first time I cannot draw gradually the Koch snowflake: when a click to perform I see the result of the drawing when the recursion is gone.
so I implemented it with worker thread
As I don't know how to share external var like a class or an array (because if a create a type="module" script it is not ok with the worker.js)
I can arrived to do it with difficulty
'Koch snowflake' is made in recursive mode.
The project is
index.html
main.js
worker.js that contain
(onmessage = function... , and the VonKoch class)
in worker.js it's like
onmessage = function(e) {
console.log('Worker: Message received from main script');
let depth = e.data.depth;
let f = new VonKoch(VonKoch);
};
class VonKoch {
constructor(depth) {
...
}
pencil_advance(){
...
postMessage([this.x, this.y, this.x + dx, this.y + dy, ]);
Surprising ! it can send message from/into the class which is out of onmessage
}
...
}
From the main.js it is like:
function setup() {
noLoop();
createCanvas(800, 400);
if (window.Worker) {
const myWorker = new Worker("worker.js");
let el = {profondeur:profondeur};
myWorker.postMessage(el);
let a=0;
a message has been received so with p5.js it draws a line
myWorker.onmessage = (e) => {
line(e.data[0],e.data[1],e.data[2],e.data[3]);
a++;
};
With that I can see the draws gradually.
Ok, but I want more simple and speed result with an external array[][] of pixel that I can redraw every setInterval( ...; redraw(), 500).
The "main.js" can recognize this array if it is declared and initialized in "worker.js" but when this array is modified in "worker.js" (with VonKoch class), the main.js will see the initial array class without modification. Maybe it is come from "new Worker("worker.js")?
And if I create this array class in the "main.js" it won't be recognized in "worker.js".
The only way to have a "array class" that is recognized in "main.js" is to put it in the "worker.js".
Thank you for your help

Solved.
With localStorage : not ok (see comment)
with indexedDB : not ok (see comment)
The solution is :
in the "worker.js" that perform Koch snowflake, when it does to draw there is a test of time. If the Date.now() is greater than 500 (ms) i send the matrix with post.
So the "main.js" while draw the matrix with p5.js after receiving the message comes from "worker.js".
It is fast
depth of 12

Related

Three.js : Box3 from an object not in scene

I have a problem adding a Bounding Box from an object in a different module. Right now, it is working fine as long as I write everything in my main function, but as soon as I create my function in a different file, and import in in the main file, it's not working anymore.
The error code I get :
Uncaught TypeError: Cannot read properties of undefined (reading 'updateWorldMatrix')
at Box3.expandByObject (three.module.js:4934:10)
at Box3.setFromObject (three.module.js:4852:15)
at camCollision (camColliders.js:68:37)
at HTMLDocument.<anonymous> (World.js:355:7)
camColliders.js being the file I'm trying to put the function in, and World.js my main file.
Here is the function :
function camCollision() {
const camBB = new Box3().setFromObject(camSphereDetector);
const boule1BB = new Box3().setFromObject(boule1Obj);
boule1BB.name = 'first';
const boule2BB = new Box3().setFromObject(boule2Obj);
boule2BB.name = 'second';
const boule3BB = new Box3().setFromObject(boule3Obj);
boule3BB.name = 'third';
const boulesBB = [boule1BB, boule2BB, boule3BB];
boulesBB.forEach((bbs) => {
if (bbs.intersectsBox(camBB)) {
console.log('got it');
}
});
}
document.addEventListener('mouseup', () => {
camCollision();
});
When I'm doing this in a separate file, i'm first importing the objects from another file and they are all Mesh.
I believe the problem is that I can't create the Bounding Boxes in a separate file, because it needs to be added to the scene first, and I'm only adding them in the scene in World.js. Yet, the error is leading me to line 68, the variable for 'boule1BB', which is weird because 'camBB' should have the problem first ?
Here is how I'm creating the Box3 (these are just copying some GLTF objects position and size, cause I can't manage to get a Box3 from it) :
const boule1Obj = new Mesh(
new SphereGeometry(2, 32, 16),
new MeshBasicMaterial({ color: 'red', transparent: true, opacity: 0 }),
);
boule1Obj.position.set(10, -3.5, 0);
Then, I would like to know, if I got the problem right : is there a way to create a Box3 in a different js file, from an object that is not added to the scene yet (even if it should when the function is being called) ? With a different way than 'setFromObject' maybe ? Or am I not understanding the real problem.
For a bit of context, the aim is to provide some informations about each model when the user clicks on it, and I'm planning on putting the informations as '.name' for each Mesh corresponding to a model. So I don't want to write all this stuff in the main file, but rather on a separate one.
I hope this is clear enough, and I've given enough content for a solution to be found. I couldn't find anyone else having this problem. If you need anything else, please tell me !
Thanks already for your help !
I believe the problem is that I can't create the Bounding Boxes in a separate file, because it needs to be added to the scene in World.js.
Not so. Since a constructed THREE.Mesh has a shape with extents (from its geometry) and a transform (by default, translated to the origin, with no scaling or rotation), Three.js can and will determine a bounding box from that information as though the mesh were in the scene. I've posted a demo of this on CodePen.
Nor should defining the object in one file and referencing it another make any difference, as long as the object is in scope and initialized at the time it's bound to.
Here, I suspect that you're assigning boule1Obj, boule2Obj, and boule3Obj in World.js. In that case, the imported function is being hoisted before the variables are assigned, and the function is seeingbinding to them as unassignedundefined.
Try changing camCollision() to accept the bouleXObjs as arguments.
function camCollision(...objs) {
const camBB = new Box3().setFromObject(camSphereDetector);
for(let i = 0; i < objs.length; i++) {
const objBB = new Box3().setFromGeometry(objs[i]);
objBB.name = `Bounding Box ${i + 1}`;
if(objBB.intersectsBox(camBB)) {
console.log("Got it!");
}
}
}
And then call it as
document.addEventListener("mouseup", () => {
camCollision(boule1Obj, boule2Obj, boule3Obj);
});

Mutiple Meyda analyzers not possible: library issue or problem in my implementation?

Ok, I'm using Meyda, a library for extracting audio features, in a Electron project. To handle everything related to audio in this project I implemented an Audio() class. Summarizing, I get the audio track, splitt it in left and right channels and merge it again. For each channel, there will be a Meyda analyzer extracting features. A simplified code, that shows only meyda sending data to a spectrogram graph object, would be:
class Audio {
constructor(audioElementID, spectrogramObj) {
const audioContext = new AudioContext();
this.audioElement = document.getElementById(audioElementID);
const track = audioContext.createMediaElementSource(this.audioElement);
const splitter = audioContext.createChannelSplitter(2);
track.connect(splitter);
this.gainNode = {
master: audioContext.createGain(),
left: audioContext.createGain(),
right: audioContext.createGain()
};
splitter.connect(this.gainNode.left, 0);
splitter.connect(this.gainNode.right, 1);
const merger = audioContext.createChannelMerger(2);
this.gainNode.left.connect(merger, 0, 0);
this.gainNode.right.connect(merger, 0, 1);
merger.connect(this.gainNode.master);
this.gainNode.master.connect(audioContext.destination);
// first analyzer
this.analyzerLeft = Meyda.createMeydaAnalyzer({
'audioContext': audioContext,
'source': this.gainNode.left,
'bufferSize': 1024,
'featureExtractors': ['amplitudeSpectrum'],
'callback': features => {
spectrogramObj.left.updatePlot(features.amplitudeSpectrum);
}
});
// second analyzer
this.analyzerRight = Meyda.createMeydaAnalyzer({
'audioContext': audioContext,
'source': this.gainNode.right,
'bufferSize': 1024,
'featureExtractors': ['amplitudeSpectrum'],
'callback': features => {
spectrogramObj.right.updatePlot(features.amplitudeSpectrum);
}
});
}
play() {
this.audioElement.play();
this.analyzerLeft.start();
this.analyzerRight.start();
};
pause() {
this.audioElement.pause();
this.analyzerLeft.stop();
this.analyzerRight.stop();
};
}
module.exports.Audio = Audio;
As you see, I correctly named both analyzer differently. Problem is: only the last analyzer works. It's seems actually that analyzerLeft and analyzerRight are all ponting to the last analyzer created. If I add a third one, named thirdAnalyzer and in the method play() DO NOT write this.thirdAnalyzer.start(), the third one will be started even so, and only it.
Is this a library issue or something related to Class implementation?
From what I can tell it looks like Meyda only allows one MeydaAnalyzer at a time. When you create a new instance of the MeydaAnalyzer with the factory method it receives the Meyda object itself as a second parameter. MeydaAnalyzer does use this object to attach all the values to it. Whenever you create the next MeydaAnalyzer it will simply overwrite the previous values.
I'm not sure if this is a bug or a feature. But since you already filed an issue we will surely find out soon. :-)
In the meantime you can work around this issue by copying the internal reference to the Meyda object directly after you created a new MeydaAnalyzer. This will for example make sure that each instance of the MeydaAnalyzer uses a different ScriptProcessorNode.
this.analyzerLeft = Meyda.createMeydaAnalyzer({
// ...
});
this.analyzerLeft._m = { ...this.analyzerLeft._m };
But keep in mind that this hack uses a private class member of the MeydaAnalyzer class which may or may not disappear in a future version of Meyda.

Porting scriptprocessor based application to audioworklet

Since the old Webaudio scriptprocessor has been deprecated since 2014 and Audioworklets came up in Chrome 64 I decided to give those a try. However I'm having difficulties in porting my application. I'll give 2 examples from a nice article to show my point.
First the scriptprocessor way:
var node = context.createScriptProcessor(1024, 1, 1);
node.onaudioprocess = function (e) {
var output = e.outputBuffer.getChannelData(0);
for (var i = 0; i < output.length; i++) {
output[i] = Math.random();
}
};
node.connect(context.destination);
Another one that fills a buffer and then plays it:
var node = context.createBufferSource(), buffer =
context.createBuffer(1, 4096, context.sampleRate), data = buffer.getChannelData(0);
for (var i = 0; i < 4096; i++) {
data[i] = Math.random();
}
node.buffer = buffer;
node.loop = true;
node.connect(context.destination);
node.start(0);
The big difference between the two is the first one fills the buffer with new data during playback while the second one generates all data beforehand.
Since I generate a lot of data I can't do it beforehand. There's a lot of examples for the Audioworklet, but they all use other nodes, on which one can just run .start(), connect it and it'll start generating audio. I can't wrap my head around a way to do this when I don't have such a method.
So my question basically is how to do the above example in Audioworklet, when the data is generated continuously in the main thread in some array and the playback of that data is happening in the Webaudio thread.
I've been reading about the messageport thing, but I'm not sure that's the way to go either. The examples don't point me into that direction I'd say. What I might need is the proper way to provide the process function in the AudioWorkletProcesser derived class with my own data.
My current scriptprocessor based code is at github, specifically in vgmplay-js-glue.js.
I've been adding some code to the constructor of the VGMPlay_WebAudio class, moving from the examples to the actual result, but as I said, I don't know in which direction to move now.
constructor() {
super();
this.audioWorkletSupport = false;
window.AudioContext = window.AudioContext||window.webkitAudioContext;
this.context = new AudioContext();
this.destination = this.destination || this.context.destination;
this.sampleRate = this.context.sampleRate;
if (this.context.audioWorklet && typeof this.context.audioWorklet.addModule === 'function') {
this.audioWorkletSupport = true;
console.log("Audioworklet support detected, don't use the old scriptprocessor...");
this.context.audioWorklet.addModule('bypass-processor.js').then(() => {
this.oscillator = new OscillatorNode(this.context);
this.bypasser = new AudioWorkletNode(this.context, 'bypass-processor');
this.oscillator.connect(this.bypasser).connect(this.context.destination);
this.oscillator.start();
});
} else {
this.node = this.context.createScriptProcessor(16384, 2, 2);
}
}
So my question basically is how to do the above example in Audioworklet,
For your first example, there is already an AudioWorklet version for it:
https://github.com/GoogleChromeLabs/web-audio-samples/blob/gh-pages/audio-worklet/basic/js/noise-generator.js
I do not recommend the second example (aka buffer stitching), because it creates lots of source nodes and buffers thus it can cause GC which will interfere with the other tasks in the main thread. Also discontinuity can happen at the boundary of two consecutive buffers if the scheduled start time does not fall on the sample. With that said, you won't be able to hear glitch in this specific example because the source material is noise.
when the data is generated continuously in the main thread in some array and the playback of that data is happening in the Webaudio thread.
The first thing you should do is to separate the audio generator from the main thread. The audio generator must run on AudioWorkletGlobalScope. That's the whole purpose of AudioWorklet system - the lower latency and the better audio rendering performance.
In your code,
VGMPlay_WebAudio.generateBuffer() should be called in AudioWorkletProcessor.process() callback to fill the output buffer of the processor. That roughly matches what your onaudioprocess callback does.
I've been reading about the messageport thing, but I'm not sure that's the way to go either. The examples don't point me into that direction I'd say. What I might need is the proper way to provide the process function in the AudioWorkletProcesser derived class with my own data.
I don't think your use case requires MessagePort. I've seen other methods in the code but they really don't do much other than starting and stopping the node. That can be done by connecting/disconnecting AudioWorkletNode in the main thread. No cross-thread messaging necessary.
The code example at the end can be the setup for AudioWorklet. I am well aware that the separation between the setup and the actual audio generation can be tricky, but it will be worth it.
Few questions to you:
How does the game graphics engine send messages to the VGM generator?
Can the VGMPlay class live on the worker thread without any interaction with the main thread? I don't see any interaction in the code except for starting and stopping.
Is XMLHttpRequest essential to the VGMPlay class? Or can that be done somewhere else?

Instanciating an object via DOM element inside setup() on p5

I'm facing a problem where I'm trying to instantiate an object inside the setup function of p5, using a callback to do so:
This is the part of sketch.js relevant to the problem:
var g; //for graph instance
var dropzone; //for the file dropping area
function setup() {
createCanvas(640, 480);
dropzone = select('#dropzone'); //selects the <p> element I'm using as dropzone
dropzone.drop(process_file, unhighlight); //unhighlight is just a function to make the drop area not-highlighted
}
function draw() {
background(200);
if (g) {
g.show();
}else{
msg = "Please, load a file by dropping it in the box above";
text(msg, width/2, height/2);
}
}
// callback being used to process the text file content
function process_file(file){
data = file.data; //gets file data(content)
lines = data.split('\n'); // split the lines
digraph = lines[0] == 1 ? true : false; // is it a digraph?
v = int(lines[1]);
g = Graph(digraph, v); //the class I"m using to instantiate the graph
console.log(g); // <-- says undefined
g.init_graph(); // initialize all vertices
for (var i = 2; i < lines.length; i++) {
edge = lines[i].split(' ');
g.add_edge(edge[0], edge[1], edge[2]);
}
}
I already checked using console.log() and the content of the file is being correctly loaded, and the values are correct in what I was expecting. the Graph() class is in another file, but it is being imported as well as the sketch.js. I also tried to put the script importing at the end of the page, but got the same result, the g is still saying undefined.
What I didn't try is to put the whole code of my Graph class into the sketch.js file, but I will need to put more 15 algorithms on the class later, so the sketch file will grow in a unneeded size. I thought the by declaring g as a global variable I would have no problems with it.
I'm fairly inexperienced with JavaScript, so this is probably a rookie mistake about some kind of loading order, so please, if you answer this question, show me the why it is not working as it is. If there's need of any other piece of code, please let me know.
Thanks in advance.
Looking at this line:
g = Graph(digraph, v);
I think you meant to do this:
g = new Graph(digraph, v);
This creates a new instance and stores a reference to that instance in the g variable.

Cesium - why is scene.pickPositionSupported false

I'm ultimately trying to draw a polygon on top of my house. I can do that.
The problem is that on zoom-out, zoom-in, and rotation (or camera move) the polygon doesn't stick to the top of my house. I received great help from this answer. So, now I'm trying to go through the sample code but there is a lot of Cesium methods and functionality that I need to learn.
The sample code I am trying to follow is located in the gold standard that appears to be baked into the existing camera controller here.
I call testMe with the mousePosition as Cartesian3 and the SceneMode is 3D, so pickGlobe is executed.
Here is my code:
var pickedPosition;
var scratchZoomPickRay = new Cesium.Ray();
var scratchPickCartesian = new Cesium.Cartesian3();
function testMe(mousePosition) {
if (Cesium.defined(scene.globe)) {
if(scene.mode !== Cesium.SceneMode.SCENE2D) {
pickedPosition = pickGlobe(viewer, mousePosition, scratchPickCartesian);
} else {
pickedPosition = camera.getPickRay(mousePosition, scratchZoomPickRay).origin;
}
}
}
var pickGlobeScratchRay = new Cesium.Ray();
var scratchDepthIntersection = new Cesium.Cartesian3();
var scratchRayIntersection = new Cesium.Cartesian3();
function pickGlobe(viewer, mousePosition, result) {
var globe = scene.globe;
var camera = scene.camera;
if (!Cesium.defined(globe)) {
return undefined;
}
var depthIntersection;
if (scene.pickPositionSupported) {
depthIntersection = scene.pickPosition(mousePosition, scratchDepthIntersection);
}
var ray = camera.getPickRay(mousePosition, pickGlobeScratchRay);
var rayIntersection = globe.pick(ray, scene, scratchRayIntersection);
var pickDistance;
if(Cesium.defined(depthIntersection)) {
pickDistance = Cesium.Cartesian3.distance(depthIntersection, camera.positionWC);
} else {
pickDistance = Number.POSITIVE_INFINITY;
}
var rayDistance;
if(Cesium.defined(rayIntersection)) {
rayDistance = Cesium.Cartesian3.distance(rayIntersection, camera.positionWC);
} else {
rayDistance = Number.POSITIVE_INFINITY;
}
var scratchCenterPosition = new Cesium.Cartesian3();
if (pickDistance < rayDistance) {
var cart = Cesium.Cartesian3.clone(depthIntersection, result);
return cart;
}
var cart = Cesium.Cartesian3.clone(rayIntersection, result);
return cart;
}
Here is my problem:
Here is the result:
Here are my questions to get this code working:
1. How do I get the scene.pickPositionSupported set to true? I'm using Chrome on Windows 10. I cannot find in the sample code anything about this and I haven't had much luck with the documentation or Google.
2. Why is rayIntersection not getting set? ray and scene have values and scratchRayIntersection in an empty Cartesian3.
I think if I can get those two statements working, I can probably get the rest of the pickGlobe method working.
WebGLGraphics Report:
I clicked on Get WebGL and the cube is spinning!
Picking positions requires that the underlying WebGL implementation support depth textures, either through the WEBGL_depth_texture or WEBKIT_WEBGL_depth_texture extensions. scene.pickPositionSupported is returning false because this extension is missing. You can verify this by going to http://webglreport.com/ and looking at the list of extensions; I have both of the above listed there. There is nothing you can do in your code itself to make it suddenly return true, it's a reflection of the underlying browser.
That being said, I know for a fact that Chrome supports the depth texture and it works on Windows 10, so this sounds like a likely video card driver issue. I full expect downloading and installing the latest drivers for your system to solve the problem.
As for rayIntersection, from a quick look at your code I only expect it to be defined if the mouse is actually over the globe, which may not always be the case. If you can reduce this to a runnable Sandcastle example, it would be easier for me to debug.
OK. So it turned out that I had a totally messed up Cesium environment. I had to delete it and reinstall it in my project (npm install cesium --save-dev). Then I had to fix a few paths and VOILA! It worked. Thanks to both of you for all your help.

Categories

Resources