Thursday 26 July 2018

View Independent Animations

For some simple animations (squares moving through a scenario for example) one could hesitate between drawing these squares to a canvas or using DOM divs. The animation logic should be independent of the Canvas/Divs, which would be just 2 different views. I've adapted the TweenJS sample that I posted some days ago so that now it can either draw on a Canvas or move divs around. The new sample is here. The top animation is using the Canvas, and the one at the bottom is using Divs.

The idea is simple. I keep the animation logic in an AnimationSystem class that holds a list of the items to animate. requestAnimationFrame will ask AnimationSystem to get updated, which means that it will call to the differen AnimationItems to update its position and draw to the screen. I have an AnimationItem base class and a derived TweenJSAnimationItem that contain all the animation logic, they correspond to the "Model-Controller". They delegate the drawing logic to a View object, so we have a Canvas View and a DOM View. As these View objects have a single function, drawing, I've opted for directly using functions for them, rather than classes. As these functions need state (the canvas context or the DIV being animated) I'm wrapping them in closures. Let's see their code:

//callable interface
interface DrawItemFunc{
    (currentPosition:Vector, size:Number, color:string, alpha:number):void
}


class ViewFunctions{
    //Factory function returning a closure that traps the context, height and width
    static SystemCanvasViewFactory(height:number, width:number, ctx:CanvasRenderingContext2D):()=>void{
        return function SystemCanvasView(){
            ctx.clearRect(0,0, width, height);
        };
    }

    static SystemDOMView(){
        //nothing to do here, only at the ItemView level
    }

    //------------------------------------------

    //Factory function returning a closure trapping the canvas context
    static ItemCanvasViewFactory(ctx:CanvasRenderingContext2D):DrawItemFunc{
        return function ItemCanvasView(currentPosition:Vector, size:Number, color:string, alpha:number){
            ctx.beginPath();
            ctx.rect(currentPosition.x, currentPosition.y, size, size);
            ctx.closePath();
            ctx.fillStyle = color;
            ctx.globalAlpha = alpha;
            ctx.fill();
        };
    }

    //Factory function returning a closure trapping the DOM item
    static ItemDOMViewFactory(domDiv):DrawItemFunc{
        return function ItemDOMView(currentPosition:Vector, size:Number, color:string, alpha:number){
            Object.assign(domDiv.style,{
                //position: "absolute",
                //border: "0.1px solid red",
                backgroundColor: color,
                opacity: alpha,
                left: currentPosition.x + "px",
                top: currentPosition.y + "px",
                width: size + "px",
                height: size + "px"
            });
        };
    }
}

Notice that for the transparency, in the Canvas world we talk about context.globalAlpha, while for DOM items we use the style.opacity property.

For the canvas case, before drawing each AnimationItem via the ItemCanvasView closure, I need to clear up the canvas, this is done by the view associated to the AnimationSystem itself. For the DOM case, as we move items rather than redrawing them, there's nothing to do at the AnimationSystem level.

The AnimationItem and TweenJSAnimationItem contain the animation logic and end up invoking the View closures (pointed by the drawingFunc field).

abstract class AnimationItem{
 currentPosition:Vector;
 size:number;
 color:string;
 alpha:number;
    
 moving:boolean;
 drawingFunc:DrawItemFunc;
 resolveCurrentAnimationFunc:any;

 public abstract update():void;
 
 public draw():void{
  this.drawingFunc(this.currentPosition, this.size, this.color, this.alpha); 
 }
 
 public animate():Promise{
  this.moving = true;
        return new Promise((res, rej) => {
            this.resolveCurrentAnimationFunc = res;
        });
    }
}

class TweenJSAnimationItem extends AnimationItem{
    tweenGroup: any;
    movementTween: TWEEN.Tween;
    tweenStep:number;

    constructor(currentPosition:Vector, size:number=5, color:string="red", 
        alpha:number=1, drawingFunc:DrawItemFunc, logger)
        {
        
        super();
        this.drawingFunc = drawingFunc;
        this.currentPosition = currentPosition;
        this.size = size;
        this.color = color;
        this.alpha = alpha;
        this.moving = false;
        this.tweenStep = 0;
        this.logger = logger;
    }

    public animateDiagonal(endPosition:Vector):Promise{
        this.tweenStep = 0;
        this.tweenGroup = new TWEEN.Group();
        this.movementTween = new TWEEN.Tween(this.currentPosition, this.tweenGroup) // Create a new tween that modifies 'coords'.
            .to({ x: endPosition.x, y: endPosition.y }, 40) // Move to (300, 200) in 1 second.
            .easing(TWEEN.Easing.Quadratic.Out)
            .onComplete(() => {
                console.log("movementTween completed");
                this.moving = false;
                //cleaning up
                this.tweenGroup = null;
                this.movementTween = null;
                //I think this is not necessary, the engine itself takes care of it once the Tween is finished
                //TWEEN.remove(this.movementTween);
                this.resolveCurrentAnimationFunc();
            });
        
        let growTween = new TWEEN.Tween(this, this.tweenGroup) // Create a new tween that modifies 'coords'.
            .to({ size: this.size * 3, alpha: 0.4}, 20)
            //.easing(TWEEN.Easing.Quadratic.Out)
            .onComplete(() => console.log("growTween completed, " + this.size)); 

        let shrinkTween = new TWEEN.Tween(this, this.tweenGroup) // Create a new tween that modifies 'coords'.
            .to({ size: this.size, alpha: 1}, 20)
            //.easing(TWEEN.Easing.Quadratic.Out)
            .onComplete(() => console.log("shrinkTween completed, " + this.size)); 

        growTween.chain(shrinkTween);

        this.movementTween.start(0); // Start the tween immediately.
        growTween.start(0)
        
        return this.animate();
    }

    public animateFall(endY:number):Promise{
        this.tweenStep = 0;
        this.tweenGroup = new TWEEN.Group();
        this.movementTween = new TWEEN.Tween(this.currentPosition, this.tweenGroup) // Create a new tween that modifies 'coords'.
            .to({ x: this.currentPosition.x, y: endY }, 40) // Move to (300, 200) in 1 second.
            .easing(TWEEN.Easing.Quadratic.Out)
            .onComplete(() => {
                console.log("movementTween completed");
                this.moving = false;
                this.resolveCurrentAnimationFunc();
            });
        
        

        this.movementTween.start(0); // Start the tween immediately.

        return this.animate();
    }

    public update():void{
        if(!this.moving){
            return;
        }

        this.tweenGroup.update(this.tweenStep++);
    }

    //implemente in the base class
 // public draw():void{
    // }

   
}

Notice also that for drawing on the canvas we'll invoke ItemCanvasViewFactory just once, as the same instance of the view closure (trapping the canvas context) will be used by all the AnimationItem's. On the contrary, for the divs animation, each AnimationItem will use a different view closure that traps a different div, so we'll invoke ItemDOMViewFactory once for each AnimationItem in the system.

Sunday 22 July 2018

Energy Dependence

Even the most stupid person in the world can on occasion say something that is correct. That's what happened last week when the Trump shitbrain said that it was ridiculous that Germany was increasing even more its dependence on Russian Gas. For sure that's a very correct statement, though one could wonder why a Putin lover and Europe hater like the Trump asshole would care about such undesirable dependence. For the biggest enemy of Europe (along with Russia and the Islamists) the weaker Europe is the better... Well, Trump is just a psychopathic megalomaniac piece of shit that can say one thing and its contrary with just the seconds difference needed to tweet his brain farts...

This increased German dependence (that unfortunately applies to other Western European countries) on Russian gas is related to the construction of the Nord Stream 2 gas pipeline that will go under the Baltic Sea directly from Russia to Germany. There's already a Nord Stream gas pipeline doing the same, so this will further increase the already worrying European reliance on Russian gas. Depending on Russia is as castastrophic as depending on Islamist countries, indeed, it's even worse. Golf countries are invading Europe by educating and funding Islamists that live or will live here, but this is a relatively slow invasion that we still could easily fight if we had the bollocks to do it (massive deportation of binational Islamists, for life incarceration for the others, and capital punishment for those with links to terrorism). On the other side, if Russia decides to military invade the Baltic or Nordic countries, fighting back would be quite more complicated. Russia is our enemy both in a geopolitical sense and in a cultural sense (while Western Europe is supposed to represent freedom, equality and opennes, Russia stands for ultra-nationalism, conservadurism, racism, homophobia...) and being fully aware of this is essential for the survival of Europe as we know it.

This article gives a pretty good insight of how dangerous this new pipeline is:

If it is built, the EU would become extremely dependent on a single gas supplier - Gazprom, an entity under the full control of Russian leader Vladimir Putin.

Europe already imports 39 percent of its gas from Russia. After NS2, 80 percent of Russian gas imports would be concentrated in one route. In Germany itself, the share of Russian gas would increase from 40 percent to 60 percent.

Beyond Germany, 12 EU member states depend on Russia for 75 percent or more of their gas. After NS2, the level of their dependence would also go up.

Natural gas is supposed to be a bit cleaner than other fossil fuels, but does its slightly smaller environmental effect justify to keep us as slaves of Fascist or Islamo-Fascist states like Russia or Qatar???
The answer is a big NO for me, so:

For sure Europe needs to continue to invest on renovable energies (which clearly involves the somehow despised and forgotten HydroElectric power) but until the illusory day when they can fullfill all our needs, we'll have to continue to burn Polish cheap coal and leverage nuclear power.

By the way, you can watch this documentary about the damned new pipeline

Wednesday 18 July 2018

Equipe de France

If 20 years ago someone had told me how obsessed with race our world would be in 2018 I would have rejected to believe it, but sadly, part of this world is as obsessed with race and ancestry now as it was in the worst times of history. One could think that it's just the fault of the White far-right (Russia, Eastern Europe, Trump, and their satellites in Western Europe), but "Blacks", "Latinos" and the far-left have contributed to this mess in equal doses.

The kind of comments that have been stir up by the victory of the French football team in the World Cup are sickening. It's not just the Far-Right who considers the French team an "African team", but also African and Afro-American "personalities", or the Nicolas Maduro idiot. So some of the African people that complain about racism are as obsessed with race and ancestry as any of the very white "official racists". So for these people a Human being is not free to decide to what society he wants to belong, it's just his skin color or the birth place of his ancestors who determines that, that's all, the individual has no rights, it's only the "race", who has rights... This is revolting.

For the most part I don't give a shit about football, and I have little idea of the feelings of identity of the players of African descent in the French team. Maybe some of them love France with all their heart, maybe others are anarchists and don't care about nations, and yes, unfortunately it could happen that some of them is an "anticolonial racaille" that identifies himself with the country of his ancestors (from which they fled due to poverty) rather than with the country that has provided him with food and education... I honestly don't know, and hence I apply my general rule, if you are born and/or have lived most of your life in one country, and you give no signs of disrespecting that country, you are a citizen of that country, regardless of your physical traits or those of your ancestors. That's quite simple.

Additionally, these "African nationalists" that want to appropriate the victory of the French team based on race and ancestry are missing a huge point. Would these guys have become good players if they had lived in Africa, rather than in France, with the Western welfare state and being trained by French (of whatever color) trainers?

Hopefully, all this crap has brought up some excellent reactions. Benjamin Mendy, from the French football team, came across a stupid tweet from some sport magazine that associated to different players the flag of their alleged "country of origin", he fixed it by assigning the French flag to all of them.


The (born and raised) French Basketball player (born to a Cameroonian father) Nicolas Batum went even further (sorry for my bad translation).

Sorry for my language, but those that say "Bravo Africa for your victory", go fuck yourself!. I proudly wear the blue t-shirt with "France" written on it, I've won several trophies and medals with the French team since I was 15, I sing La Marseillaise, when I am in the USA I cry high and strong that I am French. Then, yes, I have a Cameroonian father and a Cameroonian name, but we all have fought and played for France because we have been born here, we have grown here, we have learnt our sport here, we are proud of having the French nationality. We have won for France, for the French youth that looks at us and aspire to do the same and bear high the French colors. Let's be proud of that. Vive la république et vive la France!

When one sees this growing and deviated obsession with race, the move from the French National Assembly to remove the word race from the French constitution seems particularly appropriate.

Sunday 15 July 2018

TweenJS and Canvas Basic Sample

I've been playing around a bit more with animations on the HTML5 Canvas. While in this previous sample I was doing the basic movement logic myself, for this new experiment I wanted to use some library that would allow me to use easing equations. After some basic investigation I've opted for tweenJS. I'm not animating DOM elements, but shapes that I draw on the canvas, so I needed a library that would accept any object and update its values on request based on the selected movement/evolution logic, and then I would take care of drawing it. TweenJS fits perfectly into this mindset.

As in the previous sample, I'm leveraging the power of async/await to make it simple to launch my animations in sequence. There is not much to explain, just launch your debugger to see the typescript code.

while (true){
        for (let y=0; y<itemsMatrix.length; y++){
            for (let x=0; x<itemsMatrix[y].length; x++){
                let item:TweenJSSquare = itemsMatrix[y][x];
                await item.animateDiagonal(new Vector(canvas.width - ((x + 1) * size), size * y )); //endPosition:Vector,);
            }
        }

        for (let y=0; y<itemsMatrix.length; y++){
            for (let x=0; x<itemsMatrix[y].length; x++){
                let item:TweenJSSquare = itemsMatrix[y][x];
                await item.animateFall(canvas.height - (size * (y + 1)));
            }
        }

        for (let y=0; y<itemsMatrix.length; y++){
            for (let x=0; x<itemsMatrix[y].length; x++){
                let item:TweenJSSquare = itemsMatrix[y][x];
                await item.animateDiagonal(new Vector((x * size), size * y )); //endPosition:Vector,);
            }
        }
    
        for (let y=0; y<itemsMatrix.length; y++){
            for (let x=0; x<itemsMatrix[y].length; x++){
                let item:TweenJSSquare = itemsMatrix[y][x];
                await item.animateFall(canvas.height - (size * (y + 1)));
            }
        }
    }