|
@@ -36,7 +36,7 @@ var BABYLON;
|
|
|
_super.call(this, settings);
|
|
|
/**
|
|
|
* If you set your own WorldSpaceNode to display the Canvas2D you have to provide your own implementation of this method which computes the local position in the Canvas based on the given 3D World one.
|
|
|
- * Beware that you have to take under consideration the origin and the renderScaleFactor in your calculations! Good luck!
|
|
|
+ * Beware that you have to take under consideration the origin in your calculations! Good luck!
|
|
|
*/
|
|
|
this.worldSpaceToNodeLocal = function (worldPos) {
|
|
|
var node = _this._worldSpaceNode;
|
|
@@ -46,21 +46,38 @@ var BABYLON;
|
|
|
var mtx = node.getWorldMatrix().clone();
|
|
|
mtx.invert();
|
|
|
var v = BABYLON.Vector3.TransformCoordinates(worldPos, mtx);
|
|
|
- var rsf = _this._renderScaleFactor;
|
|
|
- var res = new BABYLON.Vector2(v.x * rsf, v.y * rsf);
|
|
|
+ var res = new BABYLON.Vector2(v.x, v.y);
|
|
|
var size = _this.actualSize;
|
|
|
- var o = _this.origin;
|
|
|
res.x += size.width * 0.5; // res is centered, make it relative to bottom/left
|
|
|
res.y += size.width * 0.5;
|
|
|
return res;
|
|
|
};
|
|
|
+ /**
|
|
|
+ * If you use a custom WorldSpaceCanvasNode you have to override this property to update the UV of your object to reflect the changes due to a resizing of the cached bitmap
|
|
|
+ */
|
|
|
+ this.worldSpaceCacheChanged = function () {
|
|
|
+ var plane = _this.worldSpaceCanvasNode;
|
|
|
+ var vd = BABYLON.VertexData.ExtractFromMesh(plane); //new VertexData();
|
|
|
+ vd.uvs = new Float32Array(8);
|
|
|
+ var material = plane.material;
|
|
|
+ var tex = _this._renderableData._cacheTexture;
|
|
|
+ if (material.diffuseTexture !== tex) {
|
|
|
+ material.diffuseTexture = tex;
|
|
|
+ tex.hasAlpha = true;
|
|
|
+ }
|
|
|
+ var nodeuv = _this._renderableData._cacheNodeUVs;
|
|
|
+ for (var i = 0; i < 4; i++) {
|
|
|
+ vd.uvs[i * 2 + 0] = nodeuv[i].x;
|
|
|
+ vd.uvs[i * 2 + 1] = nodeuv[i].y;
|
|
|
+ }
|
|
|
+ vd.applyToMesh(plane);
|
|
|
+ };
|
|
|
this._notifDebugMode = false;
|
|
|
this._mapCounter = 0;
|
|
|
BABYLON.Prim2DBase._isCanvasInit = false;
|
|
|
if (!settings) {
|
|
|
settings = {};
|
|
|
}
|
|
|
- var renderScaleFactor = (settings.renderScaleFactor == null) ? 1 : settings.renderScaleFactor;
|
|
|
if (this._cachingStrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
|
|
|
this._background = new BABYLON.Rectangle2D({ parent: this, id: "###CANVAS BACKGROUND###", size: settings.size }); //TODO CHECK when size is null
|
|
|
this._background.zOrder = 1.0;
|
|
@@ -93,7 +110,6 @@ var BABYLON;
|
|
|
}
|
|
|
var engine = scene.getEngine();
|
|
|
this.__engineData = engine.getOrAddExternalDataWithFactory("__BJSCANVAS2D__", function (k) { return new Canvas2DEngineBoundData(); });
|
|
|
- this._renderScaleFactor = renderScaleFactor;
|
|
|
this._primPointerInfo = new BABYLON.PrimitivePointerInfo();
|
|
|
this._capturedPointers = new BABYLON.StringDictionary();
|
|
|
this._pickStartingPosition = BABYLON.Vector2.Zero();
|
|
@@ -104,16 +120,14 @@ var BABYLON;
|
|
|
this._engine = engine;
|
|
|
this._renderingSize = new BABYLON.Size(0, 0);
|
|
|
this._trackedGroups = new Array();
|
|
|
+ this._maxAdaptiveWorldSpaceCanvasSize = null;
|
|
|
+ this._groupCacheMaps = new BABYLON.StringDictionary();
|
|
|
this._patchHierarchy(this);
|
|
|
var enableInteraction = (settings.enableInteraction == null) ? true : settings.enableInteraction;
|
|
|
this._fitRenderingDevice = !settings.size;
|
|
|
if (!settings.size) {
|
|
|
settings.size = new BABYLON.Size(engine.getRenderWidth(), engine.getRenderHeight());
|
|
|
}
|
|
|
- else {
|
|
|
- settings.size.height *= renderScaleFactor;
|
|
|
- settings.size.width *= renderScaleFactor;
|
|
|
- }
|
|
|
// Register scene dispose to also dispose the canvas when it'll happens
|
|
|
scene.onDisposeObservable.add(function (d, s) {
|
|
|
_this.dispose();
|
|
@@ -130,7 +144,7 @@ var BABYLON;
|
|
|
});
|
|
|
}
|
|
|
this._supprtInstancedArray = this._engine.getCaps().instancedArrays !== null;
|
|
|
- // this._supprtInstancedArray = false; // TODO REMOVE!!!
|
|
|
+ //this._supprtInstancedArray = false; // TODO REMOVE!!!
|
|
|
this._setupInteraction(enableInteraction);
|
|
|
}
|
|
|
Canvas2D.prototype._canvasPreInit = function (settings) {
|
|
@@ -552,7 +566,7 @@ var BABYLON;
|
|
|
this._afterRenderObserver = null;
|
|
|
}
|
|
|
if (this._groupCacheMaps) {
|
|
|
- this._groupCacheMaps.forEach(function (m) { return m.dispose(); });
|
|
|
+ this._groupCacheMaps.forEach(function (k, m) { return m.forEach(function (e) { return e.dispose(); }); });
|
|
|
this._groupCacheMaps = null;
|
|
|
}
|
|
|
};
|
|
@@ -590,6 +604,17 @@ var BABYLON;
|
|
|
enumerable: true,
|
|
|
configurable: true
|
|
|
});
|
|
|
+ Object.defineProperty(Canvas2D.prototype, "isScreenSpace", {
|
|
|
+ /**
|
|
|
+ * Return true if the Canvas is a Screen Space one, false if it's a World Space one.
|
|
|
+ * @returns {}
|
|
|
+ */
|
|
|
+ get: function () {
|
|
|
+ return this._isScreenSpace;
|
|
|
+ },
|
|
|
+ enumerable: true,
|
|
|
+ configurable: true
|
|
|
+ });
|
|
|
Object.defineProperty(Canvas2D.prototype, "worldSpaceCanvasNode", {
|
|
|
/**
|
|
|
* Only valid for World Space Canvas, returns the scene node that displays the canvas
|
|
@@ -757,6 +782,44 @@ var BABYLON;
|
|
|
Canvas2D.prototype.updateCanvasLayout = function (forceRecompute) {
|
|
|
this._updateCanvasState(forceRecompute);
|
|
|
};
|
|
|
+ Canvas2D.prototype._updateAdaptiveSizeWorldCanvas = function () {
|
|
|
+ if (this._globalTransformStep < 2) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var n = this.worldSpaceCanvasNode;
|
|
|
+ var bi = n.getBoundingInfo().boundingBox;
|
|
|
+ var v = bi.vectorsWorld;
|
|
|
+ var cam = this.scene.cameraToUseForPointers || this.scene.activeCamera;
|
|
|
+ cam.getViewMatrix().multiplyToRef(cam.getProjectionMatrix(), Canvas2D._m);
|
|
|
+ var vp = cam.viewport.toGlobal(this.engine.getRenderWidth(), this.engine.getRenderHeight());
|
|
|
+ var projPoints = new Array(4);
|
|
|
+ for (var i = 0; i < 4; i++) {
|
|
|
+ projPoints[i] = BABYLON.Vector3.Project(v[i], Canvas2D._mI, Canvas2D._m, vp);
|
|
|
+ }
|
|
|
+ var left = projPoints[3].subtract(projPoints[0]).length();
|
|
|
+ var top = projPoints[3].subtract(projPoints[1]).length();
|
|
|
+ var right = projPoints[1].subtract(projPoints[2]).length();
|
|
|
+ var bottom = projPoints[2].subtract(projPoints[0]).length();
|
|
|
+ var w = Math.round(Math.max(top, bottom));
|
|
|
+ var h = Math.round(Math.max(right, left));
|
|
|
+ var isW = w > h;
|
|
|
+ // Basically if it's under 256 we use 256, otherwise we take the biggest power of 2
|
|
|
+ var edge = Math.max(w, h);
|
|
|
+ if (edge < 256) {
|
|
|
+ edge = 256;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ edge = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
|
|
|
+ }
|
|
|
+ // Clip values if needed
|
|
|
+ edge = Math.min(edge, this._maxAdaptiveWorldSpaceCanvasSize);
|
|
|
+ var newScale = edge / ((isW) ? this.size.width : this.size.height);
|
|
|
+ if (newScale !== this.scale) {
|
|
|
+ var scale = newScale;
|
|
|
+ // console.log(`New adaptive scale for Canvas ${this.id}, w: ${w}, h: ${h}, scale: ${scale}, edge: ${edge}, isW: ${isW}`);
|
|
|
+ this._setRenderingScale(scale);
|
|
|
+ }
|
|
|
+ };
|
|
|
Canvas2D.prototype._updateCanvasState = function (forceRecompute) {
|
|
|
// Check if the update has already been made for this render Frame
|
|
|
if (!forceRecompute && this.scene.getRenderId() === this._updateRenderId) {
|
|
@@ -795,12 +858,22 @@ var BABYLON;
|
|
|
Canvas2D.prototype._render = function () {
|
|
|
this._updateTrackedNodes();
|
|
|
this._updateCanvasState(false);
|
|
|
+ if (!this._isScreenSpace) {
|
|
|
+ this._updateAdaptiveSizeWorldCanvas();
|
|
|
+ }
|
|
|
+ this._updateCanvasState(false);
|
|
|
if (this._primPointerInfo.canvasPointerPos) {
|
|
|
this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false);
|
|
|
this._updateOverStatus(); // TODO this._primPointerInfo may not be up to date!
|
|
|
}
|
|
|
this.engine.setState(false);
|
|
|
this._groupRender();
|
|
|
+ if (!this._isScreenSpace) {
|
|
|
+ if (this._isFlagSet(BABYLON.SmartPropertyPrim.flagWorldCacheChanged)) {
|
|
|
+ this.worldSpaceCacheChanged();
|
|
|
+ this._clearFlags(BABYLON.SmartPropertyPrim.flagWorldCacheChanged);
|
|
|
+ }
|
|
|
+ }
|
|
|
// If the canvas is cached at canvas level, we must manually render the sprite that will display its content
|
|
|
if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS && this._cachedCanvasGroup) {
|
|
|
this._cachedCanvasGroup._renderCachedCanvas();
|
|
@@ -812,7 +885,10 @@ var BABYLON;
|
|
|
* @param group The group to allocate the cache of.
|
|
|
* @return custom type with the PackedRect instance giving information about the cache location into the texture and also the MapTexture instance that stores the cache.
|
|
|
*/
|
|
|
- Canvas2D.prototype._allocateGroupCache = function (group, parent, minSize) {
|
|
|
+ Canvas2D.prototype._allocateGroupCache = function (group, parent, minSize, useMipMap, anisotropicLevel) {
|
|
|
+ if (useMipMap === void 0) { useMipMap = false; }
|
|
|
+ if (anisotropicLevel === void 0) { anisotropicLevel = 1; }
|
|
|
+ var key = (useMipMap ? "MipMap" : "NoMipMap") + "_" + anisotropicLevel;
|
|
|
// Determine size
|
|
|
var size = group.actualSize;
|
|
|
size = new BABYLON.Size(Math.ceil(size.width), Math.ceil(size.height));
|
|
@@ -820,13 +896,13 @@ var BABYLON;
|
|
|
size.width = Math.max(minSize.width, size.width);
|
|
|
size.height = Math.max(minSize.height, size.height);
|
|
|
}
|
|
|
- if (!this._groupCacheMaps) {
|
|
|
- this._groupCacheMaps = new Array();
|
|
|
- }
|
|
|
+ var mapArray = this._groupCacheMaps.getOrAddWithFactory(key, function () { return new Array(); });
|
|
|
// Try to find a spot in one of the cached texture
|
|
|
var res = null;
|
|
|
- for (var _i = 0, _a = this._groupCacheMaps; _i < _a.length; _i++) {
|
|
|
- var map = _a[_i];
|
|
|
+ var map;
|
|
|
+ for (var _i = 0; _i < mapArray.length; _i++) {
|
|
|
+ var _map = mapArray[_i];
|
|
|
+ map = _map;
|
|
|
var node = map.allocateRect(size);
|
|
|
if (node) {
|
|
|
res = { node: node, texture: map };
|
|
@@ -842,8 +918,9 @@ var BABYLON;
|
|
|
mapSize.height = Math.pow(2, Math.ceil(Math.log(size.height) / Math.log(2)));
|
|
|
}
|
|
|
var id = "groupsMapChache" + this._mapCounter + "forCanvas" + this.id;
|
|
|
- map = new BABYLON.MapTexture(id, this._scene, mapSize);
|
|
|
- this._groupCacheMaps.push(map);
|
|
|
+ map = new BABYLON.MapTexture(id, this._scene, mapSize, useMipMap ? BABYLON.Texture.TRILINEAR_SAMPLINGMODE : BABYLON.Texture.BILINEAR_SAMPLINGMODE, useMipMap);
|
|
|
+ map.anisotropicFilteringLevel = 4;
|
|
|
+ mapArray.splice(0, 0, map);
|
|
|
var node = map.allocateRect(size);
|
|
|
res = { node: node, texture: map };
|
|
|
}
|
|
@@ -1005,8 +1082,9 @@ var BABYLON;
|
|
|
Canvas2D.CACHESTRATEGY_DONTCACHE = 4;
|
|
|
Canvas2D.hierarchyLevelMaxSiblingCount = 50;
|
|
|
Canvas2D._interInfo = new BABYLON.IntersectInfo2D();
|
|
|
- Canvas2D._v = BABYLON.Vector3.Zero();
|
|
|
+ Canvas2D._v = BABYLON.Vector3.Zero(); // Must stay zero
|
|
|
Canvas2D._m = BABYLON.Matrix.Identity();
|
|
|
+ Canvas2D._mI = BABYLON.Matrix.Identity(); // Must stay identity
|
|
|
/**
|
|
|
* Define the default size used for both the width and height of a MapTexture to allocate.
|
|
|
* Note that some MapTexture might be bigger than this size if the first node to allocate is bigger in width or height
|
|
@@ -1032,9 +1110,6 @@ var BABYLON;
|
|
|
* - id: a text identifier, for information purpose only, default is null.
|
|
|
* - worldPosition the position of the Canvas in World Space, default is [0,0,0]
|
|
|
* - worldRotation the rotation of the Canvas in World Space, default is Quaternion.Identity()
|
|
|
- * - renderScaleFactor A scale factor applied to create the rendering texture that will be mapped in the Scene Rectangle. If you set 2 for instance the texture will be twice large in width and height. A greater value will allow to achieve a better rendering quality. Default value is 1.
|
|
|
- * BE AWARE that the Canvas true dimension will be size*renderScaleFactor, then all coordinates and size will have to be express regarding this size.
|
|
|
- * TIPS: if you want a renderScaleFactor independent reference of frame, create a child Group2D in the Canvas with position 0,0 and size set to null, then set its scale property to the same amount than the renderScaleFactor, put all your primitive inside using coordinates regarding the size property you pick for the Canvas and you'll be fine.
|
|
|
* - sideOrientation: Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one, which is the default.
|
|
|
* - cachingStrategy Must be CACHESTRATEGY_CANVAS for now, which is the default.
|
|
|
* - enableInteraction: if true the pointer events will be listened and rerouted to the appropriate primitives of the Canvas2D through the Prim2DBase.onPointerEventObservable observable property. Default is false (the opposite of ScreenSpace).
|
|
@@ -1044,6 +1119,7 @@ var BABYLON;
|
|
|
* - backgroundBorder: the brush to use to create a background border for the canvas. can be a string value (see Canvas2D.GetBrushFromString) or a IBrush2D instance.
|
|
|
* - backgroundBorderThickness: if a backgroundBorder is specified, its thickness can be set using this property
|
|
|
* - customWorldSpaceNode: if specified the Canvas will be rendered in this given Node. But it's the responsibility of the caller to set the "worldSpaceToNodeLocal" property to compute the hit of the mouse ray into the node (in world coordinate system) as well as rendering the cached bitmap in the node itself. The properties cachedRect and cachedTexture of Group2D will give you what you need to do that.
|
|
|
+ * - maxAdaptiveCanvasSize: set the max size (width and height) of the bitmap that will contain the cached version of the WorldSpace Canvas. Default is 1024 or less if it's not supported. In any case the value you give will be clipped by the maximum that WebGL supports on the running device. You can set any size, more than 1024 if you want, but testing proved it's a good max value for non "retina" like screens.
|
|
|
* - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
|
|
|
* - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
|
|
|
* - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
|
|
@@ -1061,11 +1137,23 @@ var BABYLON;
|
|
|
}
|
|
|
_super.call(this, scene, settings);
|
|
|
BABYLON.Prim2DBase._isCanvasInit = false;
|
|
|
+ this._renderableData._useMipMap = true;
|
|
|
+ this._renderableData._anisotropicLevel = 8;
|
|
|
//if (cachingStrategy === Canvas2D.CACHESTRATEGY_DONTCACHE) {
|
|
|
// throw new Error("CACHESTRATEGY_DONTCACHE cache Strategy can't be used for WorldSpace Canvas");
|
|
|
//}
|
|
|
var createWorldSpaceNode = !settings || (settings.customWorldSpaceNode == null);
|
|
|
var id = settings ? settings.id || null : null;
|
|
|
+ // Set the max size of texture allowed for the adaptive render of the world space canvas cached bitmap
|
|
|
+ var capMaxTextSize = this.engine.getCaps().maxRenderTextureSize;
|
|
|
+ var defaultTextSize = (Math.min(capMaxTextSize, 1024)); // Default is 4K if allowed otherwise the max allowed
|
|
|
+ if (settings.maxAdaptiveCanvasSize == null) {
|
|
|
+ this._maxAdaptiveWorldSpaceCanvasSize = defaultTextSize;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // We still clip the given value with the max allowed, the user may not be aware of these limitations
|
|
|
+ this._maxAdaptiveWorldSpaceCanvasSize = Math.min(settings.maxAdaptiveCanvasSize, capMaxTextSize);
|
|
|
+ }
|
|
|
if (createWorldSpaceNode) {
|
|
|
var plane = new BABYLON.WorldSpaceCanvas2DNode(id, scene, this);
|
|
|
var vertexData = BABYLON.VertexData.CreatePlane({
|
|
@@ -1075,7 +1163,7 @@ var BABYLON;
|
|
|
});
|
|
|
var mtl = new BABYLON.StandardMaterial(id + "_Material", scene);
|
|
|
this.applyCachedTexture(vertexData, mtl);
|
|
|
- vertexData.applyToMesh(plane, false);
|
|
|
+ vertexData.applyToMesh(plane, true);
|
|
|
mtl.specularColor = new BABYLON.Color3(0, 0, 0);
|
|
|
mtl.disableLighting = true;
|
|
|
mtl.useAlphaFromDiffuseTexture = true;
|