I'm trying to change shadowCameraFov property of spot light object using onChange method, here is the code:
gui.add(controls, 'spotCameraFov', 30, 270).onChange( function (e) {
spotLight.shadowCameraFov = e;
console.log(e);
});
I can see that the value is changing ( from the console.log ), but it actually cause no effect on the spot light shadowCameraFov. Any suggestion on how can I make it works ?
Here is the pattern you need to follow:
gui.add( controls, 'shadowCameraFov', 30, 120 ).onChange( function() {
spotLight.shadowCameraFov = controls.shadowCameraFov;
spotLight.shadowCamera.fov = spotLight.shadowCameraFov;
spotLight.shadowCamera.updateProjectionMatrix();
});
You can see a live example here: http://threejs.org/examples/webgl_shading_physical.html. Controls are on the top right.
three.js r.62
Related
I'm aware of binding a pop-up to ESRI's L.esri.DynamicMapLayer here. The following code below is successful.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindPopup(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
var obj = featureCollection.features[0].properties;
var val = obj['Pixel Value'];
var lat = featureCollection.features[0].geometry.coordinates[1];
var lon = featureCollection.features[0].geometry.coordinates[0];
new L.responsivePopup({
autoPanPadding: [10, 10],
closeButton: true,
autoPan: false
}).setContent(parseFloat(val).toFixed(2)).setLatLng([lat, lon]).openOn(map);
}
});
}
});
But rather than a click response I am wondering as to whether you can mouseover using bindTooltip instead on a dynamic map. I've looked at the documentation for L.esri.DynamicMapLayer which says it is an extension of L.ImageOverlay. But perhaps there is an issue outlined here that I'm not fully understanding. Maybe it is not even related.
Aside, I've been testing multiple variations of even the simplest code to get things to work below but have been unsuccessful. Perhaps because this is asynchronous behavior it isn't possible. Looking for any guidance and/or explanation(s). Very novice programmer and much obliged for expertise.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindTooltip(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
new L.tooltip({
sticky: true
}).setContent('blah').setLatLng([lat,lng]).openOn(map);
}
});
}
});
Serendipitously, I have been working on a different problem, and one of the byproducts of that problem may come in handy for you.
Your primary issue is the asynchronous nature of the click event. If you open up your map (the first jsfiddle in your comment), open your dev tools network tab, and start clicking around, you will see a new network request made for every click. That's how a lot of esri query functions work - they need to query the server and check the database for the value you want at the given latlng. If you tried to attach that same behavior to a mousemove event, you'll trigger a huge number of network requests and you'll overload the browser - bad news.
One solution of what you can do, and its a lot more work, is to read the pixel data under the cursor of the image returned from the esri image service. If you know the exact rgb value of the pixel under the cursor, and you know what value that rgb value corresponds to in the map legend, you can achieve your result.
Here is a working example
And Here is the codesandbox source code. Don't be afraid to hit refresh, CSB is little wonky in the way it transpiles the modules.
What is happening here? Let's look step by step:
On map events like load, zoomend, moveend, a specialized function is fetching the same image that L.esri.dynamicMapLayer does, using something called EsriImageRequest, which is a class I wrote that reuses a lot of esri-leaflet's internal logic:
map.on("load moveend zoomend resize", applyImage);
const flashFloodImageRequest = new EsriImageRequest({
url: layer_url,
f: "image",
sublayer: "3",
});
function applyImage() {
flashFloodImageRequest
.fetchImage([map.getBounds()], map.getZoom())
.then((image) => {
//do something with the image
});
}
An instance of EsriImageRequest has the fetchImage method, which takes an array of L.LatLngBounds and a map zoom level, and returns an image - the same image that your dynamicMapLayer displays on the map.
EsriImageRequest is probably extra code that you don't need, but I happen to have just run into this issue. I wrote this because my app runs on a nodejs server, and I don't have a map instance with an L.esri.dynamicMapLayer. As a simpler alternative, you can target the leaflet DOM <img> element that shows your dynamicMapLayer, use that as your image source that we'll need in step 2. You will have to set up a listener on the src attribute of that element, and run the applyImage in that listener. If you're not familiar with how leaflet manages the DOM, look into your elements tab in the inspector, and you can find the <img> element here:
I'd recommend doing it that way, and not the way my example shows. Like I said, I happened to have just been working on a sort-of related issue.
Earlier in the code, I had set up a canvas, and using the css position, pointer-events, and opacity properties, it lays exactly over the map, but is set to take no interaction (I gave it a small amount of opacity in the example, but you'd probably want to set opacity to 0). In the applyImage function, the image we got is written to that canvas:
// earlier...
const mapContainer = document.getElementById("leafletMapid");
const canvas = document.getElementById("mycanvas");
const height = mapContainer.getBoundingClientRect().height;
const width = mapContainer.getBoundingClientRect().width;
canvas.height = height;
canvas.width = width;
const ctx = canvas.getContext("2d");
// inside applyImage .then:
.then((image) => {
image.crossOrigin = "*";
ctx.drawImage(image, 0, 0, width, height);
});
Now we have an invisible canvas who's pixel content is exactly the same as the dynamicMapLayer's.
Now we can listen to the map's mousemove event, and get the mouse's rgba pixel value from the canvas we created. If you read into my other question, you can see how I got the array of legend values, and how I'm using that array to map the pixel's rgba value back to the legend's value for that color. We can use the legend's value for that pixel, and set the popup content to that value.
map.on("mousemove", (e) => {
// get xy position on cavnas of the latlng
const { x, y } = map.latLngToContainerPoint(e.latlng);
// get the pixeldata for that xy position
const pixelData = ctx.getImageData(x, y, 1, 1);
const [R, G, B, A] = pixelData.data;
const rgbvalue = { R, G, B, A };
// get the value of that pixel according to the layer's legend
const value = legend.find((symbol) =>
compareObjectWithTolerance(symbol.rgbvalue, rgbvalue, 5)
);
// open the popup if its not already open
if (!popup.isOpen()) {
popup.setLatLng(e.latlng);
popup.openOn(map);
}
// set the position of the popup to the mouse cursor
popup.setLatLng(e.latlng);
// set the value of the popup content to the value you got from the legend
popup.setContent(`Value: ${value?.label || "unknown"}`);
});
As you can see, I'm also setting the latlng of the popup to wherever the mouse is. With closeButton: false in the popup options, it behaves much like a tooltip. I tried getting it to work with a proper L.tooltip, but I was having some trouble myself. This seems to create the same effect.
Sorry if this was a long answer. There are many ways to adapt / improve my code sample, but this should get you started.
I am working on an A-Frame project that has small textures in the distance that are hard to see with the default three.js texture anisotropy setting of 1. How can you set this value on a texture in an A-Frame project?
Here is a revised component incorporating the suggested event listener and maximum anisotropy value.
AFRAME.registerComponent('max-anisotropy', {
dependencies: ['material', 'geometry'],
init: function () {
// wait until this material is ready ...
this.el.addEventListener('materialtextureloaded', () => {
let myMap = this.el.getObject3D('mesh').material.map;
myMap.anisotropy = this.el.sceneEl.renderer.capabilities.getMaxAnisotropy();
myMap.needsUpdate = true;
})
}
})
Here's a very simple A-Frame component that I created to solve this problem:
AFRAME.registerComponent('af', {
dependencies: ['material', 'geometry'],
init: function () {
// wait until this material is ready ...
setTimeout(() => {
this.el.getObject3D('mesh').material.map.anisotropy = 4;
this.el.getObject3D('mesh').material.map.needsUpdate = true;
}, 5000);
},
})
I cheat here by using setTimeout as I see an error without adding a delay to implementing the anisotropy setting change.
UPDATE: It might be possible to listen for the "materialtextureloaded" event per this example from the A-Frame docs: https://aframe.io/docs/1.0.0/components/visible.html#hiding-entity-until-texture-loaded
I'm using Hammer.js to look for horizontal pan gestures, I've devised a simple function to clicks a button when panned left or right. It works okay, except the vertical scroll doesn't do anything on a touch device, or it's really glitchy and weird.
Here's the function:
var panelSliderPan = function() {
// Pan options
myOptions = {
// possible option
};
var myElement = document.querySelector('.scroll__inner'),
mc = new Hammer.Manager(myElement);
mc.add(new Hammer.Pan(myOptions));
// Pan control
var panIt = function(e) {
// I'm checking the direction here, my common sense says it shouldn't
// affect the vertical gestures, but it blocks them somehow
// 2 means it's left pan
if (e.direction === 2) {
$('.controls__btn--next').click();
// 4 == right
} else if (e.direction === 4) {
$('.controls__btn--prev').click();
}
};
// Call it
mc.on("panstart", function(e) {
panIt(e);
});
};
I've tried to add a horizontal direction to the recognizer but it didn't really help (not sure if I did it even right):
mc = new Hammer.Manager(myElement, {
recognizers: [
[Hammer.Pan,{ direction: Hammer.DIRECTION_HORIZONTAL }],
]
});
Thanks!
Try setting the touch-action property to auto.
mc = new Hammer.Manager(myElement, {
touchAction: 'auto',
recognizers: [
[Hammer.Pan,{ direction: Hammer.DIRECTION_HORIZONTAL }],
]
});
From the hammer.js docs:
When you set the touchAction to auto it doesnt prevent any defaults, and Hammer would probably break. You have to call preventDefault manually to fix this. You should only use this if you know what you're doing.
User patforna is correct. You need to adjust the touch-action property. This will fix scrolling not working when you have hammer bound on a big element in mobile.
You create a Hammer instance like so
var h = new Hammer(options.contentEl, {
touchAction : 'auto'
});
I was working on a pull to refresh feature, so I need the pan event.
Add the recognizers.
h.get( 'pan' ).set({
direction : Hammer.DIRECTION_VERTICAL,
});
h.on('panstart pandown panup panend', eventHandler);
Inside the eventhandler, you'd look at the event that was triggered and manually call on event.preventDefault() when you require it. This is applicable for hammer 2.0.6.
For anyone who's looking the pull to refresh code was taken from - https://github.com/apeatling/web-pull-to-refresh
My problem was that vertical scroll was toggling a sidebar that was supposed to show/hide on horizontal pan/swipe. After looking at the event details, I realized that Hammer probably triggers panleft and panright event based on X delta and doesn't consider Y delta, so my quick solution was to check the pan direction in my handler:
this.$data.$hammer.on('panleft', (e) => {
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
return;
}
this.isVisible = true;
});
I was stuck on this for several days. Hope this will fix your problem.
mc = new Hammer(myElement, {
inputClass: Hammer.SUPPORT_POINTER_EVENTS ? Hammer.PointerEventInput : Hammer.TouchInput,
touchAction: 'auto',
});
When the relevant gesture is triggered, we applied a css class to the element, that would set the touch-action to none.
mc.on('panmove panstart', event => {
mc.addClass('is-dragging');
}
);
.is-dragging {
touch-action: none !important;
}
Hammer 2.x does not support vertical swipe/pan. Documentation says:
Notes:
When calling Hammer() to create a simple instance, the pan and swipe recognizers are configured to only detect horizontal gestures
You can however use older 1.1.x version, which supports vertical gestures
——
Clarification: this refers to a ‘simple instance’ which is when you don’t pass in any recognizer configuration as the second parameter. In other words these are the defaults but can (and usually should) be overridden.
I'm loading quite large 3d models using BinaryLoader (up to 6MB), and I would like to display progress information.
Using loader.showStatus was already a 1st step (now my app displays "Loading" while the model is loading), but I would like to show which percentage of the model has already been loaded.
Reading the code for BinaryLoader I found the 'showProgress' flag, but if I set it to true, I get multiple errors like this one:
Uncaught Error: InvalidStateError: DOM Exception 11
xhr.onreadystatechange
at line BinaryLoader.js:99, which corresponds to:
callbackProgress( { total: length, loaded: xhr.responseText.length } );
I also tried configuring loader.onLoadProgress, but I wasn't able to figure out how to do it...
thanks in advance!
3 years later I ran into the same problem as you. Here is my solution:
var onLoadComplete= function (geometry) {
model = new THREE.Mesh(geometry, material);
scene.add(model);
//etc
};
var onLoadProgress = function (e) {
var percentage = Math.round((e.loaded / e.total * 100));
jQuery('#some-loader-div').html('Loading ' + percentage + '%')
};
loader.load(file_url, onLoadComplete, onLoadProgress);
Reference: http://definitelytyped.org/docs/threejs--three/classes/three.jsonloader.html#onloadprogress
Hope this helps someone!
exactly copying source code from Pnotify project page, and put in in my page.
all links to jqueryUi and Pnotiy and other relative files were done and correct.
but when hovering on element for showing tooltip, toltip box goes to top-right of page;it's fixed and do not care of cursor position !
can ya help ?
$('span.required').bind({
mouseenter: function() {
var ttText = $(this).siblings('.required').html();
var ttTitle = "it's required";
tooltip = $.pnotify({
pnotify_title: ttTitle,
pnotify_text: ttText,
pnotify_hide: false,
pnotify_closer: false,
pnotify_history: false,
pnotify_animate_speed: 100,
pnotify_opacity: .9,
pnotify_notice_icon: "ui-icon ui-icon-comment",
pnotify_stack: false,
pnotify_after_init: function(pnotify){
pnotify.mouseout(function(){
pnotify.pnotify_remove();
});
},
pnotify_before_open: function(pnotify){
pnotify.pnotify({
pnotify_before_open: null
});
return false;
}
});
tooltip.pnotify_display();
},
mouseleave: function() {
tooltip.pnotify_remove();
}
});
Stacks are how you change the positioning.
Browse to http://pines.sourceforge.net/pnotify/.
Scroll down to "Advanced Demos".
Examine "Examples of custom stacks" bit. Click the buttons there.
View Source on the page to get to the code.
The tooltips do not use stacks like all the other notifications do. Instead you need to set up the position through CSS.
tooltip.get().css({
'top': event.clientY + 12,
'left': event.clientX + 12
});