/**Physical and stress test Author: karoshi Email: nguyenhuongw@gmail.com Manual: Initial mode: Shape building + Switch mode: right mouse. Physic mode: + Camera action: Move mouse to change view + Generate element: Click on center element to generate new element. + Generate big bang: Click and hold on center element. -- History -- 04/10: Add Torus 06/10: Change background, Use click event 10/10: open core alg, Fix bug devide-by-zero */ require(['famous/core/Engine', 'famous/core/Surface', 'famous/core/EventHandler', 'famous/core/View', 'famous/core/Transform', 'famous/core/Modifier', 'famous/modifiers/StateModifier', 'famous/physics/PhysicsEngine', 'famous/physics/bodies/Body', 'famous/physics/bodies/Circle', 'famous/physics/constraints/Wall', 'famous/physics/constraints/Collision', 'famous/transitions/SpringTransition', 'famous/transitions/WallTransition', 'famous/transitions/Transitionable', 'famous/physics/constraints/Distance', 'famous/math/Vector' ], function(Engine, Surface, EventHandler, View, Transform, Modifier, StateModifier, PhysicsEngine, Circle, Body, Wall, Collision, SpringTransition, WallTransition, Transitionable, Distance, Vector ) { Transitionable.registerMethod('spring', SpringTransition); Transitionable.registerMethod('wall', WallTransition); var balls = []; var particles = []; var born = []; var isActive = true; var isMouseDown = true; var ne = false; var abc = 0; var context = Engine.createContext(); context.setPerspective(2000); var handler = new EventHandler(); var cameraView = new View(); var camera = new StateModifier({ // origin:[0.5,0.5], align: [0.5, 0.5], //transform: Transform.rotate(-Math.PI / 6, Math.PI / 8, 0) }); context.add(camera).add(cameraView); var physicsEngine = new PhysicsEngine(); var size = Math.min(window.innerWidth, window.innerHeight) / 2.5; var r = size / 4; var leftWall, rightWall, topWall, bottomWall, farWall, nearWall; var collision = new Collision(); function _createWall(size, r) { leftWall = new Wall({ normal: [1, 0, 0], distance: size - r / 2, restitution: 0.5 }); rightWall = new Wall({ normal: [-1, 0, 0], distance: size - r / 2, restitution: 0.5 }); topWall = new Wall({ normal: [0, 1, 0], distance: size - r / 2, restitution: 0.5 }); bottomWall = new Wall({ normal: [0, -1, 0], distance: size - r / 2, restitution: 0.5 }); farWall = new Wall({ normal: [0, 0, 1], distance: size, restitution: 0.5 }); nearWall = new Wall({ normal: [0, 0, -1], distance: size - r / 2, restitution: 0.5 }); } function clickCallback() { if (this.idx == 0) add(); return false; } /*HAVE to attach before adding */ function _attachCollision(ball) { if (balls.length > 0) physicsEngine.attach(collision, particles, ball.particle); } function _attachWall(ball) { physicsEngine.addBody(ball.particle); physicsEngine.attach(leftWall, [ball.particle]); physicsEngine.attach(rightWall, [ball.particle]); physicsEngine.attach(topWall, [ball.particle]); physicsEngine.attach(bottomWall, [ball.particle]); physicsEngine.attach(farWall, [ball.particle]); physicsEngine.attach(nearWall, [ball.particle]); } function _pushForce(ball) { if (balls.length > 1) { var v = ball.particle.getVelocity(); // ball.particle.setAngularMomentum([Math.random(), Math.random(), Math.random() * 100]); // ball.particle.setAngularVelocity([Math.random() - 0.5, Math.random() - 0.5, Math.random()*100]); ball.particle.setVelocity([v[0] + Math.random() / 2 - 0.25, v[1] + Math.random() / 2 - 0.25, v[2] + Math.random() / 5 - 0.1]); } } var anchor = new Vector(0, 0, 0); var distance = new Distance({ length: 200, minLength: 30, period: 200, dampingRatio: 0.1, anchor: anchor }); function _distance(ball) { physicsEngine.attach(distance, [ball.particle]); } function add() { var ball = new Surface({ size: [r, r], classes: ['double-sided', 'center', 'shadow'], properties: { backgroundColor: "hsla(" + ((balls.length + 50) * 360 / 50) + ", 100%, 50%,0.8)", borderRadius: '100px', lineHeight: size / 4 + 'px', }, content: String.fromCharCode(33 + abc++) //33 is start of char code }); //% if (33 + abc >= 127) abc = 0; //127 is end char code ball.idx = balls.length; ball.mod = new StateModifier({ size: [r, r], origin: [0.5, 0.5], //align: [0.5, 0.5] }); ball.particle = new Circle({ // mass: 1.01, radius: r }); _attachWall(ball); ball.on("click", clickCallback.bind(ball)); cameraView.add(ball.mod).add(ball); balls.push(ball); born.push(Date.now()); _pushForce(ball); } var manual; function _addText() { manual = new Surface({ classes: ['align'], content: "Change shape: Left mouse click" + "
Switch to physics mode: Right mouse click." + "
Author: karoshi" }); context.add(new Modifier({ size: [true, true], origin: [0, 0], align: [0, 0], })).add(manual); } function _setText() { manual.setContent("Camera action: Move mouse to change view" + "
Generate element: Click on center element to generate new element." + "
Generate big bang: Click and hold on center element." + "
Author: karoshi" ); } function _addBox() { /*Create Box*/ var convert = Math.PI / 180; var matrix = []; var x, y, rot, scale; var rotate = [ [0, 0, 0], [0, 90, 0], [0, -90, 0], [0, 0, 0], [90, 0, 0], [-90, 0, 0] ]; var scale = 1; var xlate = [ [0, 0, size], [size, -size, size], [-size, -size, -size], [-size, -size, -size], [-size, size, -size], [-size, -size, size] ]; var xlt, rot; function originalCube() { for (var i = 0; i < 6; i++) { xlt = xlate[i] rot = rotate[i]; matrix.push(Transform.multiply( Transform.translate(xlt[0], xlt[1], xlt[2]), Transform.rotate(rot[0] * convert, rot[1] * convert, rot[2] * convert))); // matrix = Transform.aboutOrigin([0.5,0.5,0],matrix) _smod[i].setTransform( matrix[i]); } } var colors = ['rgba(255,0,0,0)', 'rgba(0,255,0,0.5)', 'rgba(255,255,0,0.5)', 'rgba(0,0,255,0.5)', 'rgba(255,0,255,0.5)', 'rgba(125,0,255,0.5)']; var _surface = []; var _smod = []; var boxView = new View(); var boxViewMod = new Modifier({ // align:[0.5,0.5] }); cameraView.add(boxViewMod).add(boxView); for (var i = 0; i < 6; i++) { var surface = new Surface({ //origin:[0.5,0.5], size: [size * 2, size * 2], classes: ['double-sided'], properties: { backgroundColor: colors[i] } }); var mod = new Modifier({ //origin: [0.5, 0.5], // align:[0.5,0.5] }); _surface.push(surface); _smod.push(mod); boxView.add(mod).add(surface); // _ctx.add(_surface[i]); } originalCube(); } setTimeout(function() { isMouseDown = false; }, 1000); function autoGen() { if (isActive) clickCallback.call(balls[0]); }; function parentLive() { var ball = balls[0]; var scale = 1; var ran = Math.random() / 10; scale = ne ? 0.7 + ran : 1.5 + ran; ne = !ne; var scaleT = Transform.scale(scale, scale, scale); var tran = ball.particle.getTransform(); tran = Transform.multiply(tran, scaleT); ball.mod.setTransform(tran, { curve: 'easeOut', duration: 1000 }, autoGen); } function _updatePhysic() { Engine.on('prerender', function() { var nowT = Date.now(); for (var i = 1; i < balls.length; i++) { var ball = balls[i]; if (ball.idx === null) continue; var tran = ball.particle.getTransform(); if (nowT - born[i] > 60000 && ball.idx >= 0) { ball.mod.setOpacity(0, { duration: 10000 }, function() { //remove from physic // balls.splice(this.idx,1); //FIX ME!! //physicsEngine.removeBody(this.particle); this.idx = null; }.bind(ball)); ball.idx = -1; }; ball.mod.setTransform(tran); //ball.modSphere.setTransform(camRotInv); }; //auto gen by mouse if (isMouseDown) { var rand = Math.round(Math.random() * 500) + 1000; setTimeout(function() { autoGen(); //if (isMouseDown) loop(); }, rand); } }); } function _distance(balls) { } function _createBalls(n) { for (var i = 0; i < n; i++) add(); } /*Utilities*/ function crossProduct(v1, v2, vR) { vR[0] = ((v1[1] * v2[2]) - (v1[2] * v2[1])); vR[1] = -((v1[0] * v2[2]) - (v1[2] * v2[0])); vR[2] = ((v1[0] * v2[1]) - (v1[1] * v2[0])); } function sub(v1, v2, vR) { vR[0] = v2[0] - v1[0]; vR[1] = v2[1] - v1[1]; vR[2] = v2[2] - v1[2]; } function len(v) { return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); } function normalize(v1, vR) { var fMag = Math.sqrt(Math.pow(v1[0], 2) + Math.pow(v1[1], 2) + Math.pow(v1[2], 2) ); fMag = fMag == 0 ? 1 : fMag; vR[0] = v1[0] / fMag; vR[1] = v1[1] / fMag; vR[2] = v1[2] / fMag; } function _collapse(balls) { for (var i = 0; i < balls.length; i++) { var index = i; var ball = balls[index]; var tran = Transform.translate(0, 0, 0); ball.mod.setTransform(tran, { method: "spring", dampingRatio: 0.5, period: 600 + index }); } } function _grid(balls, cols, rows) { cols = Math.floor(window.innerWidth / r) + 1; for (var i = 0; i < balls.length && i < rows; i++) for (var j = 0; j < balls.length && j < cols; j++) { var index = j + i * cols; if (index >= balls.length) return; var ball = balls[index]; var x = j * r - window.innerWidth / 2; var y = i * r - window.innerHeight / 2; var tran = Transform.translate(x + r / 2, y + r / 2, 0); ball.mod.setTransform(tran, { method: "spring", dampingRatio: 0.5, period: 600 + index }); } } function _sphere(balls, R) { /* x = rsinVcosP, y = rsinVsinP, z = rcosV; V =[0,PI];//lon P=[0,2PI];//lat */ var index = -1; var lat = 1; var lon = Math.floor(R * Math.PI / r) + 2; for (var i = 0; i < lon; i++) for (var j = 0; j < lat; j++) { index++; if (index >= balls.length) return; var ball = balls[index]; var V = (i) / (lon - 1) * Math.PI; var rd = R * Math.sin(V); var n = Math.floor(2 * Math.PI * rd / r); n = n > 0 ? n : 1; lat = n; if (j > n - 1) break; var P = (j / (lat)) * Math.PI * 2; var pos = [R * Math.sin(V) * Math.cos(P), R * Math.sin(V) * Math.sin(P), R * Math.cos(V)]; var tran = Transform.translate(R * Math.sin(V) * Math.cos(P), R * Math.sin(V) * Math.sin(P), R * Math.cos(V)); //normal /*A=V1xV2; sin = len(A)/(V1*V2); axis is A; */ var A = []; var origin = [0, 0, 1]; var normal = pos; crossProduct(origin, normal, A); var angleSin = len(normal) ? len(A) / len(normal) : 0; var angle = Math.asin(angleSin); angle = V > Math.PI / 2 ? -angle : angle; //famo?? normalize(A, A); var rot = Transform.rotateAxis(A, angle); tran = Transform.multiply(tran, rot); ball.mod.setTransform(tran, { method: "spring", dampingRatio: 0.4, period: 600 + index * 5 }); } //last ones index++; for (var i = index; i < balls.length; i++) balls[i].mod.setTransform(Transform.identity, { method: "spring", dampingRatio: 0.5, period: 600 + index }); } function _torus(balls, R, s) { /* x = (R + rcosV)cosP y = (R + rcosV)sinP z = rsinV V,P = [0,2PI] */ var sum = -1; var lat = Math.floor(2 * (R - s) * Math.PI / r); var lon = Math.floor(2 * s * Math.PI / r) + 1; for (var i = 0; i < lon; i++) { var Vt = (i) / (lon) * (Math.PI * 2); lat = Math.floor(2 * (R + (s * Math.sin(Vt + Math.PI / 2))) * Math.PI / r); for (var j = 0; j < lat; j++) { var index = ++sum; if (index >= balls.length) return; var ball = balls[index]; var V = (i) / (lon) * (Math.PI * 2); var P = (j / (lat)) * Math.PI * 2; var pos = [(R + s * Math.cos(V)) * Math.cos(P), (R + s * Math.cos(V)) * Math.sin(P), s * Math.sin(V)]; var tran = Transform.translate(pos[0], pos[1], pos[2]); //normal /*A=V1xV2; sin = len(A)/(V1*V2); axis is A; */ var A = []; var origin = [0, 0, 1]; var pos1 = [(R + s * Math.cos(0)) * Math.cos(P), (R + s * Math.cos(0)) * Math.sin(P), s * Math.sin(0)]; var last = (lon - 1) / (lon) * (Math.PI); var pos2 = [(R + s * Math.cos(last)) * Math.cos(P), (R + s * Math.cos(last)) * Math.sin(P), s * Math.sin(last)]; var normal = [(pos1[0] + pos2[0]) / 2 - pos[0], (pos1[1] + pos2[1]) / 2 - pos[1], (pos1[2] + pos2[2]) / 2 - pos[2]]; crossProduct(origin, normal, A); var angleSin = len(normal) ? len(A) / len(normal) : 0; var angle = Math.asin(angleSin); angle = V > Math.PI ? angle : -angle; //famo?? normalize(A, A); var rot = Transform.rotateAxis(A, angle); tran = Transform.multiply(tran, rot); ball.mod.setTransform(tran, { method: "spring", dampingRatio: 0.4, period: 600 + index * 7 }); } } sum++; for (var i = sum; i < balls.length; i++) balls[i].mod.setTransform(Transform.identity, { method: "spring", dampingRatio: 0.5, period: 600 + i }); } Engine.on("mousemove", function(e) { var tranRot = Transform.rotate(Math.PI * (e.clientY - document.body.clientHeight / 2) / document.body.clientHeight, -Math.PI * (e.clientX - document.body.clientWidth / 2) / document.body.clientWidth, 0); camera.setTransform(tranRot); }); var temp = 0; Engine.on("mousedown", function(e) { e.preventDefault(); isMouseDown = true; if (e.which > 1 && temp !== null) { _collapse(balls); setTimeout(function() { _addBox(); _setText(); setInterval(parentLive, 1000); _updatePhysic(); }, 7000); temp = null; } }); Engine.on("click", function(e) { e.preventDefault(); if (temp !== null) { if (temp == 0) _grid(balls, 10, 25); else if (temp == 1) _sphere(balls, size); else _torus(balls, size, size / 3); temp = temp < 2 ? temp + 1 : 0; } }); Engine.on("mouseup", function() { isMouseDown = false; }); window.onfocus = function() { isActive = true; }; window.onblur = function() { isActive = false; }; document.oncontextmenu = function(e) { return false; } //Start with parent _createWall(size, r); _addText(); //torus: 2PIR*2PIs //sphere: PIR*R var min4Torus = Math.floor((4 * Math.PI * size * Math.PI * size / 3) / (Math.PI * r / 2 * r / 2)); //var min4Sphere = 2*Math.PI*size*size _createBalls(min4Torus); //_sphere(balls, size, 10, 20) _torus(balls, size, size / 3); })