Parcourir la source

Merge pull request #1173 from nockawa/capIntersection

canvas2D: many fixes
David Catuhe il y a 9 ans
Parent
commit
9b79a21a05

+ 11 - 5
src/Canvas2d/babylon.bounding2d.ts

@@ -135,6 +135,8 @@
             return r;
         }
 
+        private static _transform: Array<Vector2> = new Array<Vector2>(Vector2.Zero(), Vector2.Zero(), Vector2.Zero(), Vector2.Zero());
+
         /**
          * Transform this BoundingInfo2D with a given matrix and store the result in an existing BoundingInfo2D instance.
          * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it every time.
@@ -143,11 +145,15 @@
          */
         public transformToRef(matrix: Matrix, result: BoundingInfo2D) {
             // Construct a bounding box based on the extent values
-            let p = new Array<Vector2>(4);
-            p[0] = new Vector2(this.center.x + this.extent.x, this.center.y + this.extent.y);
-            p[1] = new Vector2(this.center.x + this.extent.x, this.center.y - this.extent.y);
-            p[2] = new Vector2(this.center.x - this.extent.x, this.center.y - this.extent.y);
-            p[3] = new Vector2(this.center.x - this.extent.x, this.center.y + this.extent.y);
+            let p = BoundingInfo2D._transform;
+            p[0].x = this.center.x + this.extent.x;
+            p[0].y = this.center.y + this.extent.y;
+            p[1].x = this.center.x + this.extent.x;
+            p[1].y = this.center.y - this.extent.y;
+            p[2].x = this.center.x - this.extent.x;
+            p[2].y = this.center.y - this.extent.y;
+            p[3].x = this.center.x - this.extent.x;
+            p[3].y = this.center.y + this.extent.y;
 
             // Transform the four points of the bounding box with the matrix
             for (let i = 0; i < 4; i++) {

+ 1 - 3
src/Canvas2d/babylon.canvas2d.ts

@@ -453,8 +453,6 @@
                 return;
             }
 
-            this._updateCanvasState();
-
             this.intersect(ii);
 
             this._previousIntersectionList = this._actualIntersectionList;
@@ -902,7 +900,7 @@
                 }
             }
 
-            var context = new PreapreRender2DContext();
+            var context = new PrepareRender2DContext();
 
             ++this._globalTransformProcessStep;
             this.updateGlobalTransVis(false);

+ 19 - 1
src/Canvas2d/babylon.ellipse2d.ts

@@ -243,7 +243,25 @@
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
                 let size = options.size || (new Size(options.width || 10, options.height || 10));
 
-                ellipse.setupEllipse2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.subdivisions || 64, fill, options.border || null, options.borderThickness || 1, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+                ellipse.setupEllipse2D
+                (
+                    parent.owner,
+                    parent,
+                    options.id || null,
+                    pos,
+                    options.origin || null,
+                    size,
+                    (options.subdivisions == null) ? 64 : options.subdivisions,
+                    fill,
+                    options.border || null,
+                    (options.borderThickness == null) ? 1 : options.borderThickness,
+                    (options.isVisible == null) ? true : options.isVisible,
+                    options.marginTop || null,
+                    options.marginLeft || null,
+                    options.marginRight || null,
+                    options.marginBottom || null,
+                    options.vAlignment || null,
+                    options.hAlignment || null);
             }
 
             return ellipse;

+ 18 - 3
src/Canvas2d/babylon.group2d.ts

@@ -55,7 +55,22 @@
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
                 let size = (!options.size && !options.width && !options.height) ? null : (options.size || (new Size(options.width || 0, options.height || 0)));
                 
-                g.setupGroup2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.isVisible || true, options.cacheBehavior || Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY, options.marginTop, options.marginLeft, options.marginRight, options.marginBottom, options.hAlignment || Prim2DBase.HAlignLeft, options.vAlignment || Prim2DBase.VAlignTop);
+                g.setupGroup2D
+                (
+                    parent.owner,
+                    parent,
+                    options.id || null,
+                    pos,
+                    options.origin || null,
+                    size,
+                    (options.isVisible == null) ? true : options.isVisible,
+                    (options.cacheBehavior == null) ? Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY : options.cacheBehavior,
+                    options.marginTop,
+                    options.marginLeft,
+                    options.marginRight,
+                    options.marginBottom,
+                    options.hAlignment || Prim2DBase.HAlignLeft,
+                    options.vAlignment || Prim2DBase.VAlignTop);
             }
        
             return g;
@@ -205,7 +220,7 @@
 
         public _renderCachedCanvas() {
             this.updateGlobalTransVis(true);
-            let context = new PreapreRender2DContext();
+            let context = new PrepareRender2DContext();
             this._prepareGroupRender(context);
             this._groupRender();
         }
@@ -231,7 +246,7 @@
         }
 
         // Method called only on renderable groups to prepare the rendering
-        protected _prepareGroupRender(context: PreapreRender2DContext) {
+        protected _prepareGroupRender(context: PrepareRender2DContext) {
             let sortedDirtyList: Prim2DBase[] = null;
 
             // Update the Global Transformation and visibility status of the changed primitives

+ 42 - 13
src/Canvas2d/babylon.lines2d.ts

@@ -234,29 +234,37 @@
             this._endCap = value;
         }
 
+        private static _prevA: Vector2 = Vector2.Zero();
+        private static _prevB: Vector2 = Vector2.Zero();
+        private static _curA: Vector2 = Vector2.Zero();
+        private static _curB: Vector2 = Vector2.Zero();
+
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
             let pl = this.points.length;
             let l = this.closed ? pl + 1 : pl;
 
-            let originOffset = new Vector2(-0.5, -0.5);
+//            let originOffset = new Vector2(-0.5, -0.5);
             let p = intersectInfo._localPickPosition;
 
-            let prevA = this.transformPointWithOrigin(this._contour[0], originOffset);
-            let prevB = this.transformPointWithOrigin(this._contour[1], originOffset);
+            this.transformPointWithOriginToRef(this._contour[0], null, Lines2D._prevA);
+            this.transformPointWithOriginToRef(this._contour[1], null, Lines2D._prevB);
             for (let i = 1; i < l; i++) {
-                let curA = this.transformPointWithOrigin(this._contour[(i % pl) * 2 + 0], originOffset);
-                let curB = this.transformPointWithOrigin(this._contour[(i % pl) * 2 + 1], originOffset);
+                this.transformPointWithOriginToRef(this._contour[(i % pl) * 2 + 0], null, Lines2D._curA);
+                this.transformPointWithOriginToRef(this._contour[(i % pl) * 2 + 1], null, Lines2D._curB);
 
-                if (Vector2.PointInTriangle(p, prevA, prevB, curA)) {
+                if (Vector2.PointInTriangle(p, Lines2D._prevA, Lines2D._prevB, Lines2D._curA)) {
                     return true;
                 }
-                if (Vector2.PointInTriangle(p, curA, prevB, curB)) {
+                if (Vector2.PointInTriangle(p, Lines2D._curA, Lines2D._prevB, Lines2D._curB)) {
                     return true;
                 }
 
-                prevA = curA;
-                prevB = curB;
+                Lines2D._prevA.x = Lines2D._curA.x;
+                Lines2D._prevA.y = Lines2D._curA.y;
+                Lines2D._prevB.x = Lines2D._curB.x;
+                Lines2D._prevB.y = Lines2D._curB.y;
             }
+
             return false;
         }
 
@@ -284,7 +292,7 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
+            BoundingInfo2D.CreateFromMinMaxToRef(this._boundingMin.x, this._boundingMax.x, this._boundingMin.y, this._boundingMax.y, this._levelBoundingInfo, this.origin);
         }
 
         protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
@@ -335,7 +343,28 @@
                 }
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
 
-                lines.setupLines2D(parent.owner, parent, options.id || null, pos, options.origin || null, points, options.fillThickness || 1, options.startCap || 0, options.endCap || 0, fill, options.border || null, options.borderThickness || 1, options.closed || false, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);                
+                lines.setupLines2D
+                (
+                    parent.owner,
+                    parent,
+                    options.id || null,
+                    pos,
+                    options.origin || null,
+                    points,
+                    (options.fillThickness == null) ? 1 : options.fillThickness,
+                    (options.startCap == null) ? 0 : options.startCap,
+                    (options.endCap == null) ? 0 : options.endCap,
+                    fill,
+                    options.border || null,
+                    (options.borderThickness == null) ? 1 : options.borderThickness,
+                    (options.closed == null) ? false : options.closed,
+                    (options.isVisible == null) ? true : options.isVisible,
+                    options.marginTop || null,
+                    options.marginLeft || null,
+                    options.marginRight || null,
+                    options.marginBottom || null,
+                    options.vAlignment || null,
+                    options.hAlignment || null);                
             }
 
             return lines;
@@ -351,8 +380,8 @@
             let engine = this.owner.engine;
 
             // Init min/max because their being computed here
-            this.boundingMin = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
-            this.boundingMax = new Vector2(Number.MIN_VALUE, Number.MIN_VALUE);
+            this._boundingMin = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
+            this._boundingMax = new Vector2(Number.MIN_VALUE, Number.MIN_VALUE);
 
             let perp = (v: Vector2, res: Vector2) => {
                 res.x = v.y;

+ 20 - 18
src/Canvas2d/babylon.prim2dBase.ts

@@ -1,6 +1,6 @@
 module BABYLON {
 
-    export class PreapreRender2DContext {
+    export class PrepareRender2DContext {
         constructor() {
             this.forceRefreshPrimitive = false;
         }
@@ -799,7 +799,7 @@
         }
 
         /**
-         * Get the boundingInfo associated to the primitive.
+         * Get the boundingInfo associated to the primitive and its children.
          * The value is supposed to be always up to date
          */
         public get boundingInfo(): BoundingInfo2D {
@@ -876,28 +876,30 @@
             }
 
             // Fast rejection test with boundingInfo
-            if (!this.boundingInfo.doesIntersect(intersectInfo._localPickPosition)) {
+            if (this.isPickable && !this.boundingInfo.doesIntersect(intersectInfo._localPickPosition)) {
                 // Important to call this before each return to allow a good recursion next time this intersectInfo is reused
                 intersectInfo._exit(firstLevel);
                 return false;
             }
 
             // We hit the boundingInfo that bounds this primitive and its children, now we have to test on the primitive of this level
-            let levelIntersectRes = this.levelIntersect(intersectInfo);
-            if (levelIntersectRes) {
-                let pii = new PrimitiveIntersectedInfo(this, intersectInfo._localPickPosition.clone());
-                intersectInfo.intersectedPrimitives.push(pii);
-                if (!intersectInfo.topMostIntersectedPrimitive || (intersectInfo.topMostIntersectedPrimitive.prim.getActualZOffset() > pii.prim.getActualZOffset())) {
-                    intersectInfo.topMostIntersectedPrimitive = pii;
-                }
+            let levelIntersectRes = false;
+            if (this.isPickable) {
+                levelIntersectRes = this.levelIntersect(intersectInfo);
+                if (levelIntersectRes) {
+                    let pii = new PrimitiveIntersectedInfo(this, intersectInfo._localPickPosition.clone());
+                    intersectInfo.intersectedPrimitives.push(pii);
+                    if (!intersectInfo.topMostIntersectedPrimitive || (intersectInfo.topMostIntersectedPrimitive.prim.getActualZOffset() > pii.prim.getActualZOffset())) {
+                        intersectInfo.topMostIntersectedPrimitive = pii;
+                    }
 
-                // If we must stop at the first intersection, we're done, quit!
-                if (intersectInfo.findFirstOnly) {
-                    intersectInfo._exit(firstLevel);
-                    return true;
+                    // If we must stop at the first intersection, we're done, quit!
+                    if (intersectInfo.findFirstOnly) {
+                        intersectInfo._exit(firstLevel);
+                        return true;
+                    }
                 }
             }
-
             // Recurse to children if needed
             if (!levelIntersectRes || !intersectInfo.findFirstOnly) {
                 for (let curChild of this._children) {
@@ -998,15 +1000,15 @@
             return this._visibilityChanged || this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
         }
 
-        public _prepareRender(context: PreapreRender2DContext) {
+        public _prepareRender(context: PrepareRender2DContext) {
             this._prepareRenderPre(context);
             this._prepareRenderPost(context);
         }
 
-        public _prepareRenderPre(context: PreapreRender2DContext) {
+        public _prepareRenderPre(context: PrepareRender2DContext) {
         }
 
-        public _prepareRenderPost(context: PreapreRender2DContext) {
+        public _prepareRenderPost(context: PrepareRender2DContext) {
             // Don't recurse if it's a renderable group, the content will be processed by the group itself
             if (this instanceof Group2D) {
                 var self: any = this;

+ 87 - 3
src/Canvas2d/babylon.rectangle2d.ts

@@ -203,14 +203,80 @@
             this.notRounded = value === 0;
         }
 
+        private static _i0 = Vector2.Zero();
+        private static _i1 = Vector2.Zero();
+        private static _i2 = Vector2.Zero();
+
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
             // If we got there it mean the boundingInfo intersection succeed, if the rectangle has not roundRadius, it means it succeed!
             if (this.notRounded) {
                 return true;
             }
 
-            // Well, for now we neglect the area where the pickPosition could be outside due to the roundRadius...
-            // TODO make REAL intersection test here!
+            // If we got so far it means the bounding box at least passed, so we know it's inside the bounding rectangle, but it can be outside the roundedRectangle.
+            // The easiest way is to check if the point is inside on of the four corners area (a little square of roundRadius size at the four corners)
+            // If it's the case for one, check if the mouse is located in the quarter that we care about (the one who is visible) then finally make a distance check with the roundRadius radius to see if it's inside the circle quarter or outside.
+
+            // First let remove the origin out the equation, to have the rectangle with an origin at bottom/left
+            let o = this.origin;
+            let size = this.size;
+            Rectangle2D._i0.x = intersectInfo._localPickPosition.x + (size.width * o.x);
+            Rectangle2D._i0.y = intersectInfo._localPickPosition.y + (size.height * o.y);
+
+            let rr = this.roundRadius;
+            let rrs = rr * rr;
+
+            // Check if the point is in the bottom/left quarter area
+            Rectangle2D._i1.x = rr;
+            Rectangle2D._i1.y = rr;
+            if (Rectangle2D._i0.x <= Rectangle2D._i1.x && Rectangle2D._i0.y <= Rectangle2D._i1.y) {
+                // Compute the intersection point in the quarter local space
+                Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
+                Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
+
+                // It's a hit if the squared distance is less/equal to the squared radius of the round circle
+                return Rectangle2D._i2.lengthSquared() <= rrs;
+            }
+
+            // Check if the point is in the top/left quarter area
+            Rectangle2D._i1.x = rr;
+            Rectangle2D._i1.y = size.height - rr;
+            if (Rectangle2D._i0.x <= Rectangle2D._i1.x && Rectangle2D._i0.y >= Rectangle2D._i1.y) {
+                // Compute the intersection point in the quarter local space
+                Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
+                Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
+
+                // It's a hit if the squared distance is less/equal to the squared radius of the round circle
+                return Rectangle2D._i2.lengthSquared() <= rrs;
+            }
+
+            // Check if the point is in the top/right quarter area
+            Rectangle2D._i1.x = size.width - rr;
+            Rectangle2D._i1.y = size.height - rr;
+            if (Rectangle2D._i0.x >= Rectangle2D._i1.x && Rectangle2D._i0.y >= Rectangle2D._i1.y) {
+                // Compute the intersection point in the quarter local space
+                Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
+                Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
+
+                // It's a hit if the squared distance is less/equal to the squared radius of the round circle
+                return Rectangle2D._i2.lengthSquared() <= rrs;
+            }
+
+
+            // Check if the point is in the bottom/right quarter area
+            Rectangle2D._i1.x = size.width - rr;
+            Rectangle2D._i1.y = rr;
+            if (Rectangle2D._i0.x >= Rectangle2D._i1.x && Rectangle2D._i0.y <= Rectangle2D._i1.y) {
+                // Compute the intersection point in the quarter local space
+                Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
+                Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
+
+                // It's a hit if the squared distance is less/equal to the squared radius of the round circle
+                return Rectangle2D._i2.lengthSquared() <= rrs;
+            }
+
+            // At any other locations the point is guarantied to be inside
+
             return true;
         }
 
@@ -254,7 +320,25 @@
                 let size = options.size || (new Size(options.width || 10, options.height || 10));
                 let fill = options.fill===undefined ? Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF") : options.fill;
 
-                rect.setupRectangle2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.roundRadius || 0, fill, options.border || null, options.borderThickness || 1, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+                rect.setupRectangle2D
+                (
+                    parent.owner,
+                    parent,
+                    options.id || null,
+                    pos,
+                    options.origin || null,
+                    size,
+                    (options.roundRadius == null) ? 0 : options.roundRadius,
+                    fill,
+                    options.border || null,
+                    (options.borderThickness==null) ? 1 : options.borderThickness,
+                    options.isVisible || true,
+                    options.marginTop || null,
+                    options.marginLeft || null,
+                    options.marginRight || null,
+                    options.marginBottom || null,
+                    options.vAlignment || null,
+                    options.hAlignment || null);
             }
             return rect;
         }

+ 2 - 3
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -385,7 +385,7 @@
             return true;
         }
 
-        public _prepareRenderPre(context: PreapreRender2DContext) {
+        public _prepareRenderPre(context: PrepareRender2DContext) {
             super._prepareRenderPre(context);
 
             // If the model changed and we have already an instance, we must remove this instance from the obsolete model
@@ -674,8 +674,7 @@
             res.y = p.y - ((this.origin.y + (originOffset ? originOffset.y : 0)) * actualSize.height);
         }
 
-        protected transformPointWithOrigin(p: Vector2, originOffset: Vector2): Vector2 {
-            let res = new Vector2(0, 0);
+        protected transformPointWithOriginToRef(p: Vector2, originOffset: Vector2, res: Vector2) {
             this.transformPointWithOriginByRef(p, originOffset, res);
             return res;
         }

+ 49 - 14
src/Canvas2d/babylon.sprite2d.ts

@@ -11,7 +11,7 @@
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
             // Do nothing if the shader is still loading/preparing 
             if (!this.effectsReady) {
-                if ((!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady()))) {
+                if ((this.effect && (!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady())))) {
                     return false;
                 }
                 this.effectsReady = true;
@@ -106,13 +106,12 @@
             return null;
         }
 
+        // 3 floats being:
+        // - x: frame number to display
+        // - y: invertY setting
+        // - z: alignToPixel setting
         @instanceData()
-        get frame(): number {
-            return null;
-        }
-
-        @instanceData()
-        get invertY(): number {
+        get properties(): Vector3 {
             return null;
         }
     }
@@ -126,6 +125,7 @@
         public static spriteLocationProperty: Prim2DPropInfo;
         public static spriteFrameProperty: Prim2DPropInfo;
         public static invertYProperty: Prim2DPropInfo;
+        public static alignToPixelProperty: Prim2DPropInfo;
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Sprite2D.textureProperty = pi)
         public get texture(): Texture {
@@ -176,6 +176,14 @@
             this._invertY = value;
         }
 
+        public get alignToPixel(): boolean {
+            return this._alignToPixel;
+        }
+
+        public set alignToPixel(value: boolean) {
+            this._alignToPixel = value;
+        }
+
         protected updateLevelBoundingInfo() {
             BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo, this.origin);
         }
@@ -194,7 +202,7 @@
             return true;
         }
 
-        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean, alignToPixel: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
             this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.texture = texture;
             this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
@@ -203,6 +211,7 @@
             this.spriteLocation = spriteLocation || new Vector2(0,0);
             this.spriteFrame = 0;
             this.invertY = invertY;
+            this.alignToPixel = alignToPixel;
             this._isTransparent = true;
 
             if (!this.spriteSize) {
@@ -222,20 +231,39 @@
          *  - spriteSize: the size of the sprite, if null the size of the given texture will be used, default is null.
          *  - spriteLocation: the location in the texture of the top/left corner of the Sprite to display, default is null (0,0)
          *  - invertY: if true the texture Y will be inverted, default is false.
+         *  - alignToPixel: if true the sprite's texels will be aligned to the rendering viewport pixels, ensuring the best rendering quality but slow animations won't be done as smooth as if you set false. If false a texel could lies between two pixels, being blended by the texture sampling mode you choose, the rendering result won't be as good, but very slow animation will be overall better looking. Default is true: content will be aligned.
          *  - isVisible: true if the sprite must be visible, false for hidden. Default is true.
          *  - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Sprite2D {
+        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean, alignToPixel?: boolean, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Sprite2D {
             Prim2DBase.CheckParent(parent);
 
             let sprite = new Sprite2D();
             if (!options) {
-                sprite.setupSprite2D(parent.owner, parent, null, Vector2.Zero(), null, texture, null, null, false, true, null, null, null, null, null, null);
+                sprite.setupSprite2D(parent.owner, parent, null, Vector2.Zero(), null, texture, null, null, false, true, true, null, null, null, null, null, null);
             } else {
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
-                sprite.setupSprite2D(parent.owner, parent, options.id || null, pos, options.origin || null, texture, options.spriteSize || null, options.spriteLocation || null, options.invertY || false, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+                sprite.setupSprite2D
+                (
+                    parent.owner,
+                    parent,
+                    options.id || null,
+                    pos,
+                    options.origin || null,
+                    texture, options.spriteSize || null,
+                    options.spriteLocation || null,
+                    (options.invertY == null) ? false : options.invertY,
+                    (options.alignToPixel == null) ? true : options.alignToPixel,
+                    (options.isVisible == null) ? true : options.isVisible,
+                    options.marginTop || null,
+                    options.marginLeft || null,
+                    options.marginRight || null,
+                    options.marginBottom || null,
+                    options.vAlignment || null,
+                    options.hAlignment || null
+                );
             }
 
             return sprite;
@@ -244,7 +272,7 @@
         static _createCachedCanvasSprite(owner: Canvas2D, texture: MapTexture, size: Size, pos: Vector2): Sprite2D {
 
             let sprite = new Sprite2D();
-            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new Vector2(0, 0), null, texture, size, pos, false, true, null, null, null, null, null, null);
+            sprite.setupSprite2D(owner, null, "__cachedCanvasSprite__", new Vector2(0, 0), null, texture, size, pos, false, true, true, null, null, null, null, null, null);
 
             return sprite;
         }
@@ -292,6 +320,8 @@
             return [new Sprite2DInstanceData(Sprite2D.SPRITE2D_MAINPARTID)];
         }
 
+        private static _prop: Vector3 = Vector3.Zero();
+
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataPart(part)) {
                 return false;
@@ -305,9 +335,13 @@
                 d.topLeftUV = new Vector2(sl.x / ts.width, sl.y / ts.height);
                 let suv = new Vector2(ss.width / ts.width, ss.height / ts.height);
                 d.sizeUV = suv;
-                d.frame = this.spriteFrame;
+
+                Sprite2D._prop.x = this.spriteFrame;
+                Sprite2D._prop.y = this.invertY ? 1 : 0;
+                Sprite2D._prop.z = this.alignToPixel ? 1 : 0;
+                d.properties = Sprite2D._prop;
+
                 d.textureSize = new Vector2(ts.width, ts.height);
-                d.invertY = this.invertY ? 1 : 0;
             }
             return true;
         }
@@ -317,6 +351,7 @@
         private _location: Vector2;
         private _spriteFrame: number;
         private _invertY: boolean;
+        private _alignToPixel: boolean;
     }
 
 

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

@@ -11,7 +11,7 @@
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
             // Do nothing if the shader is still loading/preparing 
             if (!this.effectsReady) {
-                if ((!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady()))) {
+                if ((this.effect && (!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady())))) {
                     return false;
                 }
                 this.effectsReady = true;
@@ -239,7 +239,25 @@
                 text2d.setupText2D(parent.owner, parent, null, Vector2.Zero(), null, "12pt Arial", text, null, new Color4(1,1,1,1), 4, true, null, null, null, null, null, null);
             } else {
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
-                text2d.setupText2D(parent.owner, parent, options.id || null, pos, options.origin || null, options.fontName || "12pt Arial", text, options.areaSize, options.defaultFontColor || new Color4(1, 1, 1, 1), options.tabulationSize || 4, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
+                text2d.setupText2D
+                (
+                    parent.owner,
+                    parent,
+                    options.id || null,
+                    pos,
+                    options.origin || null,
+                    options.fontName || "12pt Arial",
+                    text,
+                    options.areaSize,
+                    options.defaultFontColor || new Color4(1, 1, 1, 1),
+                    (options.tabulationSize==null) ? 4 : options.tabulationSize,
+                    (options.isVisible==null) ? true : options.isVisible,
+                    options.marginTop || null,
+                    options.marginLeft || null,
+                    options.marginRight || null,
+                    options.marginBottom || null,
+                    options.vAlignment || null,
+                    options.hAlignment || null);
             }
             return text2d;
         }

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

@@ -60,7 +60,7 @@ void main(void) {
 #endif
 
 	vec4 pos;
-	pos.xy = position.xy - ((origin.xy-vec2(0.5,0.5)) * (boundingMax - boundingMin));
+	pos.xy = position.xy - (origin.xy * (boundingMax - boundingMin));
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);

+ 15 - 3
src/Shaders/sprite2d.vertex.fx

@@ -12,8 +12,10 @@ att vec2 topLeftUV;
 att vec2 sizeUV;
 att vec2 origin;
 att vec2 textureSize;
-att float frame;
-att float invertY;
+
+// x: frame, y: invertY, z: alignToPixel
+att vec3 properties;
+
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
@@ -31,6 +33,10 @@ void main(void) {
 	//vec2 off = vec2(1.0 / textureSize.x, 1.0 / textureSize.y);
 	vec2 off = vec2(0.0, 0.0);
 
+	float frame = properties.x;
+	float invertY = properties.y;
+	float alignToPixel = properties.z;
+
 	// Left/Top
 	if (index == 0.0) {
 		pos2 = vec2(0.0, 0.0);
@@ -60,7 +66,13 @@ void main(void) {
 	}
 
 	vec4 pos;
-	pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
+	if (alignToPixel == 1.0)
+	{
+		pos.xy = floor((pos2.xy * sizeUV * textureSize) - origin);
+	} else {
+		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, 1);