Browse Source

Merge pull request #1144 from nockawa/engine2d

 Canvas2D: Animation and ActionManager support + bugs fixes
David Catuhe 9 years ago
parent
commit
2e526fda37

+ 4 - 0
src/Actions/babylon.actionManager.ts

@@ -44,6 +44,10 @@
         public static CreateNewFromScene(scene: Scene, evt: Event): ActionEvent {
             return new ActionEvent(null, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt);
         }
+
+        public static CreateNewFromPrimitive(prim: any, pointerPos: Vector2, evt?: Event, additionalData?: any): ActionEvent {
+            return new ActionEvent(prim, pointerPos.x, pointerPos.y, null, evt, additionalData);
+        }
     }
 
     /**

+ 36 - 8
src/Animations/babylon.animation.ts

@@ -124,6 +124,8 @@
                 dataType = Animation.ANIMATIONTYPE_VECTOR2;
             } else if (from instanceof Color3) {
                 dataType = Animation.ANIMATIONTYPE_COLOR3;
+            } else if (from instanceof Size) {
+                dataType = Animation.ANIMATIONTYPE_SIZE;
             }
 
             if (dataType == undefined) {
@@ -293,6 +295,10 @@
             return Vector2.Lerp(startValue, endValue, gradient);
         }
 
+        public sizeInterpolateFunction(startValue: Size, endValue: Size, gradient: number): Size {
+            return Size.Lerp(startValue, endValue, gradient);
+        }
+
         public color3InterpolateFunction(startValue: Color3, endValue: Color3, gradient: number): Color3 {
             return Color3.Lerp(startValue, endValue, gradient);
         }
@@ -405,6 +411,15 @@
                                 case Animation.ANIMATIONLOOPMODE_RELATIVE:
                                     return this.vector2InterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
                             }
+                        // Size
+                        case Animation.ANIMATIONTYPE_SIZE:
+                            switch (loopMode) {
+                                case Animation.ANIMATIONLOOPMODE_CYCLE:
+                                case Animation.ANIMATIONLOOPMODE_CONSTANT:
+                                    return this.sizeInterpolateFunction(startValue, endValue, gradient);
+                                case Animation.ANIMATIONLOOPMODE_RELATIVE:
+                                    return this.sizeInterpolateFunction(startValue, endValue, gradient).add(offsetValue.scale(repeatCount));
+                            }
                         // Color3
                         case Animation.ANIMATIONTYPE_COLOR3:
                             switch (loopMode) {
@@ -453,15 +468,16 @@
                 destination = this._target;
             }
 
+            if (!this._originalBlendValue) {
+                if (destination[path].clone) {
+                    this._originalBlendValue = destination[path].clone();
+                } else {
+                    this._originalBlendValue = destination[path];
+                }
+            }
+
             // Blending
             if (this.enableBlending && this._blendingFactor <= 1.0) {
-                if (!this._originalBlendValue) {
-                    if (destination[path].clone) {
-                        this._originalBlendValue = destination[path].clone();
-                    } else {
-                        this._originalBlendValue = destination[path];
-                    }
-                }
 
                 if (this._originalBlendValue.prototype) { // Complex value
                     
@@ -482,7 +498,7 @@
             }
 
             if (this._target.markAsDirty) {
-                this._target.markAsDirty(this.targetProperty);
+                this._target.markAsDirty(this.targetProperty, this._originalBlendValue);
             }
         }
 
@@ -553,6 +569,9 @@
                             // Vector2
                             case Animation.ANIMATIONTYPE_VECTOR2:
                                 this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
+                            // Size
+                            case Animation.ANIMATIONTYPE_SIZE:
+                                this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                             // Color3
                             case Animation.ANIMATIONTYPE_COLOR3:
                                 this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
@@ -586,6 +605,10 @@
                     case Animation.ANIMATIONTYPE_VECTOR2:
                         offsetValue = Vector2.Zero();
                         break;
+                    // Size
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        offsetValue = Size.Zero();
+                        break;
                     // Color3
                     case Animation.ANIMATIONTYPE_COLOR3:
                         offsetValue = Color3.Black();
@@ -676,6 +699,7 @@
         private static _ANIMATIONTYPE_MATRIX = 3;
         private static _ANIMATIONTYPE_COLOR3 = 4;
         private static _ANIMATIONTYPE_VECTOR2 = 5;
+        private static _ANIMATIONTYPE_SIZE = 6;
         private static _ANIMATIONLOOPMODE_RELATIVE = 0;
         private static _ANIMATIONLOOPMODE_CYCLE = 1;
         private static _ANIMATIONLOOPMODE_CONSTANT = 2;
@@ -692,6 +716,10 @@
             return Animation._ANIMATIONTYPE_VECTOR2;
         }
 
+        public static get ANIMATIONTYPE_SIZE(): number {
+            return Animation._ANIMATIONTYPE_SIZE;
+        }
+
         public static get ANIMATIONTYPE_QUATERNION(): number {
             return Animation._ANIMATIONTYPE_QUATERNION;
         }

+ 108 - 14
src/Canvas2d/babylon.canvas2d.ts

@@ -130,6 +130,7 @@
             this._hierarchySiblingZDelta = this._hierarchyLevelZFactor / this._hierarchyLevelMaxSiblingCount;
             this._primPointerInfo = new PrimitivePointerInfo();
             this._capturedPointers = new StringDictionary<Prim2DBase>();
+            this._pickStartingPosition = Vector2.Zero();
 
             this.setupGroup2D(this, null, name, Vector2.Zero(), size, this._cachingStrategy===Canvas2D.CACHESTRATEGY_ALLGROUPS ? Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE : Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY);
 
@@ -208,7 +209,7 @@
             }
 
             this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
-            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerGotCapture);
+            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerGotCapture, null);
 
             this._capturedPointers.add(pointerId.toString(), primitive);
             return true;
@@ -230,7 +231,7 @@
             }
 
             this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
-            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerLostCapture);
+            this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerLostCapture, null);
             this._capturedPointers.remove(pointerId.toString());
             return true;
         }
@@ -281,13 +282,13 @@
             // Analyze the pointer event type and fire proper events on the primitive
 
             if (eventData.type === PointerEventTypes.POINTERWHEEL) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMouseWheel);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMouseWheel, <MouseWheelEvent>eventData.event);
             } else if (eventData.type === PointerEventTypes.POINTERMOVE) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMove);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMove, <PointerEvent>eventData.event);
             } else if (eventData.type === PointerEventTypes.POINTERDOWN) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerDown);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerDown, <PointerEvent>eventData.event);
             } else if (eventData.type === PointerEventTypes.POINTERUP) {
-                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerUp);
+                this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerUp, <PointerEvent>eventData.event);
             }
         }
 
@@ -373,13 +374,13 @@
                 // Notify the previous "over" prim that the pointer is no longer over it
                 if ((capturedPrim && capturedPrim===prevPrim) || (!capturedPrim && prevPrim)) {
                     this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
-                    this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut);
+                    this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut, null);
                 }
 
                 // Notify the new "over" prim that the pointer is over it
                 if ((capturedPrim && capturedPrim === actualPrim) || (!capturedPrim && actualPrim)) {
                     this._primPointerInfo.updateRelatedTarget(actualPrim, this._actualOverPrimitive.intersectionLocation);
-                    this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver);
+                    this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver, null);
                 }
             }
 
@@ -415,8 +416,8 @@
             console.log(debug);
         }
 
-        private _bubbleNotifyPrimPointerObserver(prim: Prim2DBase, mask: number) {
-            let pii = this._primPointerInfo;
+        private _bubbleNotifyPrimPointerObserver(prim: Prim2DBase, mask: number, eventData: any) {
+            let ppi = this._primPointerInfo;
 
             // In case of PointerOver/Out we will first notify the children (but the deepest to the closest) with PointerEnter/Leave
             if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) !== 0) {
@@ -432,11 +433,12 @@
 
                     // Exec the observers
                     this._debugExecObserver(cur, mask);
-                    cur._pointerEventObservable.notifyObservers(pii, mask);
+                    cur._pointerEventObservable.notifyObservers(ppi, mask);
+                    this._triggerActionManager(cur, ppi, mask, eventData);
 
                     // Bubble canceled? If we're not executing PointerOver or PointerOut, quit immediately
                     // If it's PointerOver/Out we have to trigger PointerEnter/Leave no matter what
-                    if (pii.cancelBubble) {
+                    if (ppi.cancelBubble) {
                         if ((mask & (PrimitivePointerInfo.PointerOver | PrimitivePointerInfo.PointerOut)) === 0) {
                             return;
                         }
@@ -454,13 +456,13 @@
                 // Trigger a PointerEnter corresponding to the PointerOver
                 if (mask === PrimitivePointerInfo.PointerOver) {
                     this._debugExecObserver(cur, PrimitivePointerInfo.PointerEnter);
-                    cur._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerEnter);
+                    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerEnter);
                 }
 
                 // Trigger a PointerLeave corresponding to the PointerOut
                 else if (mask === PrimitivePointerInfo.PointerOut) {
                     this._debugExecObserver(cur, PrimitivePointerInfo.PointerLeave);
-                    cur._pointerEventObservable.notifyObservers(pii, PrimitivePointerInfo.PointerLeave);
+                    cur._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerLeave);
                 }
 
                 // Loop to the parent
@@ -468,6 +470,95 @@
             }
         }
 
+        private _triggerActionManager(prim: Prim2DBase, ppi: PrimitivePointerInfo, mask: number, eventData) {
+
+            // Process Trigger related to PointerDown
+            if ((mask & PrimitivePointerInfo.PointerDown) !== 0) {
+                // On pointer down, record the current position and time to be able to trick PickTrigger and LongPressTrigger
+                this._pickStartingPosition = ppi.primitivePointerPos.clone();
+                this._pickStartingTime = new Date().getTime();
+                this._pickedDownPrim = null;
+
+                if (prim.actionManager) {
+                    this._pickedDownPrim = prim;
+                    if (prim.actionManager.hasPickTriggers) {
+                        let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+
+                        switch (eventData.button) {
+                        case 0:
+                            prim.actionManager.processTrigger(ActionManager.OnLeftPickTrigger, actionEvent);
+                            break;
+                        case 1:
+                            prim.actionManager.processTrigger(ActionManager.OnCenterPickTrigger, actionEvent);
+                            break;
+                        case 2:
+                            prim.actionManager.processTrigger(ActionManager.OnRightPickTrigger, actionEvent);
+                            break;
+                        }
+                        prim.actionManager.processTrigger(ActionManager.OnPickDownTrigger, actionEvent);
+                    }
+
+                    if (prim.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
+                        window.setTimeout(() => {
+                            let ppi = this._primPointerInfo;
+                            let capturedPrim = this.getCapturedPrimitive(ppi.pointerId);
+                            this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null);
+
+                            let ii = new IntersectInfo2D();
+                            ii.pickPosition = ppi.canvasPointerPos.clone();
+                            ii.findFirstOnly = false;
+                            this.intersect(ii);
+
+                            if (ii.isIntersected) {
+                                let iprim = ii.topMostIntersectedPrimitive.prim;
+                                if (iprim.actionManager) {
+                                    if (this._pickStartingTime !== 0 && ((new Date().getTime() - this._pickStartingTime) > ActionManager.LongPressDelay) && (Math.abs(this._pickStartingPosition.x - ii.pickPosition.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ii.pickPosition.y) < ActionManager.DragMovementThreshold)) {
+                                        this._pickStartingTime = 0;
+                                        iprim.actionManager.processTrigger(ActionManager.OnLongPressTrigger, ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData));
+                                    }
+                                }
+                            }
+                        }, ActionManager.LongPressDelay);
+                    }
+                }
+            }
+
+            // Process Triggers related to Pointer Up
+            else if ((mask & PrimitivePointerInfo.PointerUp) !== 0) {
+                this._pickStartingTime = 0;
+
+                let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                if (prim.actionManager) {
+                    // OnPickUpTrigger
+                    prim.actionManager.processTrigger(ActionManager.OnPickUpTrigger, actionEvent);
+
+                    // OnPickTrigger
+                    if (Math.abs(this._pickStartingPosition.x - ppi.canvasPointerPos.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ppi.canvasPointerPos.y) < ActionManager.DragMovementThreshold) {
+                        prim.actionManager.processTrigger(ActionManager.OnPickTrigger, actionEvent);
+                    }
+                }
+
+                // OnPickOutTrigger
+                if (this._pickedDownPrim && this._pickedDownPrim.actionManager && (this._pickedDownPrim !== prim)) {
+                    this._pickedDownPrim.actionManager.processTrigger(ActionManager.OnPickOutTrigger, actionEvent);
+                }
+            }
+
+            else if ((mask & PrimitivePointerInfo.PointerOver) !== 0) {
+                if (prim.actionManager) {
+                    let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(ActionManager.OnPointerOverTrigger, actionEvent);
+                }
+            }
+
+            else if ((mask & PrimitivePointerInfo.PointerOut) !== 0) {
+                if (prim.actionManager) {
+                    let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
+                    prim.actionManager.processTrigger(ActionManager.OnPointerOutTrigger, actionEvent);
+                }
+            }
+        }
+
         _notifChildren(prim: Prim2DBase, mask: number) {
             let pii = this._primPointerInfo;
 
@@ -673,6 +764,9 @@
         private _updateRenderId: number;
         private _intersectionRenderId: number;
         private _hoverStatusRenderId: number;
+        private _pickStartingPosition: Vector2;
+        private _pickedDownPrim: Prim2DBase;
+        private _pickStartingTime: number;
         private _previousIntersectionList: Array<PrimitiveIntersectedInfo>;
         private _actualIntersectionList: Array<PrimitiveIntersectedInfo>;
         private _previousOverPrimitive: PrimitiveIntersectedInfo;

+ 43 - 2
src/Canvas2d/babylon.prim2dBase.ts

@@ -327,6 +327,14 @@
             this.origin = new Vector2(0.5, 0.5);
         }
 
+
+        public get actionManager(): ActionManager {
+            if (!this._actionManager) {
+                this._actionManager = new ActionManager(this.owner.scene);
+            }
+            return this._actionManager;
+        }
+
         /**
          * From 'this' primitive, traverse up (from parent to parent) until the given predicate is true
          * @param predicate the predicate to test on each parent
@@ -371,12 +379,39 @@
             return this._id;
         }
 
+        /**
+         * Metadata of the position property
+         */
         public static positionProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the rotation property
+         */
         public static rotationProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the scale property
+         */
         public static scaleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the origin property
+         */
         public static originProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the levelVisible property
+         */
         public static levelVisibleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the isVisible property
+         */
         public static isVisibleProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the zOrder property
+         */
         public static zOrderProperty: Prim2DPropInfo;
 
         @instanceLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
@@ -686,6 +721,11 @@
                 return false;
             }
 
+            if (!this._actionManager) {
+                this._actionManager.dispose();
+                this._actionManager = null;
+            }
+
             // If there's a parent, remove this object from its parent list
             if (this._parent) {
                 let i = this._parent._children.indexOf(this);
@@ -716,7 +756,7 @@
         }
 
         public _needPrepare(): boolean {
-            return (this.isVisible || this._visibilityChanged) && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
+            return this._visibilityChanged && (this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep));
         }
 
         public _prepareRender(context: Render2DContext) {
@@ -806,7 +846,7 @@
                 this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
 
                 // Detect a change of visibility
-                this._visibilityChanged = (curVisibleState !== undefined) && curVisibleState !== this.isVisible;
+                this._visibilityChanged = curVisibleState !== this.isVisible;
 
                 // Get/compute the localTransform
                 let localDirty = this._updateLocalTransform();
@@ -831,6 +871,7 @@
 
         private _owner: Canvas2D;
         private _parent: Prim2DBase;
+        private _actionManager: ActionManager;
         protected _children: Array<Prim2DBase>;
         private _renderGroup: Group2D;
         private _hierarchyDepth: number;

+ 99 - 22
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -142,23 +142,57 @@
             this._instanceDirtyFlags = 0;
             this._isDisposed = false;
             this._levelBoundingInfo = new BoundingInfo2D();
+            this.animations = new Array<Animation>();
         }
 
+        /**
+         * An observable that is triggered when a property (using of the XXXXLevelProperty decorator) has its value changing.
+         * You can add an observer that will be triggered only for a given set of Properties using the Mask feature of the Observable and the corresponding Prim2DPropInfo.flagid value (e.g. Prim2DBase.positionProperty.flagid|Prim2DBase.rotationProperty.flagid to be notified only about position or rotation change)
+         */
         public propertyChanged: Observable<PropertyChangedInfo>;
 
+        /**
+         * Check if the object is disposed or not.
+         * @returns true if the object is dispose, false otherwise.
+         */
         public get isDisposed(): boolean {
             return this._isDisposed;
         }
 
+        /**
+         * Disposable pattern, this method must be overloaded by derived types in order to clean up hardware related resources.
+         * @returns false if the object is already dispose, true otherwise. Your implementation must call super.dispose() and check for a false return and return immediately if it's the case.
+         */
         public dispose(): boolean {
             if (this.isDisposed) {
                 return false;
             }
 
+            // Don't set to null, it may upset somebody...
+            this.animations.splice(0);
+
             this._isDisposed = true;
             return true;
         }
 
+        /**
+         * Animation array, more info: http://doc.babylonjs.com/tutorials/Animations
+         */
+        public animations: Animation[];
+
+        /**
+         * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
+         * Look at Sprite2D for more information
+         */
+        public getAnimatables(): IAnimatable[] {
+            return new Array<IAnimatable>();
+        }
+
+        /**
+         * Property giving the Model Key associated to the property.
+         * This value is constructed from the type of the primitive and all the name/value of its properties declared with the modelLevelProperty decorator
+         * @returns the model key string.
+         */
         public get modelKey(): string {
 
             // No need to compute it?
@@ -181,10 +215,18 @@
             return modelKey;
         }
 
+        /**
+         * States if the Primitive is dirty and should be rendered again next time.
+         * @returns true is dirty, false otherwise
+         */
         public get isDirty(): boolean {
             return (this._instanceDirtyFlags !== 0) || this._modelDirty;
         }
 
+        /**
+         * Access the dictionary of properties metadata. Only properties decorated with XXXXLevelProperty are concerned
+         * @returns the dictionary, the key is the property name as declared in Javascript, the value is the metadata object
+         */
         private get propDic(): StringDictionary<Prim2DPropInfo> {
             if (!this._propInfo) {
                 let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(Object.getPrototypeOf(this));
@@ -214,7 +256,7 @@
             propInfo.name = propName;
             propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
             propInfo.typeLevelCompare = typeLevelCompare;
-            node.levelContent.add(propId.toString(), propInfo);
+            node.levelContent.add(propName, propInfo);
 
             return propInfo;
         }
@@ -243,7 +285,43 @@
 
         private static propChangedInfo = new PropertyChangedInfo();
 
+        public markAsDirty(propertyName: string, oldValue: any) {
+            let i = propertyName.indexOf(".");
+            if (i !== -1) {
+                propertyName = propertyName.substr(0, i);
+            }
+
+            var propInfo = this.propDic.get(propertyName);
+            if (!propInfo) {
+                return;
+            }
+
+            var newValue = this[propertyName];
+            this._handlePropChanged(oldValue, newValue, propertyName, propInfo, propInfo.typeLevelCompare);
+        }
+
         private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
+            // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
+            if (propInfo.dirtyBoundingInfo) {
+                this._levelBoundingInfoDirty = true;
+
+                // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
+                if (this instanceof Prim2DBase) {
+                    let curprim = (<any>this).parent;
+                    while (curprim) {
+                        curprim._boundingInfoDirty = true;
+
+                        if (curprim instanceof Group2D) {
+                            if (curprim.isRenderableGroup) {
+                                break;
+                            }
+                        }
+
+                        curprim = curprim.parent;
+                    }
+                }
+            }
+
             // Trigger property changed
             let info = SmartPropertyPrim.propChangedInfo;
             info.oldValue = curValue;
@@ -286,15 +364,29 @@
 
         }
 
+        /**
+         * Check if a given set of properties are dirty or not.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return true if at least one property is dirty, false if none of them are.
+         */
         public checkPropertiesDirty(flags: number): boolean {
             return (this._instanceDirtyFlags & flags) !== 0;
         }
 
+        /**
+         * Clear a given set of properties.
+         * @param flags a ORed combination of Prim2DPropInfo.flagId values
+         * @return the new set of property still marked as dirty
+         */
         protected clearPropertiesDirty(flags: number): number {
             this._instanceDirtyFlags &= ~flags;
             return this._instanceDirtyFlags;
         }
 
+        /**
+         * Retrieve the boundingInfo for this Primitive, computed based on the primitive itself and NOT its children
+         * @returns {} 
+         */
         public get levelBoundingInfo(): BoundingInfo2D {
             if (this._levelBoundingInfoDirty) {
                 this.updateLevelBoundingInfo();
@@ -303,10 +395,16 @@
             return this._levelBoundingInfo;
         }
 
+        /**
+         * This method must be overridden by a given Primitive implementation to compute its boundingInfo
+         */
         protected updateLevelBoundingInfo() {
 
         }
 
+        /**
+         * Property method called when the Primitive becomes dirty
+         */
         protected onPrimBecomesDirty() {
 
         }
@@ -339,27 +437,6 @@
                     // Change the value
                     setter.call(this, val);
 
-                    // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
-                    if (propInfo.dirtyBoundingInfo) {
-                        prim._levelBoundingInfoDirty = true;
-
-                        // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
-                        if (prim instanceof Prim2DBase) {
-                            let curprim = prim.parent;
-                            while (curprim) {
-                                curprim._boundingInfoDirty = true;
-
-                                if (curprim instanceof Group2D) {
-                                    if (curprim.isRenderableGroup) {
-                                        break;
-                                    }
-                                }
-
-                                curprim = curprim.parent;
-                            }
-                        }
-                    }
-
                     // Notify change, dirty flags update
                     prim._handlePropChanged(curVal, val, <string>propName, propInfo, typeLevelCompare);
                 }

+ 9 - 0
src/Canvas2d/babylon.sprite2d.ts

@@ -165,6 +165,15 @@
             BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo, this.origin);
         }
 
+        public getAnimatables(): IAnimatable[] {
+            let res = new Array<IAnimatable>();
+
+            if (this.texture && this.texture.animations && this.texture.animations.length > 0) {
+                res.push(this.texture);
+            }
+            return res;
+        }
+
         protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean) {
             this.setupRenderablePrim2D(owner, parent, id, position, true);
             this.texture = texture;

+ 2 - 6
src/Canvas2d/babylon.text2d.ts

@@ -25,7 +25,7 @@
             engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
 
             var cur = engine.getAlphaMode();
-            engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            engine.setAlphaMode(Engine.ALPHA_ADD);
             let count = instanceInfo._instancesPartsData[0].usedElementCount;
             if (instanceInfo._owner.owner.supportInstancedArray) {
                 engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
@@ -227,7 +227,6 @@
             this.hAlign = hAlign;
             this._tabulationSize = tabulationSize;
             this._isTransparent = true;
-            this.origin = Vector2.Zero();
         }
 
         public static Create(parent: Prim2DBase, id: string, x: number, y: number, fontName: string, text: string, defaultFontColor?: Color4, areaSize?: Size, vAlign = Text2D.TEXT2D_VALIGN_TOP, hAlign = Text2D.TEXT2D_HALIGN_LEFT, tabulationSize: number = 4): Text2D {
@@ -267,9 +266,7 @@
 
             // Effects
             let ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"]);
-            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, e => {
-//                renderCache.setupUniformsLocation(e, ei.uniforms, Text2D.TEXT2D_MAINPARTID);
-            });
+            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
 
             return renderCache;
         }
@@ -310,7 +307,6 @@
                 let charxpos = 0;
                 d.dataElementCount = this._charCount;
                 d.curElement = 0;
-                let customOrigin = Vector2.Zero();
                 for (let char of this.text) {
 
                     // Line feed

+ 2 - 2
src/Materials/Textures/babylon.fontTexture.ts

@@ -56,7 +56,7 @@
                 return ft;
             }
 
-            ft = new FontTexture(null, lfn, scene);
+            ft = new FontTexture(null, lfn, scene, 200, Texture.NEAREST_SAMPLINGMODE);
             dic.add(lfn, ft);
 
             return ft;
@@ -174,7 +174,7 @@
 
             // Fill the CharInfo object
             info.topLeftUV = new Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
-            info.bottomRightUV = new Vector2(info.topLeftUV.x + (width / textureSize.width), info.topLeftUV.y + ((this._lineHeight + 1) / textureSize.height));
+            info.bottomRightUV = new Vector2(info.topLeftUV.x + (width / textureSize.width), info.topLeftUV.y + ((this._lineHeight + 2) / textureSize.height));
             info.charWidth = width;
 
             // Add the info structure

+ 18 - 0
src/Math/babylon.math.ts

@@ -1598,6 +1598,24 @@
         public static Zero(): Size {
             return new Size(0, 0);
         }
+
+        public add(otherSize: Size): Size {
+            let r = new Size(this.width + otherSize.width, this.height + otherSize.height);
+            return r;
+        }
+
+        public substract(otherSize: Size): Size {
+            let r = new Size(this.width - otherSize.width, this.height - otherSize.height);
+            return r;
+        }
+
+        public static Lerp(start: Size, end: Size, amount: number): Size {
+            var w = start.width + ((end.width - start.width) * amount);
+            var h = start.height + ((end.height - start.height) * amount);
+
+            return new Size(w, h);
+        }
+
     }
 
 

+ 1 - 1
src/Shaders/text2d.vertex.fx

@@ -52,7 +52,7 @@ void main(void) {
 
 	vColor = color;
 	vec4 pos;
-	pos.xy = (pos2.xy - origin) * sizeUV * textureSize;
+	pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, zBias.y);