mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2024-12-25 19:28:28 +00:00
[engine] 新动态图集实现
This commit is contained in:
parent
9fca60746c
commit
8b0ef377a9
@ -95,7 +95,7 @@ var builtins = {
|
||||
effect.addRef();
|
||||
cc.sp.inited = true;
|
||||
cc.sp.multiBatcher.init();
|
||||
|
||||
if (cc.dynamicAtlasManager.maxAtlasCount === -1) cc.dynamicAtlasManager.maxAtlasCount = cc.sp.MAX_MULTITEXTURE_NUM;
|
||||
cb();
|
||||
});
|
||||
},
|
||||
|
@ -828,6 +828,11 @@ let SpriteFrame = cc.Class(/** @lends cc.SpriteFrame# */{
|
||||
handle.result.push(this, '_textureSetter', textureUuid);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
cc.dynamicAtlasManager && cc.dynamicAtlasManager.deleteSpriteFrame(this);
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -478,7 +478,7 @@ let Label = cc.Class({
|
||||
if (this.cacheMode === oldValue) return;
|
||||
|
||||
if (oldValue === CacheMode.BITMAP && !(this.font instanceof cc.BitmapFont)) {
|
||||
this._frame && this._frame._resetDynamicAtlasFrame();
|
||||
deleteFromDynamicAtlas(this, this._frame);
|
||||
}
|
||||
|
||||
if (oldValue === CacheMode.CHAR) {
|
||||
@ -583,6 +583,10 @@ let Label = cc.Class({
|
||||
type: RenderComponent.EnableType,
|
||||
default: RenderComponent.EnableType.GLOBAL,
|
||||
},
|
||||
allowDynamicAtlas: {
|
||||
type: RenderComponent.EnableType,
|
||||
default: RenderComponent.EnableType.GLOBAL,
|
||||
},
|
||||
},
|
||||
|
||||
statics: {
|
||||
@ -641,9 +645,12 @@ let Label = cc.Class({
|
||||
this._assemblerData = null;
|
||||
this._letterTexture = null;
|
||||
if (this._ttfTexture) {
|
||||
// HACK 由于会出现多个 uuid 一样的情况,销毁时会将动态图集中的区域直接销毁,导致其它使用该区域的 Label 显示错误,所以先将 packable = false,不走销毁逻辑,在下方走 frame 的回收逻辑
|
||||
this._ttfTexture._packable = false;
|
||||
this._ttfTexture.destroy();
|
||||
this._ttfTexture = null;
|
||||
}
|
||||
this._resetFrame();
|
||||
this._super();
|
||||
},
|
||||
|
||||
@ -760,7 +767,7 @@ let Label = cc.Class({
|
||||
}
|
||||
|
||||
if (this.cacheMode !== CacheMode.CHAR) {
|
||||
this._frame._resetDynamicAtlasFrame();
|
||||
deleteFromDynamicAtlas(this, this._frame);
|
||||
this._frame._refreshTexture(this._ttfTexture);
|
||||
if (this._srcBlendFactor === cc.macro.BlendFactor.ONE && !CC_NATIVERENDERER) {
|
||||
this._ttfTexture.setPremultiplyAlpha(true);
|
||||
|
@ -374,7 +374,31 @@ let RichText = cc.Class({
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
allowDynamicAtlas: {
|
||||
type: RenderComponent.EnableType,
|
||||
default: RenderComponent.EnableType.GLOBAL,
|
||||
notify: function (oldValue) {
|
||||
if (this.allowDynamicAtlas === oldValue) return;
|
||||
for (let i = 0; i < this._labelSegments.length; i++) {
|
||||
const labelComponent = this._labelSegments[i].getComponent(cc.Label);
|
||||
if (labelComponent) {
|
||||
labelComponent.allowDynamicAtlas = this.allowDynamicAtlas;
|
||||
}
|
||||
const spriteComponent = this._labelSegments[i].getComponent(cc.Sprite);
|
||||
if (spriteComponent) {
|
||||
spriteComponent.allowDynamicAtlas = this.allowDynamicAtlas;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < this._labelSegmentsCache.length; i++) {
|
||||
const labelComponent = this._labelSegmentsCache[i].getComponent(cc.Label);
|
||||
if (labelComponent) {
|
||||
labelComponent.allowDynamicAtlas = this.allowDynamicAtlas;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
statics: {
|
||||
@ -682,6 +706,8 @@ let RichText = cc.Class({
|
||||
let spriteComponent = spriteNode.addComponent(cc.Sprite);
|
||||
|
||||
spriteComponent.autoSwitchMaterial = this.autoSwitchMaterial;
|
||||
spriteComponent.allowDynamicAtlas = this.allowDynamicAtlas;
|
||||
|
||||
switch (richTextElement.style.imageAlign)
|
||||
{
|
||||
case 'top':
|
||||
@ -975,6 +1001,8 @@ let RichText = cc.Class({
|
||||
labelComponent.cacheMode = this.cacheMode;
|
||||
|
||||
labelComponent.autoSwitchMaterial = this.autoSwitchMaterial;
|
||||
labelComponent.allowDynamicAtlas = this.allowDynamicAtlas;
|
||||
|
||||
let isAsset = this.font instanceof cc.Font;
|
||||
if (isAsset && !this._isSystemFontUsed) {
|
||||
labelComponent.font = this.font;
|
||||
|
@ -392,6 +392,10 @@ var Sprite = cc.Class({
|
||||
type: RenderComponent.EnableType,
|
||||
default: RenderComponent.EnableType.GLOBAL,
|
||||
},
|
||||
allowDynamicAtlas: {
|
||||
type: RenderComponent.EnableType,
|
||||
default: RenderComponent.EnableType.GLOBAL,
|
||||
},
|
||||
},
|
||||
|
||||
statics: {
|
||||
|
@ -27,12 +27,15 @@ export default class Assembler {
|
||||
packDynamicAtlasAndCheckMaterial(comp, frame) {
|
||||
if (CC_TEST) return false;
|
||||
|
||||
const allowDynamicAtlas = comp.allowDynamicAtlas;
|
||||
if ((cc.sp.allowDynamicAtlas && allowDynamicAtlas === 0) || allowDynamicAtlas === 1) {
|
||||
if (!frame._original && dynamicAtlasManager && frame._texture.packable && frame._texture.loaded) {
|
||||
let packedFrame = dynamicAtlasManager.insertSpriteFrame(frame);
|
||||
if (packedFrame) {
|
||||
frame._setDynamicAtlasFrame(packedFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const material = comp._materials[0];
|
||||
if (!material) return false;
|
||||
|
@ -1,140 +1,5 @@
|
||||
const RenderTexture = require('../../../assets/CCRenderTexture');
|
||||
|
||||
const space = 2;
|
||||
|
||||
// 保留备用
|
||||
function Atlas(width, height) {
|
||||
let texture = new RenderTexture();
|
||||
texture.initWithSize(width, height);
|
||||
texture.update();
|
||||
|
||||
this._texture = texture;
|
||||
|
||||
this._x = space;
|
||||
this._y = space;
|
||||
this._nexty = space;
|
||||
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
this._innerTextureInfos = {};
|
||||
this._innerSpriteFrames = [];
|
||||
|
||||
this._count = 0;
|
||||
}
|
||||
|
||||
Atlas.DEFAULT_HASH = (new RenderTexture())._getHash();
|
||||
|
||||
cc.js.mixin(Atlas.prototype, {
|
||||
insertSpriteFrame (spriteFrame) {
|
||||
let rect = spriteFrame._rect,
|
||||
texture = spriteFrame._texture,
|
||||
info = this._innerTextureInfos[texture._id];
|
||||
|
||||
let sx = rect.x, sy = rect.y;
|
||||
|
||||
if (info) {
|
||||
sx += info.x;
|
||||
sy += info.y;
|
||||
}
|
||||
else {
|
||||
let width = texture.width, height = texture.height;
|
||||
|
||||
if ((this._x + width + space) > this._width) {
|
||||
this._x = space;
|
||||
this._y = this._nexty;
|
||||
}
|
||||
|
||||
if ((this._y + height + space) > this._nexty) {
|
||||
this._nexty = this._y + height + space;
|
||||
}
|
||||
|
||||
if (this._nexty > this._height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// texture bleeding
|
||||
if (cc.dynamicAtlasManager.textureBleeding) {
|
||||
// Smaller frame is more likely to be affected by linear filter
|
||||
if (width <= 8 || height <= 8) {
|
||||
this._texture.drawTextureAt(texture, this._x-1, this._y-1);
|
||||
this._texture.drawTextureAt(texture, this._x-1, this._y+1);
|
||||
this._texture.drawTextureAt(texture, this._x+1, this._y-1);
|
||||
this._texture.drawTextureAt(texture, this._x+1, this._y+1);
|
||||
}
|
||||
|
||||
this._texture.drawTextureAt(texture, this._x-1, this._y);
|
||||
this._texture.drawTextureAt(texture, this._x+1, this._y);
|
||||
this._texture.drawTextureAt(texture, this._x, this._y-1);
|
||||
this._texture.drawTextureAt(texture, this._x, this._y+1);
|
||||
}
|
||||
|
||||
this._texture.drawTextureAt(texture, this._x, this._y);
|
||||
|
||||
this._innerTextureInfos[texture._id] = {
|
||||
x: this._x,
|
||||
y: this._y,
|
||||
texture: texture
|
||||
};
|
||||
|
||||
this._count++;
|
||||
|
||||
sx += this._x;
|
||||
sy += this._y;
|
||||
|
||||
this._x += width + space;
|
||||
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
let frame = {
|
||||
x: sx,
|
||||
y: sy,
|
||||
texture: this._texture
|
||||
}
|
||||
|
||||
this._innerSpriteFrames.push(spriteFrame);
|
||||
|
||||
return frame;
|
||||
},
|
||||
|
||||
update () {
|
||||
if (!this._dirty) return;
|
||||
this._texture.update();
|
||||
this._dirty = false;
|
||||
},
|
||||
|
||||
deleteInnerTexture (texture) {
|
||||
if (texture && this._innerTextureInfos[texture._id]) {
|
||||
delete this._innerTextureInfos[texture._id];
|
||||
this._count--;
|
||||
}
|
||||
},
|
||||
|
||||
isEmpty () {
|
||||
return this._count <= 0;
|
||||
},
|
||||
|
||||
reset () {
|
||||
this._x = space;
|
||||
this._y = space;
|
||||
this._nexty = space;
|
||||
|
||||
let frames = this._innerSpriteFrames;
|
||||
for (let i = 0, l = frames.length; i < l; i++) {
|
||||
let frame = frames[i];
|
||||
if (!frame.isValid) {
|
||||
continue;
|
||||
}
|
||||
frame._resetDynamicAtlasFrame();
|
||||
}
|
||||
this._innerSpriteFrames.length = 0;
|
||||
this._innerTextureInfos = {};
|
||||
},
|
||||
|
||||
destroy () {
|
||||
this.reset();
|
||||
this._texture.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Atlas;
|
||||
|
@ -1,12 +1,15 @@
|
||||
const Atlas = require('./atlas');
|
||||
// const Atlas = require('./atlas');
|
||||
import { Atlas, Rect } from './reusable-atlas';
|
||||
|
||||
let _atlases = [];
|
||||
let _atlasIndex = -1;
|
||||
|
||||
let _maxAtlasCount = 5;
|
||||
let _maxAtlasCount = -1;
|
||||
let _textureSize = 2048;
|
||||
let _maxFrameSize = 512;
|
||||
let _textureBleeding = true;
|
||||
let _autoMultiBatch = true;
|
||||
let _autoResetBeforeSceneLoad = true;
|
||||
|
||||
let _debugNode = null;
|
||||
|
||||
@ -15,13 +18,16 @@ function newAtlas () {
|
||||
if (!atlas) {
|
||||
atlas = new Atlas(_textureSize, _textureSize);
|
||||
_atlases.push(atlas);
|
||||
if (dynamicAtlasManager.autoMultiBatch) cc.sp.multiBatcher.requsetMaterial(atlas._texture);
|
||||
}
|
||||
return atlas;
|
||||
}
|
||||
|
||||
function beforeSceneLoad() {
|
||||
if (_autoResetBeforeSceneLoad) {
|
||||
dynamicAtlasManager.reset();
|
||||
}
|
||||
}
|
||||
|
||||
let _enabled = false;
|
||||
|
||||
@ -32,6 +38,7 @@ let _enabled = false;
|
||||
*/
|
||||
let dynamicAtlasManager = {
|
||||
Atlas: Atlas,
|
||||
Rect: Rect,
|
||||
|
||||
/**
|
||||
* !#en Enable or disable the dynamic atlas, see [Dynamic Atlas](https://docs.cocos.com/creator/manual/en/advanced-topics/dynamic-atlas.html) for details.
|
||||
@ -119,6 +126,58 @@ let dynamicAtlasManager = {
|
||||
_maxFrameSize = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* !#en Is enable autoMultiBatch.
|
||||
* !#zh 是否开启自动多纹理合批
|
||||
* @property autoMultiBatch
|
||||
* @type {Boolean}
|
||||
*/
|
||||
get autoMultiBatch() {
|
||||
return _autoMultiBatch;
|
||||
},
|
||||
|
||||
set autoMultiBatch(enable) {
|
||||
if (_autoMultiBatch === enable) return;
|
||||
|
||||
if (enable) {
|
||||
for (let i = 0, l = _atlases.length; i < l; i++) {
|
||||
cc.sp.multiBatcher.requsetMaterial(_atlases[i]._texture);
|
||||
}
|
||||
}
|
||||
|
||||
_autoMultiBatch = enable;
|
||||
},
|
||||
|
||||
/**
|
||||
* !#en Is enable autoResetBeforeSceneLoad.
|
||||
* !#zh 是否在场景切换时清空所有图集
|
||||
* @property autoResetBeforeSceneLoad
|
||||
* @type {Boolean}
|
||||
*/
|
||||
get autoResetBeforeSceneLoad() {
|
||||
return _autoResetBeforeSceneLoad;
|
||||
},
|
||||
|
||||
set autoResetBeforeSceneLoad(enable) {
|
||||
if (_autoResetBeforeSceneLoad === enable) return;
|
||||
_autoResetBeforeSceneLoad = enable;
|
||||
},
|
||||
|
||||
/**
|
||||
* !#en atlases
|
||||
* !#zh 图集数组
|
||||
* @property atlases
|
||||
* @type {Atlas}
|
||||
*/
|
||||
get atlases() {
|
||||
return _atlases;
|
||||
},
|
||||
|
||||
/**
|
||||
* 已用空间集合
|
||||
*/
|
||||
rects: Object.create(null),
|
||||
|
||||
/**
|
||||
* !#en The minimum size of the picture that can be added to the atlas.
|
||||
* !#zh 可以添加进图集的图片的最小尺寸。
|
||||
@ -135,24 +194,75 @@ let dynamicAtlasManager = {
|
||||
*/
|
||||
insertSpriteFrame(spriteFrame) {
|
||||
if (CC_EDITOR) return null;
|
||||
if (!_enabled || _atlasIndex === _maxAtlasCount ||
|
||||
!spriteFrame || spriteFrame._original) return null;
|
||||
if (!_enabled || !spriteFrame || spriteFrame._original) return null;
|
||||
|
||||
if (!spriteFrame._texture.packable) return null;
|
||||
let atlas, frame;
|
||||
|
||||
let atlas = _atlases[_atlasIndex];
|
||||
if (!atlas) {
|
||||
atlas = newAtlas();
|
||||
// 是否贴图已经在图集中
|
||||
let rect = spriteFrame._rect,
|
||||
texture = spriteFrame._texture,
|
||||
info = this.rects[texture._uuid];
|
||||
|
||||
let sx = rect.x, sy = rect.y;
|
||||
|
||||
if (info) {
|
||||
sx += info.x;
|
||||
sy += info.y;
|
||||
|
||||
info.spriteFrames.push(spriteFrame);
|
||||
|
||||
frame = {
|
||||
x: sx,
|
||||
y: sy,
|
||||
texture: info.atlas._texture,
|
||||
};
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
let frame = atlas.insertSpriteFrame(spriteFrame);
|
||||
if (!frame && _atlasIndex !== _maxAtlasCount) {
|
||||
// 尝试加入已有图集
|
||||
for (let i = 0; i <= _atlasIndex; i++) {
|
||||
atlas = _atlases[i];
|
||||
frame = atlas.insertSpriteFrame(spriteFrame);
|
||||
if (frame) {
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新图集尝试加入
|
||||
if (_atlasIndex + 1 < _maxAtlasCount) {
|
||||
atlas = newAtlas();
|
||||
return atlas.insertSpriteFrame(spriteFrame);
|
||||
}
|
||||
|
||||
return frame;
|
||||
},
|
||||
|
||||
/**
|
||||
* !#en Delete a sprite frame from the dynamic atlas.
|
||||
* !#zh 使精灵帧取消使用动态图集
|
||||
* @method deleteSpriteFrame
|
||||
* @param {SpriteFrame} spriteFrame
|
||||
*/
|
||||
deleteSpriteFrame(spriteFrame) {
|
||||
if (spriteFrame && !CC_TEST) {
|
||||
if (spriteFrame._original) {
|
||||
this.deleteAtlasSpriteFrame(spriteFrame);
|
||||
spriteFrame._resetDynamicAtlasFrame();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* !#en Delete a texture from the dynamic atlas.
|
||||
* !#zh 从动态图集删除该贴图,使用该贴图的精灵帧会被还原
|
||||
* @method deleteTexture
|
||||
* @param {Texture2D} texture
|
||||
*/
|
||||
deleteTexture(texture) {
|
||||
this.deleteAtlasTexture(texture);
|
||||
},
|
||||
|
||||
/**
|
||||
* !#en Resets all dynamic atlas, and the existing ones will be destroyed.
|
||||
* !#zh 重置所有动态图集,已有的动态图集会被销毁。
|
||||
@ -170,18 +280,18 @@ let dynamicAtlasManager = {
|
||||
if (!spriteFrame._original) return;
|
||||
|
||||
let texture = spriteFrame._original._texture;
|
||||
this.deleteAtlasTexture(texture);
|
||||
for (let i = _atlases.length - 1; i >= 0; i--) {
|
||||
if (_atlases[i].deleteSpriteFrame(texture, spriteFrame)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deleteAtlasTexture (texture) {
|
||||
if (texture) {
|
||||
for (let i = _atlases.length - 1; i >= 0; i--) {
|
||||
_atlases[i].deleteInnerTexture(texture);
|
||||
|
||||
if (_atlases[i].isEmpty()) {
|
||||
_atlases[i].destroy();
|
||||
_atlases.splice(i, 1);
|
||||
_atlasIndex--;
|
||||
if (_atlases[i].deleteInnerTexture(texture, true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,593 @@
|
||||
// @ts-expect-error
|
||||
const RenderTexture = require('../../../assets/CCRenderTexture');
|
||||
|
||||
|
||||
/**
|
||||
* 矩形
|
||||
*/
|
||||
export class Rect {
|
||||
|
||||
/**
|
||||
* 对象池
|
||||
*/
|
||||
static pool: Rect[] = [];
|
||||
|
||||
/**
|
||||
* 对象池指针
|
||||
*/
|
||||
static pointer: number = 0;
|
||||
|
||||
|
||||
/**
|
||||
* 复用
|
||||
*/
|
||||
static reuse(atlas: Atlas, width: number, height: number, x: number, y: number) {
|
||||
if (this.pointer === 0) {
|
||||
for (let i = 0; i < 128; i++) {
|
||||
Rect.pool[i] = new Rect(atlas, 0, 0, 0, 0);
|
||||
}
|
||||
this.pointer += 128;
|
||||
}
|
||||
|
||||
this.pointer--;
|
||||
const rect = this.pool[this.pointer];
|
||||
|
||||
rect.atlas = atlas;
|
||||
rect.width = width;
|
||||
rect.height = height;
|
||||
rect.x = x;
|
||||
rect.y = y;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 回收
|
||||
*/
|
||||
static recycle(rect: Rect) {
|
||||
rect.atlas = undefined!;
|
||||
rect.uuid = "";
|
||||
rect.spriteFrames.length = 0;
|
||||
rect.parentRect = undefined;
|
||||
rect.subRectA = undefined;
|
||||
rect.subRectB = undefined;
|
||||
rect.subRectC = undefined;
|
||||
|
||||
rect.cacheIndex = -1;
|
||||
|
||||
this.pool[this.pointer] = rect;
|
||||
this.pointer++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 所属 Atlas
|
||||
*/
|
||||
atlas: Atlas;
|
||||
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
width: number = 0;
|
||||
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
height: number = 0;
|
||||
|
||||
/**
|
||||
* 横坐标
|
||||
*/
|
||||
x: number = 0;
|
||||
|
||||
/**
|
||||
* 纵坐标
|
||||
*/
|
||||
y: number = 0;
|
||||
|
||||
/**
|
||||
* 在 freeRects 中的下标
|
||||
*/
|
||||
cacheIndex: number = -1;
|
||||
|
||||
/**
|
||||
* cc.Texture2D UUID
|
||||
*/
|
||||
uuid: string = '';
|
||||
|
||||
/**
|
||||
* 使用该贴图的精灵帧数组
|
||||
*/
|
||||
spriteFrames: any[] = [];
|
||||
|
||||
/**
|
||||
* 父矩形
|
||||
*/
|
||||
parentRect: Rect | undefined;
|
||||
|
||||
/**
|
||||
* 子矩形之一
|
||||
*/
|
||||
subRectA: Rect | undefined;
|
||||
|
||||
/**
|
||||
* 子矩形之一
|
||||
*/
|
||||
subRectB: Rect | undefined;
|
||||
|
||||
/**
|
||||
* 子矩形之一
|
||||
*/
|
||||
subRectC: Rect | undefined;
|
||||
|
||||
/**
|
||||
* 子矩形或自身计数
|
||||
*/
|
||||
used: number = 0;
|
||||
|
||||
/**
|
||||
* 像素数
|
||||
*/
|
||||
get sizes() {
|
||||
return this.width * this.height;
|
||||
}
|
||||
|
||||
|
||||
constructor(atlas: Atlas, width: number, height: number, x: number, y: number) {
|
||||
this.atlas = atlas;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 动态图集
|
||||
*
|
||||
* 装箱算法:类似断头台装箱算法
|
||||
* 合并算法:树形回退模式
|
||||
*/
|
||||
export class Atlas {
|
||||
|
||||
/**
|
||||
* 当自由空间的某边长度不足该值则直接忽略该空间
|
||||
*/
|
||||
static ignoreRectSize: number = 10;
|
||||
|
||||
/**
|
||||
* 默认 Atlas
|
||||
*/
|
||||
static DEFAULT_HASH = (new RenderTexture())._getHash();
|
||||
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
width: number = 0;
|
||||
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
height: number = 0;
|
||||
|
||||
/**
|
||||
* 间距
|
||||
*/
|
||||
padding: number = 0;
|
||||
|
||||
/**
|
||||
* 边距
|
||||
*/
|
||||
border: number = 0;
|
||||
|
||||
/**
|
||||
* 根矩形
|
||||
*/
|
||||
rootRect: Rect;
|
||||
|
||||
/**
|
||||
* 自由空间
|
||||
*/
|
||||
freeRects: Rect[] = [];
|
||||
|
||||
/**
|
||||
* 已使用数量
|
||||
*/
|
||||
_count = 0;
|
||||
|
||||
/**
|
||||
* cc.RenderTexture
|
||||
*/
|
||||
_texture: any;
|
||||
|
||||
/**
|
||||
* texture update dirty
|
||||
*/
|
||||
_dirty: boolean = false;
|
||||
|
||||
|
||||
constructor(width: number, height: number, padding: number = 2, border: number = 2) {
|
||||
const texture = new RenderTexture();
|
||||
texture.initWithSize(width, height);
|
||||
texture.update();
|
||||
this._texture = texture;
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.padding = padding;
|
||||
this.border = border;
|
||||
|
||||
this.rootRect = Rect.reuse(
|
||||
this,
|
||||
this.width + this.padding - this.border * 2,
|
||||
this.height + this.padding - this.border * 2,
|
||||
this.border,
|
||||
this.border,
|
||||
);
|
||||
this.pushFreeRect(this.rootRect);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* push to free rects
|
||||
*/
|
||||
protected pushFreeRect(rect: Rect) {
|
||||
const i = this.freeRects.push(rect) - 1;
|
||||
rect.cacheIndex = i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* faster remove from free rects
|
||||
*/
|
||||
protected removeFreeRect(index: number) {
|
||||
const temp = this.freeRects[index];
|
||||
const temp2 = this.freeRects[this.freeRects.length - 1];
|
||||
temp2.cacheIndex = index;
|
||||
temp.cacheIndex = -1;
|
||||
this.freeRects[index] = temp2;
|
||||
this.freeRects.pop();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* change member from free rects
|
||||
*/
|
||||
protected replaceFreeRect(index: number, rect: Rect) {
|
||||
this.freeRects[index].cacheIndex = -1;
|
||||
rect.cacheIndex = index;
|
||||
this.freeRects[index] = rect;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 插入 SpriteFrame
|
||||
*/
|
||||
insertSpriteFrame(spriteFrame: any) {
|
||||
let rect = spriteFrame._rect,
|
||||
texture = spriteFrame._texture;
|
||||
|
||||
let sx = rect.x, sy = rect.y;
|
||||
let width = texture.width, height = texture.height;
|
||||
|
||||
const result = this.insert(texture);
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// texture bleeding
|
||||
if (cc.dynamicAtlasManager.textureBleeding) {
|
||||
// Smaller frame is more likely to be affected by linear filter
|
||||
if (width <= 8 || height <= 8) {
|
||||
this._texture.drawTextureAt(texture, result.x - 1, result.y - 1);
|
||||
this._texture.drawTextureAt(texture, result.x - 1, result.y + 1);
|
||||
this._texture.drawTextureAt(texture, result.x + 1, result.y - 1);
|
||||
this._texture.drawTextureAt(texture, result.x + 1, result.y + 1);
|
||||
}
|
||||
|
||||
this._texture.drawTextureAt(texture, result.x - 1, result.y);
|
||||
this._texture.drawTextureAt(texture, result.x + 1, result.y);
|
||||
this._texture.drawTextureAt(texture, result.x, result.y - 1);
|
||||
this._texture.drawTextureAt(texture, result.x, result.y + 1);
|
||||
}
|
||||
|
||||
this._texture.drawTextureAt(texture, result.x, result.y);
|
||||
|
||||
this._count++;
|
||||
|
||||
sx += result.x;
|
||||
sy += result.y;
|
||||
|
||||
result.spriteFrames.push(spriteFrame);
|
||||
|
||||
this._dirty = true;
|
||||
|
||||
let frame = {
|
||||
x: sx,
|
||||
y: sy,
|
||||
texture: this._texture,
|
||||
};
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 插入子函数
|
||||
*/
|
||||
insert(texture: any) {
|
||||
const width = texture.width + this.padding, height = texture.height + this.padding;
|
||||
let score = Number.MAX_VALUE;
|
||||
let areaFit = 0;
|
||||
let original: Rect | undefined = undefined;
|
||||
let originalIndex = 0;
|
||||
|
||||
// 查找足够容纳的空区域
|
||||
for (let i = 0; i < this.freeRects.length; i++) {
|
||||
const rect = this.freeRects[i];
|
||||
if (rect.width >= width && rect.height >= height) {
|
||||
areaFit = rect.sizes - width * height;
|
||||
if (areaFit < score) {
|
||||
original = rect;
|
||||
originalIndex = i;
|
||||
score = areaFit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切割空区域
|
||||
if (original) {
|
||||
if (original.width === width && original.height === height) {
|
||||
original.uuid = texture._uuid;
|
||||
original.used++;
|
||||
if (original.parentRect) original.parentRect.used++;
|
||||
cc.dynamicAtlasManager.rects[texture._uuid] = original;
|
||||
this.removeFreeRect(originalIndex);
|
||||
return original;
|
||||
}
|
||||
|
||||
const best = Rect.reuse(this, width, height, original.x, original.y);
|
||||
let tmp: Rect;
|
||||
if (best.y + best.height < original.y + original.height) {
|
||||
tmp = Rect.reuse(
|
||||
this,
|
||||
original.width,
|
||||
original.y + original.height - (best.y + best.height),
|
||||
original.x,
|
||||
best.y + best.height,
|
||||
);
|
||||
|
||||
tmp.parentRect = original;
|
||||
original.subRectB = tmp;
|
||||
|
||||
if (tmp.width > Atlas.ignoreRectSize && tmp.height > Atlas.ignoreRectSize) {
|
||||
// 替换旧区域
|
||||
this.replaceFreeRect(originalIndex, tmp);
|
||||
originalIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (best.x + best.width < original.x + original.width) {
|
||||
tmp = Rect.reuse(
|
||||
this,
|
||||
original.x + original.width - (best.x + best.width),
|
||||
original.height - (original.y + original.height - (best.y + best.height)),
|
||||
best.x + best.width,
|
||||
original.y,
|
||||
);
|
||||
|
||||
tmp.parentRect = original;
|
||||
original.subRectC = tmp;
|
||||
|
||||
if (tmp.width > Atlas.ignoreRectSize && tmp.height > Atlas.ignoreRectSize) {
|
||||
if (originalIndex !== -1) {
|
||||
// 替换旧区域
|
||||
this.replaceFreeRect(originalIndex, tmp);
|
||||
originalIndex = -1;
|
||||
} else {
|
||||
this.pushFreeRect(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (originalIndex !== -1) {
|
||||
this.removeFreeRect(originalIndex);
|
||||
}
|
||||
|
||||
best.parentRect = original;
|
||||
original.subRectA = best;
|
||||
best.used++;
|
||||
original.used++;
|
||||
if (original.used === 1 && original.parentRect) original.parentRect.used++;
|
||||
best.uuid = texture._uuid;
|
||||
cc.dynamicAtlasManager.rects[texture._uuid] = best;
|
||||
return best;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* update texture
|
||||
*/
|
||||
update() {
|
||||
if (!this._dirty) return;
|
||||
this._texture.update();
|
||||
this._dirty = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除精灵帧
|
||||
*/
|
||||
deleteSpriteFrame(texture: any, frame: any) {
|
||||
if (texture) {
|
||||
const rect: Rect | undefined = cc.dynamicAtlasManager.rects[texture._uuid];
|
||||
if (rect) {
|
||||
const index = rect.spriteFrames.indexOf(frame);
|
||||
if (index !== -1) {
|
||||
rect.spriteFrames.splice(index, 1);
|
||||
|
||||
// 判断如果没有引用则删除 Texture
|
||||
if (rect.spriteFrames.length === 0) {
|
||||
rect.atlas.deleteInnerRect(rect);
|
||||
}
|
||||
} else {
|
||||
cc.warn('[Dynamic Atlas] can\'t find spriteFrame in Rect.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除子矩形
|
||||
*/
|
||||
deleteInnerRect(rect: Rect) {
|
||||
delete cc.dynamicAtlasManager.rects[rect.uuid];
|
||||
rect.uuid = "";
|
||||
this._count--;
|
||||
|
||||
// 还原 SpriteFrame
|
||||
for (const spriteFrame of rect.spriteFrames) {
|
||||
if (spriteFrame.isValid) {
|
||||
spriteFrame._resetDynamicAtlasFrame();
|
||||
}
|
||||
}
|
||||
rect.spriteFrames.length = 0;
|
||||
|
||||
this.tryMergeRecycle(rect);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除贴图
|
||||
*/
|
||||
deleteInnerTexture(texture: any) {
|
||||
if (texture) {
|
||||
const rect: Rect | undefined = cc.dynamicAtlasManager.rects[texture._uuid];
|
||||
if (rect) {
|
||||
rect.atlas.deleteInnerRect(rect);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 尝试合并和回收
|
||||
*/
|
||||
protected tryMergeRecycle(rect: Rect) {
|
||||
let old: Rect | undefined = undefined;
|
||||
let parent: Rect | undefined = rect;
|
||||
while (parent) {
|
||||
parent.used--;
|
||||
if (parent.used === 0) {
|
||||
// 回收所有子矩形
|
||||
if (parent.subRectA) {
|
||||
// 可能是 ignoreRect
|
||||
const i = parent.subRectA.cacheIndex;
|
||||
if (i !== -1) {
|
||||
this.removeFreeRect(i);
|
||||
}
|
||||
Rect.recycle(parent.subRectA);
|
||||
parent.subRectA = undefined;
|
||||
}
|
||||
if (parent.subRectB) {
|
||||
const i = parent.subRectB.cacheIndex;
|
||||
if (i !== -1) {
|
||||
this.removeFreeRect(i);
|
||||
}
|
||||
Rect.recycle(parent.subRectB);
|
||||
parent.subRectB = undefined;
|
||||
}
|
||||
if (parent.subRectC) {
|
||||
const i = parent.subRectC.cacheIndex;
|
||||
if (i !== -1) {
|
||||
this.removeFreeRect(i);
|
||||
}
|
||||
Rect.recycle(parent.subRectC);
|
||||
parent.subRectC = undefined;
|
||||
}
|
||||
old = parent;
|
||||
parent = parent.parentRect;
|
||||
} else {
|
||||
if (old) {
|
||||
if (old.width > Atlas.ignoreRectSize && old.height > Atlas.ignoreRectSize) {
|
||||
this.pushFreeRect(old);
|
||||
}
|
||||
}
|
||||
old = parent;
|
||||
parent = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (old === this.rootRect && old.used === 0) {
|
||||
this.pushFreeRect(old);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否未使用
|
||||
*/
|
||||
isEmpty() {
|
||||
return this._count <= 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清空
|
||||
*/
|
||||
reset() {
|
||||
const rects = cc.dynamicAtlasManager.rects;
|
||||
for (const key in rects) {
|
||||
const rect: Rect = rects[key];
|
||||
if (rect.atlas === this) {
|
||||
delete rects[key];
|
||||
for (const spriteFrame of rect.spriteFrames) {
|
||||
if (spriteFrame.isValid) {
|
||||
spriteFrame._resetDynamicAtlasFrame();
|
||||
}
|
||||
}
|
||||
Rect.recycle(rect);
|
||||
}
|
||||
}
|
||||
|
||||
for (const rect of this.freeRects) {
|
||||
Rect.recycle(rect);
|
||||
}
|
||||
|
||||
this.freeRects.length = 0;
|
||||
this._count = 0;
|
||||
|
||||
this.rootRect = Rect.reuse(
|
||||
this,
|
||||
this.width + this.padding - this.border * 2,
|
||||
this.height + this.padding - this.border * 2,
|
||||
this.border,
|
||||
this.border,
|
||||
);
|
||||
this.pushFreeRect(this.rootRect)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
destroy() {
|
||||
this.reset();
|
||||
this._texture.destroy();
|
||||
}
|
||||
|
||||
}
|
@ -355,6 +355,34 @@ export default class TTFAssembler extends Assembler2D {
|
||||
return this.packDynamicAtlasAndCheckMaterial(comp, frame);
|
||||
}
|
||||
|
||||
packDynamicAtlasAndCheckMaterial(comp, frame) {
|
||||
const allowDynamicAtlas = comp.allowDynamicAtlas;
|
||||
if ((cc.sp.allowDynamicAtlas && allowDynamicAtlas === 0) || allowDynamicAtlas === 1) {
|
||||
frame._texture._uuid = _fontDesc
|
||||
+ _overflow
|
||||
+ (_premultiply ? 'P' : 'NP')
|
||||
+ (_enableUnderline ? 'UL' : 'NUL')
|
||||
+ _string;
|
||||
|
||||
if (_outlineComp) {
|
||||
frame._texture._uuid += _outlineComp.color.toHEX()
|
||||
+ ','
|
||||
+ _outlineComp.width
|
||||
+ ',';
|
||||
}
|
||||
|
||||
if (_shadowComp) {
|
||||
frame._texture._uuid += _shadowComp.color.toHEX()
|
||||
+ _shadowComp.offset.x
|
||||
+ ','
|
||||
+ _shadowComp.offset.y
|
||||
+ ','
|
||||
+ _shadowComp.blur;
|
||||
}
|
||||
}
|
||||
return super.packDynamicAtlasAndCheckMaterial(comp, frame);
|
||||
}
|
||||
|
||||
_updateLabelDimensions () {
|
||||
let maxTextureSize = cc.renderer.device.caps ? cc.renderer.device.caps.maxTextureSize : MAX_SIZE;
|
||||
if (_canvasSize.width > maxTextureSize || _canvasSize.height > maxTextureSize) {
|
||||
|
Loading…
Reference in New Issue
Block a user