I've a problem with object3d selection on my scene.
I use the three.js r61 and the domevent.js libraries.
I have added the THREE.Object3D Patch, but doesn't seems to works.
I have a list of objects that are mesh or object3d.
When i try to capture an object click, all works fine if object is a mesh but not if it is a group.
Each object is added with objects.push(object); // mesh or object3d
Here is the code :
// Objects selection
THREE.Object3D._threexDomEvent.camera(camera);
THREE.Object3D._threexDomEvent.domElement(renderer.domElement);
for ( object in objects )
{
// Bind depends on object type
var bindControl = objects[object];
console.log('bind ' + bindControl.name);
bindControl.on('click', function(object3d) {
console.log('ok');
seletedObject = object3d.target;
console.log('selected : ' + object3d.target.name);
});
...
}
So the domevent works fine for a mesh that is directly in the scene, but if i click on a mesh that is in a group, as the group only is in the objects list, the click event is not fired.
I don't know how to make my object3d to be clickable.
If someone has an idea, it will be greatly appreciated.
Thanks
The code of domevents.js doesn't works with groups because of two reasons:
To detect intersection with or without the descendants you need to pass the second parameter as true on the intersectObjects function:
var intersects = this._raycaster.intersectObjects( boundObjs, true);
The event is attached to the parent element (the object3d or THREE.Group object) and when intersection is detected it returns the descendant object with the nearest intersection. So the _bound function will ty to get context of child element instead of parent. You have to change the behaviour of _objectCtxGet function:
THREEx.DomEvents.prototype._objectCtxGet = function(object3d){
if(typeof object3d._3xDomEvent == 'undefined' && typeof object3d.parent != 'undefined')
{
return object3d.parent._3xDomEvent;
}
return object3d._3xDomEvent;
}
So i found a solution don't know if it is the better one but it works at least with a group of 2 mesh.
Here is the code i modified in the ThreeX.domevent.js (in THREEx.DomEvent.prototype._onEvent)
var objectCtx = this._objectCtxGet(object3d);
var objectParent = object3d.parent;
while ( typeof(objectCtx) == 'undefined' && objectParent )
{
objectCtx = this._objectCtxGet(objectParent);
objectParent = objectParent.parent;
}
This is beacause the object clicked is not the one that is in the objects list, so we must found the parent that is in the list, and it work with this code.
Be aware that i've only tested it for my case and tested it in only some case, so take it carrefully.
Bye
Related
I'm trying to make a scene where there's a "hole in the wall".
This requires a plane with square removed, and then a material applied to the plane with the following properties:
Invisible to the camera
Hides any other objects from being rendered that are behind it
There's an example of this with three.js here, but how can I do it with the a-frame material syntax?
The "Mask".
Looking at the box-hole example, to create the illusion, Lee creates two boxes.
1) The box which is "in the hole"
2) A slightly bigger invisible box without a top - to cloak the first one. The top is removed to work as a "hole" through which you can see the first box
How it can be done in THREE.js
The cloaking is done by preventing the second box from rendering any color. From Lee's example:
let material = new THREE.MeshBasicMaterial({
colorWrite: false;
})
The docs state, that the flag can be used to create invisible objects hiding others.
How it can be done in a-frame
I'm afraid you can't simply make the "cloak" material in a-frame. The colorWrite property is not exposed in the material component.
What i think the simplest way would be - is creating a cloak component, which will create the second box in THREE.js:
AFRAME.registerComponent('cloak', {
init: function() {
let geometry = new THREE.BoxGeometry(1, 1, 1)
geometry.faces.splice(4, 2) // cut out the top faces
let material = new THREE.MeshBasicMaterial({
colorWrite: false
})
let mesh = new THREE.Mesh(geometry, material)
mesh.scale.set(1.1, 1.1, 1.1)
this.el.object3D.add(mesh)
}
})
and use it like this:
<a-box material="src: myPic.png; side: back;" cloak>
Check it out in this codepen. With a HIRO marker, you should get a hole like this:
Using models, or other objects as "cloaks"
Here we need to apply the colorWrite=false magic to each node/child of the model.
init: function() {
// make sure the model is loaded first
this.el.addEventListener('model-loaded', e=>{
let mesh = this.el.getObject3D('mesh') // grab the mesh
if (mesh === undefined) return; // return if no mesh :(
mesh.traverse(function(node) { // traverse through and apply settings
if (node.isMesh && node.material) { // make sure the element can be a cloak
node.material.colorWrite = false
node.material.needsUpdate = true;
}
});
})
}
Also make sure the cloak is rendered before the elements that needs cloaking:
<a-marker>
<a-entity gltf-model="#wall-with-a-hole" cloak-component></a-entity>
<!-- the other stuff that needs to be cloaked-->
</a-marker
I am trying to make clicking the right mouse button behave like the left mouse button when clicking on objects.
This is the code I have been playing around with:
$(".upper-canvas").bind('contextmenu', function (env) {
canvas.on('mouse:over', function(e) {
canvas.setActiveObject(e.target);
});
return false;
})
But it doesn't behave as I thought it would.
After right clicking on an object, it doesn't select the object, but then it subsequently and continuously selects elements on hovering.
I, perhaps naively, assumed the hover event would only be active one time on right-clicking.
So long as you're only working with objects, this should work:
$(".upper-canvas").bind('contextmenu', function(ev) {
var pointer = canvas.getPointer(ev.originalEvent);
var objects = canvas.getObjects();
for (var i = objects.length - 1; i >= 0; i--) {
if (objects[i].containsPoint(pointer)) {
canvas.setActiveObject(objects[i]);
break;
}
}
if (i < 0) {
canvas.deactivateAll();
}
canvas.renderAll();
ev.preventDefault();
return false;
});
When a user right clicks in the canvas, it gets the (x, y) coordinates of the click. Then it looks through all the objects in the canvas and selects an object if it contains the point. I believe that fabric retains z-order in reverse order in the objects list, so this should respect that since it goes through the list backwards. If the click doesn't select any object, then it will deselect any selected objects. Finally, it prevents default and returns false to prevent the normal right click pop up from occurring.
This should work fairly well with objects, but it probably won't work super well with groups.
How can I get a list of the available contexts on a given canvas? Unfortunately, using getContext on every possible option would not work since https://stackoverflow.com/a/13406681/2054629
So
var possibleContexts = ['2d', 'webgl', 'webgl2', 'experimental-webgl', 'experimental-webgl2', ...];
var availableContexts = possibleContexts.filter(function(c) { return canvas.getContext(c); });
won't work
I'm trying to get a better understanding while at some point in my code webgl context is available and sometime not. I have users for who a newly created canvas will have a webgl context and the one I'm interested in fails. I'm trying to better understand that. So solutions that would create new canvas won't work here...
At the moment I'm doing things like:
var testWebGl = function() {
var canvas = document.createElement('canvas');
return !!(canvas.getContext('webgl') || canvas.getContext('canvas.getContext('webgl')');
};
and then
if (testWebGl())
doSomeWebGlStuff(myCanvas);
And occasionally doSomeWebGlStuff will fails because I can't get a webgl context on myCanvas. So I'd like to get all the available contexts on the exact canvas I'm interested in.
The code won't work since you use the same canvas object for getContext() in all cases. The tests must be performed on different elements than the one you intend to actually use, which means you have to create a new canvas element instance before each getContext() as #tommie showed in his now deleted answer (running the snippet was evidence of that).
For reference, this is what tommie had in his answer with minor modifications/comments:
var possibleContexts = ['bitmaprenderer', '2d', 'webgl', 'webgl2', 'experimental-webgl', 'experimental-webgl2'];
var availableContexts = possibleContexts.filter(function(c) {
var canvas = document.createElement("canvas"); // new canvas element for test only
if (canvas.getContext(c)) {return c}
});
You can alternatively check for the existence of context objects directly:
var availableContexts = [];
if (typeof CanvasRenderingContext2D !== "undefined")
availableContexts.push("2d");
// prefer webgl over experimental-webgl
var hasWebGL = !!document.createElement("canvas").getContext("webgl");
if (typeof WebGLRenderingContext !== "undefined")
availableContexts.push(hasWebGL ? "webgl" : "experimental-webgl");
...etc.
Adjust as needed.
I have created a Scene with ILNumerics that consists of 3 PlotCubes and a Colorbar.
Screenshot of the ILPanel
I wanted to add a method that exports the scene as an image in two ways, the first being the screenshot you see above.
The second export is supposed to only show the centric cube.
I attempted to follow the guidelines of ILNumerics for scene management.
I wrote the following code:
public void ExportAsImage(int resolutionWidth, int resolutionHeight, string path, bool includeSubCubes)
{
using (ILScope.Enter())
{
ILGDIDriver backgroundDriver = new ILGDIDriver(resolutionWidth, resolutionHeight, ilPanel1.Scene);
if (includeSubCubes)
{
// code for standard export here
}
else
{
// setting left and top cube and color bar invisible and
// adjusting main cube size is affecting the ilPanel.Scene
backgroundDriver.Scene.First<ILColorbar>().Visible = false;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _leftCubeTag).Visible = false;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _topCubeTag).Visible = false;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _mainCubeTag).ScreenRect = new RectangleF(0, 0, 1, 1);
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _mainCubeTag).DataScreenRect = new RectangleF.Empty;
backgroundDriver.Scene.Configure();
backgroundDriver.Render();
// save image
backgroundDriver.BackBuffer.Bitmap.Save(path,System.Drawing.Imaging.ImageFormat.Png);
// revert changes done to cubes and color bar
backgroundDriver.Scene.First<ILColorbar>().Visible = true;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _leftCubeTag).Visible = true;
GetElementByTag<ILPlotCube>(backgroundDriver.Scene, _topCubeTag).Visible = true;
AdjustCubeSizes();
}
}
}
Note: "GetElementByTag" is an own implementation to retrieve objects in the ILNumerics Scene.
I first expected that the new driver basically creates a copy of the Scene I can work on, but like the code shows I have to revert all changed after the export or the displayed ilPanel only shows the scene the way I exported it.
Is it possible at all to do export to image without affecting the real Scene? Am I just missing some details?
Regards,
Florian S.
Florian, it does make a copy. But you need to add the interesting part to a new scene. The magic is happening in the Add() method:
var scene4render = new ILScene();
scene4render.Add(oldScene.First<ILPlotCube>(mytag));
// ... configure scene4render here, it will be detached from the original scene
// with the exception of shared buffers.
// ... proceed with rendering
In order to also include + render interactive state changes to the original plot cube (let's say rotations by the users mouse) you'd use something like that:
scene4render.Add(panel.SceneSyncRoot.First<ILPlotCube>(mytag));
Also, I wonder what GetElementByTag does better than ILGroup.First<T>(tag, predicate) or ILGroup.Find<T>(...)?
See also: http://ilnumerics.net/scene-management.html
is there a way to implement a erase method for raphael objects. in this erase method I want to remove specific parts of a particular raphael object. It means that the erase method should work like a real eraser. In the raphael documentation there is a method call Paper.clear(). But we only can delete entire paper.
Any kind of help is appreciated.
The normal way to be to use the remove() method.
http://raphaeljs.com/reference.html#Element.remove
element.remove();
You could eventually create a function that draws shapes with the same color than your paper background-color, on click. Something like this code (jsfiddle at the end of the post). It would cover your content and not erase it, but it would look like it.
var timeoutId = 0;
var cursorX;
var cursorY;
var mouseStillDown = false;
paper = Raphael("paper1","100%","100%");
paper.rect(10,10,100,100).attr({
fill : "black"
});
$("#paper1").mousemove(function(event){
cursorY=event.pageY;
cursorX=event.pageX;
});
function erase() {
if (!mouseStillDown) { return; }
paper.rect(cursorX-25,cursorY-25,50,50).attr({
fill :"white",
stroke : "white"
});
if (mouseStillDown) { setInterval("erase", 100); }
}
$("#paper1").mousedown(function(event) {
mouseStillDown = true;
erase(event.pageX,event.pageY);
});
$("#paper1").mouseup(function(event) {
mouseStillDown = false;
});
Here, each time you click, it creates a white rectangle at your cursor position.
Here's a fiddle of the code : http://jsfiddle.net/c6Xs6/
With a few modifications you could create a menu allowing the user to choose the size and shape of the form you use to "erase".
Something more or less like this : http://jsfiddle.net/8ABe9/
You could also use a div following your cursor to show exactly where the "eraser" would be drawn.
Hope that helped you :)