Browse Source

New Textures types:
- FontTexture: create/update a texture that stores unicode characters of a given font. Due to the considerable amount of different unicode characters, they are added in a lazy fashion.
- MapTexture: this texture encapsulate an instance of RectPackingMap to pack many rectangle based bitmaps into the texture.

nockawa 9 năm trước cách đây
mục cha
commit
3c892655a8

+ 205 - 0
src/Materials/Textures/babylon.fontTexture.ts

@@ -0,0 +1,205 @@
+module BABYLON {
+
+    /**
+     * This class given information about a given character.
+     */
+    export class CharInfo {
+        /**
+         * The normalized ([0;1]) top/left position of the character in the texture
+         */
+        topLeftUV: Vector2;
+
+        /**
+         * The normalized ([0;1]) right/bottom position of the character in the texture
+         */
+        bottomRightUV: Vector2;
+    }
+
+    interface ICharInfoMap {
+        [char: string]: CharInfo;
+    }
+
+    export class FontTexture extends Texture {
+        private _canvas: HTMLCanvasElement;
+        private _context: CanvasRenderingContext2D;
+        private _lineHeight: number;
+        private _offset: number;
+        private _currentFreePosition: Vector2;
+        private _charInfos: ICharInfoMap = {};
+        private _curCharCount = 0;
+        private _lastUpdateCharCount = -1;
+        private _spaceWidth;
+
+        public get spaceWidth() {
+            return this._spaceWidth;
+        }
+
+        /**
+         * Create a new instance of the FontTexture class
+         * @param name the name of the texture
+         * @param font the font to use, use the W3C CSS notation
+         * @param scene the scene that owns the texture
+         * @param maxCharCount the approximative maximum count of characters that could fit in the texture. This is an approximation because most of the fonts are proportional (each char has its own Width). The 'W' character's width is used to compute the size of the texture based on the given maxCharCount
+         * @param samplingMode the texture sampling mode
+         */
+        constructor(name: string, font: string, scene: Scene, maxCharCount=200, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+            super(null, scene, true, false, samplingMode);
+
+            this.name = name;
+
+            this.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this.wrapV = Texture.CLAMP_ADDRESSMODE;
+
+            // First canvas creation to determine the size of the texture to create
+            this._canvas = document.createElement("canvas");
+            this._context = this._canvas.getContext("2d");
+            this._context.font = font;
+            this._context.fillStyle = "white";
+
+            var res = this.getFontHeight(font);
+            this._lineHeight = res.height;
+            this._offset = res.offset-1;
+
+            var maxCharWidth = this._context.measureText("W").width;
+            this._spaceWidth = this._context.measureText(" ").width;
+
+            // This is an approximate size, but should always be able to fit at least the maxCharCount
+            var totalEstSurface = this._lineHeight * maxCharWidth * maxCharCount;
+            var edge = Math.sqrt(totalEstSurface);
+            var textSize = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
+
+            // Create the texture that will store the font characters
+            this._texture = scene.getEngine().createDynamicTexture(textSize, textSize, false, samplingMode);
+            var textureSize = this.getSize();
+
+            // Recreate a new canvas with the final size: the one matching the texture (resizing the previous one doesn't work as one would expect...)
+            this._canvas = document.createElement("canvas");
+            this._canvas.width = textureSize.width;
+            this._canvas.height = textureSize.height;
+            this._context = this._canvas.getContext("2d");
+            this._context.textBaseline = "top";
+            this._context.font = font;
+            this._context.fillStyle = "white";
+            this._context.imageSmoothingEnabled = false;
+
+            this._currentFreePosition = Vector2.Zero();
+
+            // Add the basic ASCII based characters
+            for (let i = 0x20; i < 0x7F; i++) {
+                var c = String.fromCharCode(i);
+                this.getChar(c);
+            }
+            this.update();
+        }
+
+        /**
+         * Make sure the given char is present in the font map.
+         * @param char the character to get or add
+         * @return the CharInfo instance corresponding to the given character
+         */
+        public getChar(char: string): CharInfo {
+            if (char.length !== 1) {
+                return null;
+            }
+
+            var info = this._charInfos[char];
+            if (info) {
+                return info;
+            }
+
+            info = new CharInfo();
+
+            var measure = this._context.measureText(char);
+            var textureSize = this.getSize();
+
+            // we reached the end of the current line?
+            var xMargin = 2;
+            var yMargin = 2;
+            let width = measure.width;
+            if (this._currentFreePosition.x + width + xMargin > textureSize.width) {
+                this._currentFreePosition.x = 0;
+                this._currentFreePosition.y += this._lineHeight + yMargin;      // +2 for safety marging
+
+                // No more room?
+                if (this._currentFreePosition.y > textureSize.height) {
+                    return this.getChar("!");
+                }
+            }
+
+            // Draw the character in the texture
+            this._context.fillText(char, this._currentFreePosition.x - 0.5, this._currentFreePosition.y - this._offset - 0.5);
+
+            // 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));
+
+            // Add the info structure
+            this._charInfos[char] = info;
+            this._curCharCount++;
+
+            // Set the next position
+            this._currentFreePosition.x += width + xMargin;
+
+            return info;
+        }
+
+        // More info here: https://videlais.com/2014/03/16/the-many-and-varied-problems-with-measuring-font-height-for-html5-canvas/
+        private getFontHeight(font: string): {height: number, offset: number} {
+            var fontDraw = document.createElement("canvas");
+            var ctx = fontDraw.getContext('2d');
+            ctx.fillRect(0, 0, fontDraw.width, fontDraw.height);
+            ctx.textBaseline = 'top';
+            ctx.fillStyle = 'white';
+            ctx.font = font;
+            ctx.fillText('jH|', 0, 0);
+            var pixels = ctx.getImageData(0, 0, fontDraw.width, fontDraw.height).data;
+            var start = -1;
+            var end = -1;
+            for (var row = 0; row < fontDraw.height; row++) {
+                for (var column = 0; column < fontDraw.width; column++) {
+                    var index = (row * fontDraw.width + column) * 4;
+                    if (pixels[index] === 0) {
+                        if (column === fontDraw.width - 1 && start !== -1) {
+                            end = row;
+                            row = fontDraw.height;
+                            break;
+                        }
+                        continue;
+                    }
+                    else {
+                        if (start === -1) {
+                            start = row;
+                        }
+                        break;
+                    }
+                }
+            }
+            return { height: end - start, offset: start-1}
+        }
+
+        public get canRescale(): boolean {
+            return false;
+        }
+
+        public getContext(): CanvasRenderingContext2D {
+            return this._context;
+        }
+
+        /**
+         * Call this method when you've call getChar() at least one time, this will update the texture if needed.
+         * Don't be afraid to call it, if no new character was added, this method simply does nothing.
+         */
+        public update(): void {
+            // Update only if there's new char added since the previous update
+            if (this._lastUpdateCharCount < this._curCharCount) {
+                this.getScene().getEngine().updateDynamicTexture(this._texture, this._canvas, false, true);
+                this._lastUpdateCharCount = this._curCharCount;
+            }
+        }
+
+        // cloning should be prohibited, there's no point to duplicate this texture at all
+        public clone(): FontTexture {
+            return null;
+        }
+    }
+} 

+ 88 - 0
src/Materials/Textures/babylon.mapTexture.ts

@@ -0,0 +1,88 @@
+module BABYLON {
+
+    export class MapTexture extends Texture {
+        private _rectPackingMap: RectPackingMap;
+        private _size: ISize;
+
+        constructor(name: string, scene: Scene, size: ISize, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+            super(null, scene, true, false, samplingMode);
+
+            this.name = name;
+            this._size = size;
+            this.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this.wrapV = Texture.CLAMP_ADDRESSMODE;
+
+            // Create the rectPackMap that will allocate portion of the texture
+            this._rectPackingMap = new RectPackingMap(new Size(size.width, size.height));
+
+            // Create the texture that will store the content
+            this._texture = scene.getEngine().createRenderTargetTexture(size, { generateMipMaps: !this.noMipmap, type: Engine.TEXTURETYPE_UNSIGNED_INT });
+        }
+
+        /**
+         * Allocate a rectangle of a given size in the texture map
+         * @param size the size of the rectangle to allocation
+         * @return the PackedRect instance corresponding to the allocated rect or null is there was not enough space to allocate it.
+         */
+        public allocateRect(size: Size): PackedRect {
+            return this._rectPackingMap.addRect(size);
+        }
+
+        /**
+         * Free a given rectangle from the texture map
+         * @param rectInfo the instance corresponding to the rect to free.
+         */
+        public freeRect(rectInfo: PackedRect) {
+            if (rectInfo) {
+                rectInfo.freeContent();
+            }
+        }
+
+        /**
+         * Return the avaible space in the range of [O;1]. 0 being not space left at all, 1 being an empty texture map.
+         * This is the cumulated space, not the biggest available surface. Due to fragmentation you may not allocate a rect corresponding to this surface.
+         * @returns {} 
+         */
+        public get freeSpace(): number {
+            return this._rectPackingMap.freeSpace;
+        }
+
+        /**
+         * Bind the texture to the rendering engine to render in the zone of a given rectangle.
+         * Use this method when you want to render into the texture map with a clipspace set to the location and size of the given rect.
+         * Don't forget to call unbindTexture when you're done rendering
+         * @param rect the zone to render to
+         */
+        public bindTextureForRect(rect: PackedRect) {
+            let engine = this.getScene().getEngine();
+            engine.bindFramebuffer(this._texture);
+//            engine.clear(new Color4(0,1,1,0), true, true);
+            engine.setDirectViewport(rect.pos.x, rect.pos.y, rect.contentSize.width, rect.contentSize.height);
+        }
+
+        /**
+         * Unbind the texture map from the rendering engine.
+         * Call this method when you're done rendering. A previous call to bindTextureForRect has to be made.
+         * @param dumpForDebug if set to true the content of the texture map will be dumped to a picture file that will be sent to the internet browser.
+         */
+        public unbindTexture(dumpForDebug?:boolean) {
+            // Dump ?
+            if (dumpForDebug) {
+                Tools.DumpFramebuffer(this._size.width, this._size.height, this.getScene().getEngine());
+            }
+
+            let engine = this.getScene().getEngine();
+            engine.unBindFramebuffer(this._texture);
+        }
+
+        public get canRescale(): boolean {
+            return false;
+        }
+
+        // Note, I don't know what behevior this method should have: clone the underlying texture/rectPackingMap or just reference them?
+        // Anyway, there's not much point to use this method for this kind of texture I guess
+        public clone(): MapTexture {
+            return null;
+        }
+    }
+}