WebGL: Force canvas to lose context - webgl

I'm working on a scene that will lose context once in a blue moon. I've set up event handlers to respond to these circumstances, and now need to test them.
I have tried gl.getExtension('WEBGL_lose_context').loseContext();, but the following event does not trigger:
var elem = document.querySelector('#canvas');
elem.addEventListener('webglcontextlost', function(e) {
console.log('context lost')
e.preventDefault();
}, false);
Is there a way to manually lose the WebGL context? khronos.org gives a link to some JS utils that can force one to lose context, but the link is dead. Any thoughts others can offer on this question would be very welcome!

Aha, my scene is in Three.js, so I needed to call the loseContext() method on the renderer's context:
renderer.context.getExtension('WEBGL_lose_context').loseContext();

or simply
renderer.forceContextLoss();
[docs]

You can do this like this for any type of canvas.
var canvas = document.getElementById('canvas');
// OR if no Id
// var canvas = document.getElementsByTagName('canvas')[0];
var gl = canvas.getContext('webgl');
canvas.addEventListener('webglcontextlost', function(e) {
console.log(`Lost WebGL context \n${e}`);
e.preventDefault();
}, false);
// Bye bye canvas :)
gl.getExtension('WEBGL_lose_context').loseContext();
Reference: https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_lose_context/loseContext

Related

How can i drag object and drop it inside three.js Mesh?

This is what i have tried yet :
$( ".box" ).draggable({
helper:'clone',
start: function( event, ui ) {
back.material.map = new THREE.Color(0xeeeeee);
back.material.needsUpdate = true;
},
stop: function( event, ui ) {
back.material.map = texture;
back.material.needsUpdate = true;
}
});
$("canvas").droppable({ // here I've selected canvas for dropping element
accept:".box",
drop: function( event, ui ) {
var item = $(ui.draggable).clone();
$(this).addClass("ui-state-highlight").append(item);
hanger1();
}
});
Basically i want to drag element from outside the three.js Scene and drop it inside canvas over a three.js mesh. i am using jQuery UI Draggable and Droppable in which i can only drop element inside html DOM only, but i want to drop it over three.js mesh.
how can i make it possible?
If you can do this. so i will highly appreciate your efforts.
how can i make it possible?
One possible approach you can try it is the usage of CSS2DRenderer like demonstrated in this official example: https://threejs.org/examples/css2d_label
When inspecting the code, you will see that the labels are rendered via HTML/CSS. So when a DOM element is dragged over the canvas and dropped, you wrap it into an instance of CSS2DObject and add it to your scene graph as a child of your target mesh.
If you need to determine which mesh was hovered by the mouse cursor, you can use basic raycasting. Meaning you can compute mouse coordinates in an mouse event listener like so:
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
and then use these coordinates to setup the raycaster like so:
raycaster.setFromCamera( mouse, camera );
The raycaster is then ready for intersection tests with your scenes.

Debugging in WebGL

I am learning WebGL and I can feel that my speed is so slow because I am having a hard time debugging my code. Is there any extension or tool with help of which I can know the value of buffer, attribpointer, matrixes, etc.
I googled and learned about chrome extension spector.js but this does not work with me. I think it supposes to show me frames or context but when I click it shows nothing.
When I click red button after a few seconds it shows:
No frames detected. Try moving the camera or implementing requestAnimationFrame.
Yes, WebGL is hard to debug and I'm not sure anything will make it a whole lot easier. Most bugs are not something a debugger can find that easily. Certain bugs like un-renderable textures or buffers on the correct size already get reported by the browser. Other bugs though are usually math bugs, logic bugs, or data bugs. For example there is no easy way to step through a WebGL shader.
In any case, if you want to use spector you need to structure your code to be spector friendly. Spector is looking for frames based on requestAnimationFrame.
So, let's take this example which is the last example from this page.
The code has a main function that looks like this
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
...
}
main();
I changed it to this. I renamed main to init and made it so I pass in the gl context.
function init(gl) {
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
...
}
Then I made a new main that looks like this
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
const startElem = document.querySelector('button');
startElem.addEventListener('click', start, {once: true});
function start() {
// run the initialization in rAF since spector only captures inside rAF events
requestAnimationFrame(() => {
init(gl);
});
// make so more frames so spector has something to look at.
// Note: a normal webgl app would have a rAF loop: https://webglfundamentals.org/webgl/lessons/webgl-animation.html
requestAnimationFrame(() => {});
requestAnimationFrame(() => {});
requestAnimationFrame(() => {});
requestAnimationFrame(() => {});
requestAnimationFrame(() => {});
}
}
main();
And I added a button to my html
<button type="button">start</button>
<canvas id="canvas"></canvas>
The code is the way it is because we need to get a webgl context first or else spector will not notice the canvas (there will be nothing to select). After when turn to turn on spector, and only after that click the start button to run our code. We need to execute our code in a requestAnimationFrame because that is what spector is looking for. It only records WebGL functions between frames.
Whether or not it will help you find any bugs though is another matter.
note that, if you are on Mac, Safari also has a WebGL debugger built in but just like spector it's only designed for frames. It requires you to draw something each frame so this worked
function start() {
// I'm not sure running the init code in a rAF is important in Safari but it worked
requestAnimationFrame(() => {
init(gl);
});
// by default safari tries to capture 3 frames so let's give it some frames
// Note: a normal webgl app would have a rAF loop: https://webglfundamentals.org/webgl/lessons/webgl-animation.html
requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
requestAnimationFrame(() => { gl.clear(gl.COLOR_BUFFER_BIT); });
}
Another thing you can do is use a helper to call gl.getError after every WebGL function. Here's a script you can use
<script src="https://greggman.github.io/webgl-helpers/webgl-gl-error-check.js"></script>
You can either download it or you can just include it via the link above. Example (open the javascript console to see th error)
const gl = document.createElement('canvas').getContext('webgl');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.vertexAttribPointer(0, 1, gl.BYE, false, 0, 0);
<script src="https://greggman.github.io/webgl-helpers/webgl-gl-error-check.js"></script>

Open layer 3 click on layer best practice

I am new to OL.
I need to implement a logic that should happen if I pressed on a specific item in a specific layer.
I have to state that I'm not writing a project from scratch, and I actually inherited a very basic but complex system.
The system gets the layers from MapGuide 2.5.
This is how the map is initiated:
var map = new ol.Map({
loadTilesWhileInteracting:true,
layers: this.layers,
target: this._element[0],
controls: controls,
interactions: interactions,
view: view
});
view.fit(that.extent, map.getSize());
I tried adding a select interaction - it didn't work (my promise was never called).
var select_interaction = new ol.interaction.Select();
select_interaction.getFeatures().on("add", function (e) {
var feature = e.element; //the feature selected
});
map.addInteraction(select_interaction);
I tried:
map.on('click', function (evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function (feature, layer) {
debugger;
this.log("fff")
});
});
In this case, the promise works but I get no features.
EDIT:
I also tried:
var feature = map.forEachLayerAtPixel(evt.pixel,
function (feature, layer) {..}
but I get the exception:
uncaught security error: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data. ol.js:341
How can I do this?
Thanks,
Ido
I was getting the same error while I was trying to loop through layers at pixel via forEachLayerAtPixel method.
Thanks to #kagelos comment, I managed to solve the problem simply by filtering the layers and looping through only Vector layers.
Layer types are roughly separated in two categories in OL. Image layers (tiles etc.) and vector layers. You have to inspect the contents of this.layers that you pass as an argument in the constructor of the map and see what type of layers it contains. You can only interact directly with vector type layers.
Here is the final code:
Map.forEachLayerAtPixel( cursorPosition, function ( _layer ) {
// you will get the vector layer here
}, this, function ( _layer ) {
if ( _layer instanceof ol.layer.Vector ) {
return true;
}
});

Use of 'drawPolygonGeometry()' on postCompose event with vectorContext

I'm trying to draw a Circle around every kind of geometry (could be every ol.geom type: point,polygon etc.) in an event called on 'postcompose'. The purpose of this is to create an animation when a certain feature is selected.
listenerKeys.push(map.on('postcompose',
goog.bind(this.draw_, this, data)));
this.draw_ = function(data, postComposeRender){
var extent = feature.getGeometry().getExtent();
var flashGeom = new ol.geom.Polygon.fromExtent(extent);
var vectorContext = postComposeRender.vectorContext;
...//ANIMATION CODE TO GET THE RADIUS WITH THE ELAPSED TIME
var imageStyle = this.getStyleSquare_(radius, opacity);
vectorContext.setImageStyle(imageStyle);
vectorContext.drawPolygonGeometry(flashGeom, null);
}
The method
drawPolygonGeometry( {ol.geom.Polygon} , {ol.feature} )
is not working. However, it works when I use the method
drawPointGeometry({ol.geom.Point}, {ol.feature} )
Even if the type of flashGeom is
ol.geom.Polygon that I just built from an extent. I don't want to use this method because extents from polygons could be received and it animates for every point of the polygon...
Finally, after analyzing the way drawPolygonGeometry in OL3 works in the source code, I realized that I need to to apply the style with this method before :
vectorContext.setFillStrokeStyle(imageStyle.getFill(),
imageStyle.getStroke());
DrawPointGeometry and drawPolygonGeometry are not using the same style instance.

mapping texture to a sphere in ThreeJS

When mapping texture to a sphere in ThreeJS with I am loosing the sphere. Instead I am getting consol errors that read --
Uncaught TypeError: Cannot call method 'add' of undefined index.html:28
and
Cross-origin image load denied by Cross-Origin Resource Sharing policy.
The image is the correct size and resolution since it works in another instance where I was attempting texture mapping, however it is not working here. It must be a problem with how I am applying the map. I am new to both javascript and ThreeJS, so bear with me. Thank you.
<body>
<div id="container"></div>
<script src="javascript/mrdoob-three.js-ad419d4/build/three.js"></script>
<script defer="defer">
// renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 500;
// material
var material = new THREE.MeshLambertMaterial({
map: THREE.ImageUtils.loadTexture('images/physicalworldmapcolor.jpg')
});
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0x000044);
scene.add(ambientLight);
// directional lighting
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// scene
var scene = new THREE.Scene();
// sphere
// the first argument of THREE.SphereGeometry is the radius,
// the second argument is the segmentsWidth
// the third argument is the segmentsHeight.
var sphere = new THREE.Mesh(new THREE.SphereGeometry(150, 70, 50),
new THREE.MeshNormalMaterial(material));
sphere.overdraw = true;
scene.add(sphere);
renderer.render(scene, camera);
</script>
There are MANY errors with the code you provided.
Just check a basic example at:
https://github.com/mrdoob/three.js/
your script is missing a render loop, your camera is not added to the scene, the Three.Scene() constructor is called after already adding objects to "scene". Then you have MeshNormalMaterial() and wrapped in there another material. This won't work , just do Three.Mesh(SphereGeometry(...), material). "overdraw" is a material propery so you will have to do sphere.material.overdraw. But i think overdraw only affects stuff for the CSS canvas renderer and i am not sure if it has any meaning if you use WebGLRenderer
Concerning the error with cross-origin, read up here:
https://github.com/mrdoob/three.js/wiki/How-to-run-things-locally

Resources