Three.js: Uniforms value not updating - javascript

I'm using Tween.js to animate the uniforms value of a shader upon clicking a button. Here is what I have:
Shader.uniforms.threshold.needsUpdate = true;
function fadeIn() {
new TWEEN.Tween( Shader.uniforms.threshold )
.to( { value : 0.6 }, 100 )
.start();
}
function fadeOut() {
new TWEEN.Tween( Shader.uniforms.threshold )
.to( { value : 2 }, 100 )
.start();
}
document.getElementById("FadeIn").onclick = function() {
fadeIn();
}
document.getElementById("FadeOut").onclick = function() {
fadeOut();
}
The above does not work. When I try refreshing the page, the value does change but the button click does nothing. Does anyone know the mistake in my implementation? Can Tween.js be used like this or is there a better method? Thanks.

Ok here's how I got it working. It seems changing the value of the "Shader" itself is the cause. The above works when I change the uniforms value of the individual objects. So instead of Shader.uniforms.threshold, I did this:
myObject.material.uniforms.threshold, which works.

Related

Aframe Dynamic Canvas as Texture

I was trying to use a canvas as texture in my aframe project. I found some instructions here. It mentioned:
The texture will automatically refresh itself as the canvas changes.
However, I gave it a try today and the canvas could only be changed / updated in init function. Afterwards the update to canvas cannot be reflected. Here is my implementation:
module.exports = {
'canvas_component': {
schema: {
canvasId: { type: 'string' }
},
init: function () {
this.canvas = document.getElementById(this.data.canvasId);
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = "#FF0000";
this.ctx.fillRect(20, 20, 150, 100);
setTimeout(() => {
this.ctx.fillStyle = "#FFFF00";
this.ctx.fillRect(20, 20, 150, 100);
}, 2000);
}
}
The color change of of the texture was never changed. Is there anything I missed? Thank you so much for any advice.
I could never get it to work with those instructions (never checked out if bug or improper use though), but you can achieve the same with Three.js:
// assuming this is inside an aframe component
init: function() {
// we'll update this manually
this.texture = null
let canvas = document.getElementById("source-canvas");
// wait until the element is ready
this.el.addEventListener('loaded', e => {
// create the texture
this.texture = new THREE.CanvasTexture(canvas);
// get the references neccesary to swap the texture
let mesh = this.el.getObject3D('mesh')
mesh.material.map = this.texture
// if there was a map before, you should dispose it
})
},
tick: function() {
// if the texture is created - update it
if (this.texture) this.texture.needsUpdate = true
}
Check it out in this glitch.
Instead using the tick function, you could update the texture whenever you get any callback from changing the canvas (mouse events, source change).
The docs are out of date, I've made a pull request to update them. Here is the code that shows how to do it now:
src: https://github.com/aframevr/aframe/issues/4878
which points to: https://github.com/aframevr/aframe/blob/b164623dfa0d2548158f4b7da06157497cd4ea29/examples/test/canvas-texture/components/canvas-updater.js
We can quickly turn that into a component like this, for example:
/* global AFRAME */
AFRAME.registerComponent('live-canvas', {
dependencies: ['geometry', 'material'],
schema: {
src: { type: "string", default: "#id"}
},
init() {
if (!document.querySelector(this.data.src)) {
console.error("no such canvas")
return
}
this.el.setAttribute('material',{src:this.data.src})
},
tick() {
var el = this.el;
var material;
material = el.getObject3D('mesh').material;
if (!material.map) {
console.error("no material map")
this.el.removeAttribute('live-canvas')
return;
}
material.map.needsUpdate = true;
}
});
(remember to declate your components before your scene...)
usage:
<body>
<canvas id="example-canvas"></canvas>
<a-scene>
<a-box live-canvas="src:#example-canvas;"></a-box>
</a-scene>
</body>
live glitch code demo here:
https://glitch.com/edit/#!/live-canvas-demo?path=index.html%3A58%3A43
You can of course be more efficient than a tick handler if you just intentionally run the equivalent code manually whenever you update the canvas yourself, if that makes more sense / isn't happening frame-by-frame.

How to lock slider and prevent updating of values with mouse into dat.GUI menu

I try to implement a way to prevent the updating of values with mouse (actually when the three.js animation has started, launched with a click on button).
For the moment, I have the following dat.GUI menu:
Once "start" button is clicked, I would like to prevent user from modifying with mouse the parameters "Rotation x" and "Rotation y".
Here is the concerned part of code for this menu:
// Create GUI
var gui = new dat.GUI({
autoplace: false,
width: 350,
height: 9 * 32 - 1
});
var params = {
GreatCircle : '',
Rotationx : torusRotationInitX,
Rotationy : torusRotationInitY,
StartingVector : '',
ComponentVectorTheta : 15.0,
ComponentVectorPhi : 15.0,
CovariantDerivativeVector : '',
ComponentCovariantDerivativeTheta : 15.0,
ComponentCovariantDerivativePhi : 15.0
};
// Set parameters for GUI
gui.add(params, 'GreatCircle').name('Great Circle ');
controllerRotationx = gui.add(params, 'Rotationx', 0, 2*Math.PI, 0.001).name('Rotation x ');
controllerRotationy = gui.add(params, 'Rotationy', 0, 2*Math.PI, 0.001).name('Rotation y ');
...
When I click on reset button, I call the following function:
// Reset Button
resetButton.onclick = function ResetParameters() {
...
// Reinitialize parameters into gui
params.Rotationx = torusRotationInitX;
params.Rotationy = torusRotationInitY;
for (var i in gui.__controllers) {
gui.__controllers[i].updateDisplay();
}
render();
}
I don't know if there is an option for controller to lock these sliders which usually change their values. Is it possible?
Update 1
Maybe I could wrapper the dat.GUI menu into a div and make this div not clickable, is it a solution?
Update 2
I tried to apply the method used on Method for disabling a button in dat.gui?
Following this solution, I have added the extension into dat.gui, just after:
dat.controllers.FunctionController = (function (Controller, dom, common) {
...
});
The following added code snippet is:
function blockEvent(event)
{
event.stopPropagation();
}
Object.defineProperty(dat.controllers.FunctionController.prototype, "disabled", {
get: function()
{
return this.domElement.hasAttribute("disabled");
},
set: function(value)
{
if (value)
{
this.domElement.setAttribute("disabled", "disabled");
this.domElement.addEventListener("click", blockEvent, true);
}
else
{
this.domElement.removeAttribute("disabled");
this.domElement.removeEventListener("click", blockEvent, true);
}
},
enumerable: true
});
Is extension code well located into dat.GUI source?
Then, I set the property "disabled" into my code to prevent user from sliding "controllerRotationx" with mouse (once start button is pressed):
if (animation)
controllerRotationx.__li.disabled = true;
Unfortunately, my method doesn't work : when animation is started, I can still move the slider contained into "controllerRotationx".
I saw that above link (Method for disabling a button in dat.gui?), this was about a button and not for a slider, does it change anything for my case?
I didn't find an explicit controller for the slider.
I would do this. The slider is not a form element, there's nothing to disable in the traditional w3c sense. Luckily we can use pointer-events and disable it properly as if it were a form element using just public dat.gui properties.
var speeder = menu.add(text, 'speed', -5, 5);
speeder.domElement.style.pointerEvents = "none"
speeder.domElement.style.opacity = .5;
The solution given by #Radio works pretty well. But, with sliders, the slider is a sibling of the text box's DOM element. We need to disable pointer events on the div which contains all the controls (and which is not exposed directly by dat.gui). So,
var speeder = menu.add(text, 'speed', -5, 5);
// disables the text box
speeder.domElement.style.pointerEvents = "none"
// disables all controller elements related to "speeder"
speeder.domElement.parentElement.style.pointerEvents = 'none'
When the Start button is pressed, set:
controllerRotationx.__li.setAttribute( "style", "display: none" );
thanks for tips
on my side i hack the Common controller
so able to chainning.
gui.add(this, '_screenW').disable(true);
Common.extend(controller, {
disable: function disable(v) {
this.domElement.style.pointerEvents = v?"none":"auto";
this.domElement.style.opacity = v?.5:1;
return controller;
},

Animate polygon points snap.svg

it's the first time I tried to animate polygon points with snap.svg and I have the felling I'm doing something wrong there.
Here is my code :
var fdr = Snap('#fdright');
var time1_stp0 = [363.617,262.895, 363.562,367.4, 273.145,315.191, 273.145,315.191];
var time1_stp1 = [363.617,262.895, 363.562,367.4, 273.145,315.191, 273.145,210.688];
var timeline1 = fdr.polygon(time1_stp0).attr({fill:'red',opacity:'0.5'});
timeline1_anim = function(){
timeline1.animate({"points":time1_stp1},3000,mina.linear);
}
timeline1_anim();
As soon as the page is loaded, my polygon disappears (I guess it's because my function is called right after the creation of the polygon). I checked the html, my polygon's still there but here is what i get :
<polygon fill="#ff0000" style="opacity: 0.5;" points="363.617"></polygon>
I don't get what might be the issue, so if someone's got an answer i'll be glad to hear it.
EDIT : I tried to add "toString()" but it's still not working :
timeline1_anim = function(){
timeline1.animate({"points":time1_stp1.toString()},3000,mina.linear);
}
I think there's a bug in Snaps polygon animation.. its listed here There is a patch submitted linked from there.
However, you can get around this easily by animating the array values though if needed.
timeline1_anim = function(){
Snap.animate(time1_stp0, time1_stp1,
function( val ){
timeline1.attr({ points: val })
},
2000);
}
jsfiddle
If you are doing a lot of them, you could write a small plugin to include it...
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.polyAnimate = function( destPoints, duration, easing, callback ) {
var poly = this;
Snap.animate( this.attr('points'), destPoints,
function( val ){ poly.attr({ points: val }) }, duration, easing, callback)
};
});
timeline1.polyAnimate( time1_stp1, 2000, mina.linear, function() { alert('finished')})
jsfiddle

About image rotation once element with specific id is clicked

Logo and elements from ul once clicked rotates image. By default image is already rotated by certain degrees, then on each click image rotates to necessary value.
So far I was using the following:
$("#objRotates").css('opacity','.2');
var value = 0;
var prev_value = 0;
$( "li" ).click(function() {
var text=$(this).text();
if(text==="text1"){value=0;}
if(text==="text2"){value=33;}
if(text==="text3"){value=66;}
if(prev_value != value){
$("#objRotates").animate({opacity:'1'});
$("#objRotates").rotate({
animateTo:value,
easing: $.easing.easeInOutExpo,
center: ["25px", "150px"],
callback: function(){$("#objRotates").animate({opacity:'0.2'});}
});
}
prev_value = value;
});
Above code is the one that was used before, where images start position was 0 and its animation was triggered from link text.
Using jqueryRotate.js examples(here)
How do I change the code, so that images start position is certain degrees and animation starts if element with specific ID is clicked?
Give at least clue..Cause for now, looking at my old code, I am lost. Thanks in advance.
SIMPLIFIED FIDDLE
Ok, so I've created a couple of samples for you to check out. The first one is very basic and I've simplified the code a little to make it easier to understand. This one just uses completely static values and a static elementId for the event, which I'm pretty sure answers your question based on your response to my comment yesterday. http://jsfiddle.net/x9ja7/594/
$("#elementId").click(function () {
var startingAngle = 45;
var endingAngle = 90;
var elementToRotate = "img";
$(elementToRotate).rotate({
angle: startingAngle,
animateTo: endingAngle
});
});
But I wanted to give another example as well that would be dynamic and repeatable for multiple elements. With the code above, you would have to copy/paste the same code over and over again if you want to perform this animation by clicking different elements. Here's an alternative. In this example, you set all of your parameters in the data attributes in the clickable element, then the function is completely repeatable, you only have to write it once. Less code = everyone happy! Here's the example: http://jsfiddle.net/x9ja7/595/
//#region Default starting angles
$("#image1").rotate({ angle: 90 });
$("#image2").rotate({ angle: 20 });
//#endregion
$(".rotateAction").click(function () {
//#region Optional parameter - used in the optional callback function
var $self = $(this);
//#endregion
var startingAngle = Number($(this).attr("data-startingangle"));
var endingAngle = Number($(this).attr("data-endingangle"));
var elementToRotate = $(this).attr("data-elementtorotate");
//#region If the current angle is the ending angle, reverse the animation - this can be removed if you want, I thought it may be cool to show some of the things you can do with this.
var currentAngle = $(elementToRotate).getRotateAngle();
if ( currentAngle[0] === endingAngle) {
startingAngle = Number($(this).attr("data-endingangle"));
endingAngle = Number($(this).attr("data-startingangle"));
}
//#endregion
$(elementToRotate).rotate({
angle: startingAngle,
animateTo: endingAngle
//#region This is optional - uncommenting this code would make the animation single-use only
//, callback: function () { $self.off().removeClass("clickable"); }
//#endregion
});
});
Hope this helps. If you need any other assistance, please let me know.

Snap SVG animate SVG on hover / reset SVG on leave

I'm using Snap.svg to create some SVGs that animate on hover.
A very simplified jsfiddle is here:
http://jsfiddle.net/62UA7/2/
var s = Snap("#svg");
var smallCircle = s.circle(100, 150, 70);
//animation
function animateSVG(){
smallCircle.animate({cy: 300}, 5000,mina.bounce);
smallCircle.animate({fill:"red"},200);
}
//reset function?
function resetSVG(){
// something here to reset SVG??
}
smallCircle.mouseover(animateSVG,resetSVG);
The hover / animation is working fine.
The intention is to stop the animation and return to original SVG state if the user moves the mouse off the SVG - and this is where I'm currently stuck.
The actual SVG files I'm using are complex, so hoping for a quick way of 'refreshing' the SVG rather than manually moving it back to original position and colour
I'm assuming there's a really easy way of doing this - just can't seem to work it out or find the answer in any documentation anywhere.
Hopefully someone can help - thanks in advance if you can!
If you are only willing to animate between 2 states I found that Codrops animated svg icons did great job with handling this type of snap.svg animations. I have started using their code as a basis for my future exploration of SNAP.SVG. But getting back to the code: the most fun part is that you configure your animation with simple JSON objects such as:
plus : {
url : 'plus',
animation : [
{
el : 'path:nth-child(1)',
animProperties : {
from : { val : '{"transform" : "r0 32 32", "opacity" : 1}' },
to : { val : '{"transform" : "r180 32 32", "opacity" : 0}' }
}
},
{
el : 'path:nth-child(2)',
animProperties : {
from : { val : '{"transform" : "r0 32 32"}' },
to : { val : '{"transform" : "r180 32 32"}' }
}
}
]
},
and you can easily attach any sort of event trigger for animation In/Out. Hope that helps.
Personally I'd probably do it something like the following, storing it in a data element. It depends what problems you are really trying to overcome though, how you are actually animating it (I suspect it could be easier than my solution with certain animations, but trying to think of something that covers most bases), and if you really need it reset, also how many attributes you are animating and if there is other stuff going on...
var smallCircle = s.circle(100, 150, 70);
var saveAttributes = ['fill', 'cy'];
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.resetSVG = function() {
this.stop();
for( var a=0; a<saveAttributes.length; a++) {
if( this.data( saveAttributes[a] ) ) {
this.attr( saveAttributes[a], this.data( saveAttributes[a] ) );
};
};
};
Element.prototype.storeAttributes = function() {
for( var a=0; a<saveAttributes.length; a++) {
if( this.attr( saveAttributes[a]) ) {
this.data( saveAttributes[a], this.attr( saveAttributes[a] ) );
};
};
};
});
function animateSVG(){
smallCircle.animate({cy: 300}, 5000,mina.bounce);
smallCircle.animate({fill:"red"},200);
};
smallCircle.storeAttributes();
smallCircle.mouseover( animateSVG );
smallCircle.mouseout( smallCircle.resetSVG );
jsfiddle

Categories

Resources