611 lines
16 KiB
JavaScript
611 lines
16 KiB
JavaScript
/*
|
|
* Gray-Scott
|
|
*
|
|
* A solver of the Gray-Scott model of reaction diffusion.
|
|
*
|
|
* ©2012 pmneila.
|
|
* p.mneila at upm.es
|
|
*/
|
|
|
|
(function(){
|
|
|
|
// Canvas.
|
|
var canvas;
|
|
var canvasQ;
|
|
var canvasWidth;
|
|
var canvasHeight;
|
|
|
|
var mMouseX, mMouseY;
|
|
var mMouseDown = false;
|
|
|
|
var mRenderer;
|
|
var mScene;
|
|
var mCamera;
|
|
var mUniforms;
|
|
var mColors;
|
|
var mColorsNeedUpdate = true;
|
|
var mLastTime = 0;
|
|
|
|
var mTexture1, mTexture2;
|
|
var mGSMaterial, mScreenMaterial;
|
|
var mScreenQuad;
|
|
|
|
var mToggled = false;
|
|
|
|
var mMinusOnes = new THREE.Vector2(-1, -1);
|
|
|
|
// Some presets.
|
|
var presets = [
|
|
{
|
|
feed: 0.031,
|
|
kill: 0.057
|
|
},
|
|
{
|
|
feed: 0.031,
|
|
kill: 0.058
|
|
},
|
|
{
|
|
feed: 0.031,
|
|
kill: 0.060
|
|
},
|
|
{
|
|
feed: 0.033,
|
|
kill: 0.058
|
|
},
|
|
{
|
|
feed: 0.033,
|
|
kill: 0.059
|
|
},
|
|
{
|
|
feed: 0.033,
|
|
kill: 0.061
|
|
},
|
|
{
|
|
feed: 0.035,
|
|
kill: 0.059
|
|
},
|
|
{
|
|
feed: 0.035,
|
|
kill: 0.060
|
|
},
|
|
{
|
|
feed: 0.035,
|
|
kill: 0.061
|
|
}
|
|
];
|
|
|
|
// Configuration.
|
|
var feed = presets[0].feed;
|
|
var kill = presets[0].kill;
|
|
|
|
var noAnimation = false;
|
|
|
|
skipWebGL = function(){
|
|
|
|
var mobile = /Android|Mobile|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
var noWebgl = false;
|
|
var canvas;
|
|
var ctx;
|
|
|
|
try {
|
|
canvas = document.createElement('canvas');
|
|
ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
}
|
|
catch (e) {
|
|
console && "log" in console && console.log("No webgl");
|
|
}
|
|
|
|
if (ctx == undefined) {
|
|
noWebgl = true;
|
|
}
|
|
|
|
if (noWebgl || mobile) {
|
|
return true;
|
|
}
|
|
|
|
return false
|
|
}
|
|
init = function()
|
|
{
|
|
|
|
if( skipWebGL() ){
|
|
$("canvas").hide();
|
|
$("#replacementImage").show()
|
|
noAnimation = true
|
|
return
|
|
}
|
|
|
|
canvasQ = $('#myCanvas');
|
|
canvas = canvasQ.get(0);
|
|
|
|
init_controls();
|
|
canvas.onmousedown = onMouseDown;
|
|
canvas.onmouseup = onMouseUp;
|
|
canvas.onmousemove = onMouseMove;
|
|
|
|
mRenderer = new THREE.WebGLRenderer({canvas: canvas, preserveDrawingBuffer: true});
|
|
|
|
mScene = new THREE.Scene();
|
|
mCamera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0, 100);
|
|
mCamera.position.z = 100;
|
|
mCamera.scale.x = mCamera.scale.y = 0.99985 ;
|
|
mScene.add(mCamera);
|
|
|
|
// For debugging purpose
|
|
window.mCamera = mCamera;
|
|
|
|
mUniforms = {
|
|
screenWidth: {type: "f", value: undefined},
|
|
screenHeight: {type: "f", value: undefined},
|
|
tSource: {type: "t", value: undefined},
|
|
delta: {type: "f", value: 0.0},
|
|
feed: {type: "f", value: feed},
|
|
kill: {type: "f", value: kill},
|
|
brush: {type: "v2", value: new THREE.Vector2(-10, -10)},
|
|
color1: {type: "v4", value: new THREE.Vector4(0, 0, 0.0, 0)},
|
|
color2: {type: "v4", value: new THREE.Vector4(0, 1, 0, 0.2)},
|
|
color3: {type: "v4", value: new THREE.Vector4(1, 1, 0, 0.21)},
|
|
color4: {type: "v4", value: new THREE.Vector4(1, 0, 0, 0.4)},
|
|
color5: {type: "v4", value: new THREE.Vector4(1, 1, 1, 0.6)}
|
|
};
|
|
mColors = [mUniforms.color1, mUniforms.color2, mUniforms.color3, mUniforms.color4, mUniforms.color5];
|
|
$("#gradient").gradient("setUpdateCallback", onUpdatedColor);
|
|
|
|
mGSMaterial = new THREE.ShaderMaterial({
|
|
uniforms: mUniforms,
|
|
vertexShader: document.getElementById('standardVertexShader').textContent,
|
|
fragmentShader: document.getElementById('gsFragmentShader').textContent,
|
|
});
|
|
mScreenMaterial = new THREE.ShaderMaterial({
|
|
uniforms: mUniforms,
|
|
vertexShader: document.getElementById('standardVertexShader').textContent,
|
|
fragmentShader: document.getElementById('screenFragmentShader').textContent,
|
|
});
|
|
|
|
var plane = new THREE.PlaneGeometry(1.0, 1.0);
|
|
mScreenQuad = new THREE.Mesh(plane, mScreenMaterial);
|
|
mScene.add(mScreenQuad);
|
|
|
|
mColorsNeedUpdate = true;
|
|
|
|
resize(canvas.clientWidth, canvas.clientHeight);
|
|
|
|
render(0);
|
|
mUniforms.brush.value = new THREE.Vector2(0.5, 0.5);
|
|
mLastTime = new Date().getTime();
|
|
requestAnimationFrame(render);
|
|
}
|
|
|
|
var resize = function(width, height)
|
|
{
|
|
// Set the new shape of canvas.
|
|
canvasQ.width(width);
|
|
canvasQ.height(height);
|
|
|
|
// Get the real size of canvas.
|
|
canvasWidth = canvasQ.width();
|
|
canvasHeight = canvasQ.height();
|
|
|
|
mRenderer.setSize(canvasWidth, canvasHeight);
|
|
|
|
// TODO: Possible memory leak?
|
|
mTexture1 = new THREE.WebGLRenderTarget(canvasWidth/2, canvasHeight/2,
|
|
{minFilter: THREE.LinearFilter,
|
|
magFilter: THREE.LinearFilter,
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType});
|
|
mTexture2 = new THREE.WebGLRenderTarget(canvasWidth/2, canvasHeight/2,
|
|
{minFilter: THREE.LinearFilter,
|
|
magFilter: THREE.LinearFilter,
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType});
|
|
mTexture1.wrapS = THREE.RepeatWrapping;
|
|
mTexture1.wrapT = THREE.RepeatWrapping;
|
|
mTexture2.wrapS = THREE.RepeatWrapping;
|
|
mTexture2.wrapT = THREE.RepeatWrapping;
|
|
|
|
mUniforms.screenWidth.value = canvasWidth/2;
|
|
mUniforms.screenHeight.value = canvasHeight/2;
|
|
}
|
|
|
|
var render = function(time)
|
|
{
|
|
var dt = (time - mLastTime)/20.0;
|
|
if(dt > 0.8 || dt<=0)
|
|
dt = 0.8;
|
|
mLastTime = time;
|
|
|
|
mScreenQuad.material = mGSMaterial;
|
|
mUniforms.delta.value = dt;
|
|
mUniforms.feed.value = feed;
|
|
mUniforms.kill.value = kill;
|
|
|
|
for(var i=0; i<8; ++i)
|
|
{
|
|
if(!mToggled)
|
|
{
|
|
mUniforms.tSource.value = mTexture1;
|
|
mRenderer.render(mScene, mCamera, mTexture2, true);
|
|
mUniforms.tSource.value = mTexture2;
|
|
}
|
|
else
|
|
{
|
|
mUniforms.tSource.value = mTexture2;
|
|
mRenderer.render(mScene, mCamera, mTexture1, true);
|
|
mUniforms.tSource.value = mTexture1;
|
|
}
|
|
|
|
mToggled = !mToggled;
|
|
mUniforms.brush.value = mMinusOnes;
|
|
}
|
|
|
|
if(mColorsNeedUpdate)
|
|
updateUniformsColors();
|
|
|
|
mScreenQuad.material = mScreenMaterial;
|
|
mRenderer.render(mScene, mCamera);
|
|
|
|
requestAnimationFrame(render);
|
|
}
|
|
|
|
loadPreset = function(idx)
|
|
{
|
|
feed = presets[idx].feed;
|
|
kill = presets[idx].kill;
|
|
worldToForm();
|
|
}
|
|
|
|
var updateUniformsColors = function()
|
|
{
|
|
var values = $("#gradient").gradient("getValuesRGBS");
|
|
for(var i=0; i<values.length; i++)
|
|
{
|
|
var v = values[i];
|
|
mColors[i].value = new THREE.Vector4(v[0], v[1], v[2], v[3]);
|
|
}
|
|
|
|
mColorsNeedUpdate = false;
|
|
}
|
|
|
|
var onUpdatedColor = function()
|
|
{
|
|
mColorsNeedUpdate = true;
|
|
updateShareString();
|
|
}
|
|
|
|
var onMouseMove = function(e)
|
|
{
|
|
var ev = e ? e : window.event;
|
|
|
|
mMouseX = ev.pageX - canvasQ.offset().left; // these offsets work with
|
|
mMouseY = ev.pageY - canvasQ.offset().top; // scrolled documents too
|
|
|
|
if(mMouseDown)
|
|
mUniforms.brush.value = new THREE.Vector2(mMouseX/canvasWidth, 1-mMouseY/canvasHeight);
|
|
}
|
|
|
|
var onMouseDown = function(e)
|
|
{
|
|
var ev = e ? e : window.event;
|
|
mMouseDown = true;
|
|
|
|
mUniforms.brush.value = new THREE.Vector2(mMouseX/canvasWidth, 1-mMouseY/canvasHeight);
|
|
}
|
|
|
|
var onMouseUp = function(e)
|
|
{
|
|
mMouseDown = false;
|
|
}
|
|
|
|
clean = function()
|
|
{
|
|
mUniforms.brush.value = new THREE.Vector2(-10, -10);
|
|
}
|
|
|
|
snapshot = function()
|
|
{
|
|
var dataURL = canvas.toDataURL("image/png");
|
|
window.open(dataURL, "name-"+Math.random());
|
|
}
|
|
|
|
// resize canvas to fullscreen, scroll to upper left
|
|
// corner and try to enable fullscreen mode and vice-versa
|
|
fullscreen = function() {
|
|
|
|
var canv = $('#myCanvas');
|
|
var elem = canv.get(0);
|
|
|
|
if(isFullscreen())
|
|
{
|
|
// end fullscreen
|
|
if (elem.cancelFullscreen) {
|
|
elem.cancelFullscreen();
|
|
} else if (document.mozCancelFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
} else if (document.webkitCancelFullScreen) {
|
|
document.webkitCancelFullScreen();
|
|
}
|
|
}
|
|
|
|
if(!isFullscreen())
|
|
{
|
|
// save current dimensions as old
|
|
window.oldCanvSize = {
|
|
width : canv.width(),
|
|
height: canv.height()
|
|
};
|
|
|
|
// adjust canvas to screen size
|
|
resize(screen.width, screen.height);
|
|
|
|
// scroll to upper left corner
|
|
$('html, body').scrollTop(canv.offset().top);
|
|
$('html, body').scrollLeft(canv.offset().left);
|
|
|
|
// request fullscreen in different flavours
|
|
if (elem.requestFullscreen) {
|
|
elem.requestFullscreen();
|
|
} else if (elem.mozRequestFullScreen) {
|
|
elem.mozRequestFullScreen();
|
|
} else if (elem.webkitRequestFullscreen) {
|
|
elem.webkitRequestFullscreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
var isFullscreen = function()
|
|
{
|
|
return document.mozFullScreenElement ||
|
|
document.webkitCurrentFullScreenElement ||
|
|
document.fullscreenElement;
|
|
}
|
|
|
|
$(document).bind('webkitfullscreenchange mozfullscreenchange fullscreenchange', function(ev) {
|
|
// restore old canvas size
|
|
if(!isFullscreen())
|
|
resize(window.oldCanvSize.width, window.oldCanvSize.height);
|
|
});
|
|
|
|
var worldToForm = function()
|
|
{
|
|
//document.ex.sldReplenishment.value = feed * 1000;
|
|
$("#sld_replenishment").slider("value", feed);
|
|
$("#sld_diminishment").slider("value", kill);
|
|
}
|
|
|
|
var init_controls = function()
|
|
{
|
|
$("#sld_replenishment").slider({
|
|
value: feed, min: 0, max:0.1, step:0.001,
|
|
change: function(event, ui) {$("#replenishment").html(ui.value); feed = ui.value; updateShareString();},
|
|
slide: function(event, ui) {$("#replenishment").html(ui.value); feed = ui.value; updateShareString();}
|
|
});
|
|
$("#sld_replenishment").slider("value", feed);
|
|
$("#sld_diminishment").slider({
|
|
value: kill, min: 0, max:0.073, step:0.001,
|
|
change: function(event, ui) {$("#diminishment").html(ui.value); kill = ui.value; updateShareString();},
|
|
slide: function(event, ui) {$("#diminishment").html(ui.value); kill = ui.value; updateShareString();}
|
|
});
|
|
$("#sld_diminishment").slider("value", kill);
|
|
|
|
$('#share').keypress(function (e) {
|
|
if (e.which == 13) {
|
|
parseShareString();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
$("#btn_clear").button({
|
|
icons : {primary : "ui-icon-document"},
|
|
text : false
|
|
});
|
|
$("#btn_snapshot").button({
|
|
icons : {primary : "ui-icon-image"},
|
|
text : false
|
|
});
|
|
$("#btn_fullscreen").button({
|
|
icons : {primary : "ui-icon-arrow-4-diag"},
|
|
text : false
|
|
});
|
|
|
|
}
|
|
|
|
alertInvalidShareString = function()
|
|
{
|
|
$("#share").val("Invalid string!");
|
|
setTimeout(updateShareString, 1000);
|
|
}
|
|
|
|
parseShareString = function()
|
|
{
|
|
var str = $("#share").val();
|
|
var fields = str.split(",");
|
|
|
|
if(fields.length != 12)
|
|
{
|
|
alertInvalidShareString();
|
|
return;
|
|
}
|
|
|
|
var newFeed = parseFloat(fields[0]);
|
|
var newKill = parseFloat(fields[1]);
|
|
|
|
if(isNaN(newFeed) || isNaN(newKill))
|
|
{
|
|
alertInvalidShareString();
|
|
return;
|
|
}
|
|
|
|
var newValues = [];
|
|
for(var i=0; i<5; i++)
|
|
{
|
|
var v = [parseFloat(fields[2+2*i]), fields[2+2*i+1]];
|
|
|
|
if(isNaN(v[0]))
|
|
{
|
|
alertInvalidShareString();
|
|
return;
|
|
}
|
|
|
|
// Check if the string is a valid color.
|
|
if(! /^#[0-9A-F]{6}$/i.test(v[1]))
|
|
{
|
|
alertInvalidShareString();
|
|
return;
|
|
}
|
|
|
|
newValues.push(v);
|
|
}
|
|
|
|
$("#gradient").gradient("setValues", newValues);
|
|
feed = newFeed;
|
|
kill = newKill;
|
|
worldToForm();
|
|
}
|
|
|
|
updateShareString = function()
|
|
{
|
|
var str = "".concat(feed, ",", kill);
|
|
|
|
var values = $("#gradient").gradient("getValues");
|
|
for(var i=0; i<values.length; i++)
|
|
{
|
|
var v = values[i];
|
|
str += "".concat(",", v[0], ",", v[1]);
|
|
}
|
|
$("#share").val(str);
|
|
}
|
|
|
|
var mWord = [[[682,100],[682,667],[569,667],[569,441],[454,441],[454,667],[342,667],[342,100],[454,100],[454,100],[454,326],[569,326],[569,100]],
|
|
//
|
|
[[682,100],[682,213],[457,213],[457,328],[682,328],[682,667],[342,667],[342,553],[570,553],[570,440],[342,440],[342,100],[682,100]],
|
|
//
|
|
[[683,100],[683,215],[453,215],[453,328],[683,328],[683,441],[453,441],[453,668],[342,668],[342,100],[683,100]]]
|
|
var currentLetterIndex = 0
|
|
var currentPositionIndex = 0
|
|
var simSpeed = 0.1
|
|
var curPos = []
|
|
var nexPos = []
|
|
var vectX = 0
|
|
var vectY = 0
|
|
var angle = 0
|
|
var moveX = 0
|
|
var moveY = 0
|
|
var curX = mWord[0][0][0]
|
|
var curY = mWord[0][0][1]
|
|
simulateClick = function(){
|
|
|
|
if( noAnimation ) {
|
|
return;
|
|
}
|
|
if( mMouseDown || noAnimation == true ){
|
|
return
|
|
}
|
|
// Let's get the current X,Y
|
|
curPos = mWord[currentLetterIndex][currentPositionIndex]
|
|
|
|
// Let's get the next position
|
|
// If last letter, loop with first
|
|
if ( currentPositionIndex >= mWord[currentLetterIndex].length - 1 ) {
|
|
nexPos = mWord[currentLetterIndex][0]
|
|
}else{
|
|
nexPos = mWord[currentLetterIndex][currentPositionIndex +1]
|
|
}
|
|
|
|
// Let's get the line to draw X and Y vector components
|
|
vectX = nexPos[0] - curPos[0]
|
|
vectY = nexPos[1] - curPos[1]
|
|
|
|
|
|
// Let's move along the vector on the line
|
|
angle = Math.atan2( vectY, vectX )
|
|
moveX = Math.cos(angle) * simSpeed
|
|
moveY = Math.sin(angle) * simSpeed
|
|
curX += moveX
|
|
curY += moveY
|
|
|
|
// Draw the point
|
|
mUniforms.brush.value = new THREE.Vector2(curX/canvasWidth, 1-curY/canvasHeight);
|
|
|
|
// Is line fully drawn?
|
|
var finishedPos = false
|
|
if (( moveX > 0 && curX > nexPos[0])
|
|
|| ( moveX < 0 && curX < nexPos[0])
|
|
|| ( moveY > 0 && curY > nexPos[1])
|
|
|| ( moveY < 0 && curY < nexPos[1]) ){
|
|
finishedPos = true
|
|
}
|
|
|
|
// Change position and letter if necessary
|
|
if( finishedPos ){
|
|
|
|
// Get new letter if it was the last line
|
|
if( currentPositionIndex >= mWord[currentLetterIndex].length - 1 ){
|
|
currentLetterIndex = currentLetterIndex == (mWord.length - 1) ? 0 : currentLetterIndex + 1
|
|
currentPositionIndex = 0
|
|
|
|
// Randomize preset
|
|
key = parseInt( Math.random() * presets.length)
|
|
newPreset = presets[key]
|
|
feed= newPreset.feed
|
|
kill = newPreset.kill
|
|
// mCamera.left = - 0.501
|
|
// mCamera.right = 0.501
|
|
|
|
// Just a new line of the letter
|
|
}else{
|
|
currentPositionIndex++
|
|
}
|
|
|
|
// Force new position point
|
|
curX = mWord[currentLetterIndex][currentPositionIndex][0]
|
|
curY = mWord[currentLetterIndex][currentPositionIndex][1]
|
|
|
|
}
|
|
var factor = 0.000002
|
|
var positionBoundary = 0.00005
|
|
randX = (Math.random() - 0.5 ) * factor;
|
|
randY = (Math.random() - 0.5 ) * factor;
|
|
randS = (Math.random() - 0.5 ) * factor * 5 ;
|
|
if( mCamera.position.x < -positionBoundary ){
|
|
mCamera.position.x = -positionBoundary;
|
|
}else if( mCamera.position.x > positionBoundary ){
|
|
mCamera.position.x = positionBoundary;
|
|
}else if(mCamera.position.y < -positionBoundary ){
|
|
mCamera.position.y = -positionBoundary;
|
|
} else if(mCamera.position.y > positionBoundary ){
|
|
mCamera.position.y = positionBoundary;
|
|
}
|
|
mCamera.position.x += randX;
|
|
mCamera.position.y += randY;
|
|
if( mCamera.scale.x < 0.9997 ){
|
|
mCamera.scale.x = 0.9997
|
|
}else if(mCamera.scale.x >= 1.0003 ){
|
|
mCamera.scale.x = 1.0003;
|
|
}
|
|
mCamera.scale.x += randS;
|
|
mCamera.scale.y += randS ;
|
|
|
|
mCamera.updateProjectionMatrix();
|
|
|
|
|
|
|
|
}
|
|
|
|
setInterval( simulateClick, 1 )
|
|
|
|
|
|
})();
|
|
$(function(){
|
|
|
|
$(".close").bind("click",function(){
|
|
$(this).parent().toggle();
|
|
})
|
|
$("#format_show").bind("click",function(){
|
|
$("#format").toggle();
|
|
})
|
|
|
|
|
|
|
|
})
|