<br />
<b>Warning</b>:  Declaration of Jetpack_IXR_Client::query() should be compatible with IXR_Client::query(...$args) in <b>/home/clients/7267bc096562fcdb78c0ab60d3ac51fb/web/blog/wp-content/plugins/jetpack/class.jetpack-ixr-client.php</b> on line <b>91</b><br />
{"id":695,"date":"2016-01-24T16:59:38","date_gmt":"2016-01-24T16:59:38","guid":{"rendered":"http:\/\/barradeau.com\/blog\/?p=695"},"modified":"2016-01-24T23:22:48","modified_gmt":"2016-01-24T23:22:48","slug":"a-short-journey","status":"publish","type":"post","link":"http:\/\/barradeau.com\/blog\/?p=695","title":{"rendered":"a short journey"},"content":{"rendered":"<p>last december, the nice people at\u00a0<a href=\"http:\/\/www.cher-ami.tv\/\" target=\"_blank\">Cher Ami<\/a> (&#8220;dear friend&#8221; in french) contacted me to give them a hand on a little animation \/ wish card they wanted to release in january, I gladly accepted which gave birth to\u00a0this refreshing little thing\u00a0<a href=\"http:\/\/www.ashortjourney.com\/\" target=\"_blank\">http:\/\/www.ashortjourney.com\/<\/a><\/p>\n<p>Cher Ami\u00a0has qualified people in house, a\u00a0<a href=\"https:\/\/twitter.com\/03prod\" target=\"_blank\">creative director<\/a>, a\u00a0<a href=\"http:\/\/germainfraisse.com\/\" target=\"_blank\">copywriter\/musician<\/a>, a front end developer (Etienne Chaumont), a motion designer (Yannice\u00a0Berthault ) and for this project,\u00a0they worked with super talented 3D artist <a href=\"http:\/\/www.benoitchalland.com\/\" target=\"_blank\">Beno\u00eet Challand<\/a>\u00a0who did all the 3D.<\/p>\n<p>I mostly did the WebGL (with <a href=\"http:\/\/threejs.org\/\" target=\"_blank\">THREE.js<\/a>), took some shortcuts and had to find\u00a0a couple\u00a0of interesting solutions\u00a0that I would like\u00a0to share here.<\/p>\n<p>the site is divided into 9 scenes, the first thing I did was to export all the 3D scenes as series of binary 3D objects. as we were on a relatively tight deadline, my project was to merge\u00a0all the objects of each\u00a0scene\u00a0into one big ass binary and so long for the bandwith but\u00a0I noticed that 2 scenes\u00a0could be reused as they were a X axis flipped version of the same models\u00a0with different textures. after taking a closer look, I realized that many objects found in the second scene (the bag closeup) were reused all the way, the car was reused four\u00a0times, the bag 3, the camera 2 etc. so I chose to use a reusable mesh\u00a0system\u00a0instead. I was quite happy with my Model class ; it allowed multiple assets\u00a0loading\u00a0and\u00a0async instanciation that proved quite handy. this is the code:<\/p>\n<pre class=\"lang:js decode:true\">var models = function( exports ){\r\n\r\n    var bl = new THREE.BinaryLoader();\r\n    exports.load = function( scope, assets, cb ){\r\n\r\n        if( assets == null || assets.length == 0 ){\r\n            if( cb )cb();\r\n            return;\r\n        }\r\n        bl.load( assets[0].model, function(g){\r\n\r\n            g.name = assets[0].model;\r\n            scope.geometries.push( g );\r\n            textures.loadTexture( assets[0].texture, function( t ){\r\n\r\n                var material = materials.bitmap.clone();\r\n                material.uniforms.texture.value = t;\r\n                scope.materials.push( material );\r\n\r\n                assets.shift();\r\n                if( assets.length &gt; 0 ){\r\n                    exports.load( scope, assets, cb );\r\n                }else{\r\n                    if( cb )cb();\r\n                }\r\n            });\r\n        });\r\n    };\r\n\r\n    exports.generic = function(){\r\n\r\n        var f = function( fc, Class, assets, parent, cb ){\r\n\r\n            THREE.Object3D.call( fc );\r\n\r\n            Class.loaded        = Class.loaded || false;\r\n            Class.loading       = Class.loading || false;\r\n            Class.geometries    = Class.geometries || [];\r\n            Class.materials     = Class.materials  || [];\r\n            Class.instances     = Class.instances  || [];\r\n\r\n            if( !Class.loaded &amp;&amp; !Class.loading ){\r\n\r\n                Class.loading = true;\r\n                var scope = fc;\r\n                exports.load( Class, assets, function(){\r\n\r\n                    Class.loaded = true;\r\n                    Class.loading = false;\r\n                    populate( scope, Class );\r\n                    parent.add(scope);\r\n                    scope.init();\r\n                    if( cb )cb( scope );\r\n                    scope.onLoadComplete();\r\n\r\n                    \/\/instantiate all pending models\r\n                    Class.instances.forEach(function(c){\r\n                        populate(c, Class);\r\n                        c.parent.add( c );\r\n                        c.init();\r\n                        if( c.cb ) c.cb( c );\r\n                        c.onLoadComplete();\r\n                    });\r\n                } );\r\n                return;\r\n            }\r\n            else if( Class.loading ){\r\n\r\n                console.warn( 'this model is being loaded, can\\'t instantiate now' );\r\n                fc.parent = parent;\r\n                fc.cb = cb;\r\n                Class.instances.push( fc );\r\n                return;\r\n            }\r\n            else\r\n            {\r\n                populate( this, Class );\r\n                parent.add( fc );\r\n                fc.init();\r\n                if( cb )cb( fc );\r\n            }\r\n        };\r\n\r\n        \/\/this is when all the subMeshes are added to this Object3D\r\n        function populate( scope, Class ){\r\n\r\n            for( var i = 0; i &lt; Class.geometries.length; i++ ){\r\n                var mat = Class.materials[ i ].clone();\r\n                mat.uniforms.texture.value = Class.materials[ i ].uniforms.texture.value;\r\n                var mesh = new THREE.Mesh( Class.geometries[i], mat );\r\n                mesh.name = Class.geometries[i].name;\r\n                scope.add( mesh );\r\n            }\r\n        }\r\n\r\n        var _p = f.prototype = Object.create( THREE.Object3D.prototype );\r\n        _p.constructor = f;\r\n        _p.init = function(){};\r\n        _p.onLoadComplete = function(){};\r\n        return f;\r\n    }();\r\n    exports.dummy = new THREE.Object3D();\r\n    exports.dummy.origin = new THREE.Vector3();\r\n    return exports;\r\n}( {} );<\/pre>\n<p>a given model then inherits from this (yeah I know &#8230; inheritance vs composition blahblah&#8230; ^^ )<\/p>\n<pre class=\"lang:js decode:true \">var Car = function(){\r\n\r\n     var assets = [\r\n     { model: 'src\/3d\/models\/car_body.js',         texture: 'src\/3d\/textures\/car_body.jpg'     },\r\n     { model: 'src\/3d\/models\/car_wheels_back.js',  texture: 'src\/3d\/textures\/wheels.jpg'   },\r\n     [...]\r\n     ];\r\n\r\n     function Model( parent, cb ){ models.generic.call( this, this, Model, assets, parent, cb ); }\r\n     var _p = Model.prototype = Object.create( models.generic.prototype );\r\n     _p.constructor = Model;\r\n\r\n     [...] specific methods for this mesh\r\n     _p.init = function(){\r\n    \r\n        Object.defineProperties( this,{\r\n            body: { get:function () { return this.children[0] || models.dummy; }},\r\n            wheels:     {get: function() { return this.children[2] || models.dummy; } }\r\n        });\r\n        this.body.material.uniforms.scaleUv.value.x = 0.5;\r\n    };\r\n    return Model;\r\n }();<\/pre>\n<p>then if you call a new Car( scene1 ) and new Car( scene2 ), both will appear in their respective scenes when the first has finished loading. of course\u00a0if a\u00a0Model is already loaded, it\u00a0is instantiated directly using the same\u00a0assets.<\/p>\n<p>the textures class is basically static\u00a0Dictionary that can load textures and\u00a0materials exposes the raw materials that each object will copy.<\/p>\n<p>I used\u00a03 custom materials: a regular &#8220;bitmap&#8221;, a &#8220;blend&#8221; and a &#8220;transition&#8221; material.<\/p>\n<p>the <em>bitmap<\/em> material displays a texture, as we have objects that can have 2 versions of their textures, I added a deltaUv and scaleUv uniforms that let me shift and squash the uvs. \u00a0for example\u00a0the following texture is used on the bag in the last bedroom.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-714\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/bag_room.jpg\" alt=\"bag_room\" width=\"512\" height=\"512\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/bag_room.jpg 512w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/bag_room-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/bag_room-300x300.jpg 300w\" sizes=\"(max-width: 512px) 100vw, 512px\" \/><\/p>\n<p>it can be used with scaleUv = .5 a deltaUv.y = 0 to display the top half\u00a0and deltaUv.y = .5 to display the bottom half, using a deltaUv.x of .5 will switch to the &#8220;night&#8221;(right side) version of both textures.<\/p>\n<p>the material\u00a0has brightness saturation contrast + alpha uniforms, brightness is used to animate the popups on the desktop for instance.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-718\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/desktop.gif\" alt=\"desktop\" width=\"512\" height=\"388\" \/><\/p>\n<p>at some point I added a &#8220;binoculars&#8221; effect hence some extra uniforms\u00a0and a slightly heavier shader.\u00a0that&#8217;s the vertex shader (nothing fancy&#8230;) it&#8217;s the same for all 3 materials.<\/p>\n<pre class=\"lang:js decode:true\">uniform vec2 deltaUv;\r\nuniform vec2 scaleUv;\r\nvarying vec2 vUv;\r\nvoid main()\r\n{\r\n    vUv = ( uv * scaleUv ) + deltaUv;\r\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1. );\r\n}<\/pre>\n<p>and the fragment<\/p>\n<pre class=\"lang:js decode:true\">uniform sampler2D texture;\r\nuniform float brightness;\r\nuniform float saturation;\r\nuniform float contrast;\r\nuniform float alpha;\r\nuniform vec2 resolution;\r\nuniform float radius;\r\nuniform vec3 color;\r\n\r\nvec3 bsc( vec3 color, float brt, float sat, float con)\r\n{\r\n    vec3 brtColor       = color * brt;\r\n    float intensity    = dot( brtColor, vec3(0.2125,0.7154,0.0721) );\r\n    vec3 satColor       = mix( vec3( intensity ), brtColor, sat );\r\n    return mix( vec3( .5 ), satColor, con);\r\n}\r\n\r\nvarying vec2 vUv;\r\nvoid main(){\r\n\r\n    \/\/samples the texture\r\n    vec4 tex = texture2D( texture, vUv );\r\n\r\n    \/\/applies brightness\/saturation\/contrast\r\n    vec3 rgb = bsc( tex.rgb, brightness, saturation, contrast );\r\n\r\n    \/\/the 'binoculars'\r\n    \/\/get a normalized screen position in the range [-1,1]\r\n    vec2 p =  2. * ( gl_FragCoord.xy \/ resolution ) - 1.;\r\n    \/\/with the same aspect ratio as the screen\r\n    p.x *= resolution.x \/ resolution.y;\r\n\r\n    \/\/left eye \/ right eye\r\n    vec2 le = vec2( -.4, 0. );\r\n    vec2 re = vec2(  .4, 0. );\r\n\r\n    \/\/we'll compute a distance to the eyes' locations and a radius\r\n    \/\/beyond which we'll use a flat color d is the union of the 2 eyes\r\n    float d = max(  ( 1.-( distance( p, le ) * radius )  ) ,  ( 1.-( distance( p, re ) * radius )  ) );\r\n\r\n    \/\/if d &lt; .5 -&gt; transparent\r\n    \/\/if d &gt; .5 -&gt; opaque color\r\n    \/\/ .55 - .5 = 0.05 : that's the opaque to transparent gradient's length\r\n    rgb = mix( color, rgb, smoothstep( 0.5, 0.55, d ) );\r\n\r\n    gl_FragColor = vec4( rgb, smoothstep( 0.5, 1., tex.a ) * alpha );\r\n\r\n}<\/pre>\n<p>here&#8217;s a live example of the binoculars effect (press play to start):<br \/>\n<iframe loading=\"lazy\" width=\"100%\" height=\"360\" frameborder=\"0\" src=\"https:\/\/www.shadertoy.com\/embed\/XsG3zm?gui=true&amp;t=10&amp;paused=true&amp;muted=false\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>the <em>blend<\/em> material is used to render animated materials, it basically reads a spritesheet and blends 2 &#8220;frames&#8221; hence its name. at some point we had to choose between morphing two meshes or\u00a0using this little trick.\u00a0I did a quick\u00a0proof of concept which was convincing enough. it is used to animate the shadow of the laptop in the first scene, the bag closing in the second scene and on the beach, it is used for the parasol and the bag.<\/p>\n<p>this shader needs a single texture with X frames, a frameCount and a transition value,\u00a0this is the fragment shader:<\/p>\n<pre class=\"lang:js decode:true\">uniform sampler2D texture;\r\nuniform float frameCount;\r\nuniform float transition;\r\n\r\nvarying vec2 vUv;\r\nvoid main(){\r\n\r\n    \/\/this is a normalized transition value [0, 1]\r\n    float t = transition;\r\n\r\n    \/\/these are the bounds of our animaition\r\n    float lastFrame = frameCount-1.;\r\n    float id = min( lastFrame, floor( frameCount * t ) );\r\n    float nextFrame = id + 1.;\r\n\r\n    \/\/this computes the 'local time' between frame X and frame X + 1\r\n    float delta = 1. \/ frameCount;\r\n    float nt = ( t - ( id * delta ) ) \/ delta;\r\n\r\n    \/\/then we find the pair of uvs to sample\r\n    vec2 uv0 = vUv + vec2( id * delta, 0. );\r\n    vec2 uv1 = vUv + vec2( min( lastFrame * delta, nextFrame * delta ), 0. );\r\n\r\n    \/\/finally we blend the 2 textures with the local time\r\n    gl_FragColor =  mix(  texture2D( texture, uv0 ),  texture2D( texture, uv1 ), nt );\r\n}<\/pre>\n<p>even if it&#8217;s a bit counter intuitive, it remains simple enough. beware to create uvs <em>frrameCount<\/em>\u00a0times smaller for your\u00a0geometry. here&#8217;s a live demo (works best with an actual spritesheet)<br \/>\n<iframe loading=\"lazy\" width=\"100%\" height=\"360\" frameborder=\"0\" src=\"https:\/\/www.shadertoy.com\/embed\/Xdy3zm?gui=true&amp;t=10&amp;paused=true&amp;muted=false\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>finally the <em>transition<\/em> shader is used to\u00a0go from scene to scene, it uses two rendertargets in which the source and destinations scenes are rendered then it performs an <em>opening\u00a0<\/em>based on a normalized ratio [0,1]<\/p>\n<p>the shader is slightly\u00a0more hairy this time as it involves some\u00a0geometry:<\/p>\n<pre class=\"lang:js decode:true \">vec2 project( vec2 p, vec2 a, vec2 b ){\r\n\r\n    float A = p.x - a.x;\r\n    float B = p.y - a.y;\r\n    float C = b.x - a.x;\r\n    float D = b.y - a.y;\r\n    float dot = A * C + B * D;\r\n    float len = C * C + D * D;\r\n    float t = dot \/ len;\r\n    return vec2( a.x + t * C, a.y + t * D );\r\n}\r\nvec2 norm( vec2 p0, vec2 p1 ){\r\n    return normalize(  vec2( -( p1.y - p0.y ), ( p1.x - p0.x ) ) ) * .5;\r\n}\r\n\r\nvoid main()\r\n{\r\n    \/\/screen space uvs\r\n    vec2 uv = gl_FragCoord.xy \/ resolution;\r\n\r\n    vec2 n = norm(p0,p1);\r\n    vec2 c = p0 + (p1 - p0 ) * .5;\r\n    vec2 a = c - n;\r\n    vec2 b = c + n;\r\n\r\n    \/\/project the screen coord onto the p0\/p1 axis \r\n    vec2 pp = project( uv, b, a ) - c;\r\n    \/\/gets a signed distance\r\n    float d = pp.x - uv.x + pp.y - uv.y ;\r\n    float s = sign( d );\r\n    \/\/use it to index the \"opening\"\r\n    pp *= ( s * exp( d * transition ) );\r\n    \/\/len is our \"physical\" transition ratio ; our gradient value\r\n    float len = ( length( pp ) \/ length( c ) );\r\n    float t = smoothstep( len - alphaThreshold,  len + alphaThreshold, transition );\r\n\r\n    \/\/the source texture alpha must disappear \r\n    vec4 tex0 = texture2D( texture0, uv );\r\n    \/\/this alters the opacity of the source and create a gradient blend\r\n    tex0.a = step( t, len ) + step( len, t );\r\n\r\n    \/\/nothing special about the destination texture\r\n    vec4 tex1 = texture2D( texture1, uv );\r\n    \/\/just a linear interpolation\r\n    vec4 tex = mix( tex0, tex1, t );\r\n    gl_FragColor = vec4( tex.rgb, tex.a );\r\n\r\n}<\/pre>\n<p>p0 and p1 are the 2 handles used when you drag the slider on the screen.<br \/>\nhere&#8217;s not exactly the same but\u00a0the demo I used as a starting point for this shader, click drag around, press play to view the transition itself.<br \/>\n<iframe loading=\"lazy\" width=\"100%\" height=\"360\" frameborder=\"0\" src=\"https:\/\/www.shadertoy.com\/embed\/ldcGWl?gui=true&amp;t=10&amp;paused=true&amp;muted=false\" allowfullscreen=\"allowfullscreen\"><\/iframe><br \/>\nthat&#8217;s all there is to materials in the whole website :)<\/p>\n<p>now there was a problem: the character.<\/p>\n<p>it was the second thing I tackled right after the meshes ; I thought we could use some sort of 2d animation system <em>\u00e0 la\u00a0After Effects.<br \/>\n<\/em>the source animation was done in AE exported as a 23000+ * 500 pixels spritesheet which\u00a0triggered\u00a0a loud\u00a0&#8220;no\u00a0way!&#8221;. indeed, there had to be a lighter way to recompose such simple motions. I\u00a0don&#8217;t have After Effects and would have had too\u00a0little time to study the scripting language. but I still have an old version of Flash so I did a quick prototype\u00a0; I\u00a0moved different parts of the body, exported the clips transform matrices at each frame along with a spritesheet of the body parts. this gave me 3 lists:<\/p>\n<pre class=\"lang:js decode:true \">\/\/the clips uv coords on the texture\r\nvar uvs = {\r\n    jum_avb: {x:49,y:237,w:50,h:61},\r\n    jum_epa: {x:206,y:237,w:40,h:32},\r\n    jum_body: {x:99,y:237,w:107,h:230},\r\n    size: {w:256,h:512}\r\n};\r\n\/\/the clips dimensions\r\nvar sizes = {\r\n    jum_epa : [ 40,32 ],\r\n    jum_avb : [ 50,61 ],\r\n    jum_body : [ 107,230 ]\r\n};\r\n\/\/the clips transform matrices in time (a,b,c,d,tx,ty,[...] )\r\nvar frames = {\r\n    jum_epa : [ 0.9999847412109375,-0.0010528564453125,0.0010528564453125,0.9999847412109375,322.7,168.35, [...]],\r\n    jum_avb : [ 0.999542236328125,0.0269622802734375,-0.0269622802734375,0.999542236328125,320.1,191.8, [...]],\r\n    jum_body : [ 1,0,0,1,241.8,132.4]\r\n};<\/pre>\n<p>and the final spritesheet<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-708\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/ref_perso.png\" alt=\"ref_perso\" width=\"256\" height=\"512\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/ref_perso.png 256w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/ref_perso-150x300.png 150w\" sizes=\"(max-width: 256px) 100vw, 256px\" \/><br \/>\nthen on the JS side, I rebuild the clips and during the update, I recompose the transforms according to a normalized time and a frame count (much like the\u00a0blend material).<\/p>\n<p>this sounds complex but is in fact pretty easy (and performant):<\/p>\n<pre class=\"lang:js decode:true\">for( var i = 0; i&lt; this.limbs.length; i++ )\r\n{\r\n    \/\/finds the frame\r\n    var l = this.limbs[i];\r\n    var id = parseInt( parseInt( time * ( l.frames.length - 1 )  ) \/ 6 ) * 6;\r\n\r\n    \/\/applies the translation rotation to the 3D mesh\r\n    l.position.x = l.frames[id+4] - sceneWidth \/ 2;\r\n    l.position.y = sceneHeight\/2 - l.frames[id+5];\r\n    l.rotation.z = Math.atan2(l.frames[id+2], l.frames[id+3] );\r\n}\r\n<\/pre>\n<p>apart from the transform matrices, we need the scene dimensions (512*512 in this case) to offset\u00a0the clips around the center of the 3D object&#8217;s center, that&#8217;s not mandatory but was more convenient.<br \/>\nnow this was all well and good <strong>BUT<\/strong> it didn&#8217;t work&#8230;<\/p>\n<p>rendering this object with a perspective camera will cause 2 issues:<\/p>\n<ol>\n<li>it will apply a perspective transform that\u00a0will\u00a0&#8220;disconnect&#8221;\u00a0 the limbs<\/li>\n<li>transparencies will screw up<\/li>\n<\/ol>\n<p>1 indeed Flash or whatever animation software usually works with an orthographic projection system, this allows to animate various elements without bothering with their 3D depth ; their position in the display list (or layer) gives them their render order and the occlusion can be computed quite easily.<\/p>\n<p>2 was more\u00a0problematic, there are <a href=\"http:\/\/stackoverflow.com\/questions\/15994944\/transparent-objects-in-threejs\" target=\"_blank\">several ways to deal with transparency\u00a0in THREE<\/a>\u00a0none of which would work in my particular case : to render the element in the proper order (1 binoculars &gt;\u00a02 shoulder\u00a0&gt; 3\u00a0body ) I had to add a consequent\u00a0depth between each mesh which made the perspective projection very visible and disconnected the limbs.<br \/>\nif I reduced the space between them, I ended up with transparency collisions ; a\u00a0mesh\u00a0supposed to be behind another would punch it&#8217;s alpha transparency through the one before it, even if I used the renderer&#8217;s logarithmicDepthBuffer and \/ or the sortObjects flag and \/ or the\u00a0objects&#8217; renderOrder property, there was no way to properly reproduce the animation.<\/p>\n<p>the\u00a0solution was &#8211; of course &#8211; to render this 3D objects into a\u00a0renderTarget with an orthographic camera and use it as a texture for a quad mesh the size of the scene (512*512). no more depth problems and no more alpha transparency problem.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-710\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/animation.gif\" alt=\"animation\" width=\"600\" height=\"600\" \/><\/p>\n<p>actually\u00a0there is one transparency problem left : the animation is rendered on a black background causing a dark outline, I\u00a0didn&#8217;t spend enough time on this issue but rather dodged it by thresholding the alpha in the material shader<\/p>\n<pre class=\"lang:js decode:true \"> smoothstep( 0.5, 1., tex.a ) * alpha<\/pre>\n<p>there are\u00a0subtle interactions: in the first scene you can play with the &#8216;pen\u00a0pot&#8217; and with the ball or the bag on the beach. this is a simple raycasting test that triggers Tweens with a Bounce easeOut and random rotations&#8230; no physics or black magic here, yet it adds a playful touch (I literally spent 30 minutes playing with the pens on the desktop ^^).<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-720\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/pens.gif\" alt=\"pens\" width=\"512\" height=\"388\" \/><\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-719\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/ball.gif\" alt=\"ball\" width=\"512\" height=\"388\" \/><\/p>\n<p>the rest of the website was taken care of by Etienne and Yannice\u00a0&#8211;\u00a0imho &#8211; the soundtrack and sound FX are\u00a0pretty awesome\u00a0and serve the whole experience.<\/p>\n<p>to sum it up, I really enjoyed working on this project,\u00a0it is far from perfect, lots of things could be optimized further but all in all this short journey\u00a0has a unique\u00a0look &amp; feel.<br \/>\nhope you&#8217;ll enjoy it much as I do :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>last december, the nice people at\u00a0Cher Ami (&#8220;dear friend&#8221; in french) contacted me to give them a hand on a little animation \/ wish card they wanted to release in january, I gladly accepted which gave birth to\u00a0this refreshing little thing\u00a0http:\/\/www.ashortjourney.com\/ Cher Ami\u00a0has qualified people in house, a\u00a0creative director, a\u00a0copywriter\/musician, a front end developer (Etienne &#8230; <span class=\"more\"><a class=\"more-link\" href=\"http:\/\/barradeau.com\/blog\/?p=695\">[Read more&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":711,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"sharing_disabled":false,"spay_email":"","jetpack_publicize_message":""},"categories":[3],"tags":[],"jetpack_featured_media_url":"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2016\/01\/cover.jpg","jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/p4oXhx-bd","_links":{"self":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/695"}],"collection":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=695"}],"version-history":[{"count":21,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/695\/revisions"}],"predecessor-version":[{"id":723,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/695\/revisions\/723"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/711"}],"wp:attachment":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=695"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=695"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=695"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}