@blooooork/three-optimization
Version:
High-performance optimization tools for Three.js including texture atlas management, asset loading, and memory optimization
150 lines (149 loc) • 5.39 kB
JavaScript
import * as g from "three";
import { GLTFLoader as x } from "three/examples/jsm/loaders/GLTFLoader.js";
class A {
constructor(t = {}) {
this.maxAtlasSize = t.maxAtlasSize || 4096, this.padding = t.padding || 2, this.textureMap = /* @__PURE__ */ new Map(), this.atlases = /* @__PURE__ */ new Map();
}
async createAtlas(t, e = "diffuse") {
const s = [...t].sort((r, o) => {
const n = r.image ? r.image.width * r.image.height : 0;
return (o.image ? o.image.width * o.image.height : 0) - n;
}), a = document.createElement("canvas"), d = a.getContext("2d");
let i = 0, m = 0, h = 0, l = 0, f = 0;
for (const r of s) {
if (!r.image) continue;
const o = r.image.width + this.padding * 2, n = r.image.height + this.padding * 2;
i + o > this.maxAtlasSize && (i = 0, m += h, h = 0), h = Math.max(h, n), l = Math.max(l, i + o), f = Math.max(f, m + n), i += o;
}
a.width = g.MathUtils.ceilPowerOfTwo(l), a.height = g.MathUtils.ceilPowerOfTwo(f), i = 0, m = 0, h = 0;
for (const r of s) {
if (!r.image) continue;
const o = r.image.width, n = r.image.height;
i + o + this.padding * 2 > this.maxAtlasSize && (i = 0, m += h, h = 0), d.drawImage(
r.image,
i + this.padding,
m + this.padding,
o,
n
);
const u = {
x: (i + this.padding) / a.width,
y: (m + this.padding) / a.height,
width: o / a.width,
height: n / a.height
};
this.textureMap.set(r.uuid, u), h = Math.max(h, n + this.padding * 2), i += o + this.padding * 2;
}
const c = new g.CanvasTexture(a);
return c.flipY = !1, this.atlases.set(e, c), c;
}
getTextureUVs(t) {
return this.textureMap.get(t);
}
getAtlas(t = "diffuse") {
return this.atlases.get(t);
}
dispose() {
this.atlases.forEach((t) => t.dispose()), this.atlases.clear(), this.textureMap.clear();
}
}
class M {
constructor() {
this.loadedAssets = /* @__PURE__ */ new Map(), this.loadingPromises = /* @__PURE__ */ new Map(), this.materialCache = /* @__PURE__ */ new Map(), this.gltfLoader = new x(), this.textureLoader = new g.TextureLoader();
}
async loadTexture(t) {
if (this.loadedAssets.has(t))
return this.loadedAssets.get(t);
if (this.loadingPromises.has(t))
return this.loadingPromises.get(t);
const e = new Promise((s, a) => {
this.textureLoader.load(
t,
(d) => {
this.loadedAssets.set(t, d), this.loadingPromises.delete(t), s(d);
},
void 0,
a
);
});
return this.loadingPromises.set(t, e), e;
}
async loadModel(t) {
if (this.loadedAssets.has(t))
return this.loadedAssets.get(t).scene.clone();
if (this.loadingPromises.has(t))
return (await this.loadingPromises.get(t)).scene.clone();
const e = new Promise((a, d) => {
this.gltfLoader.load(
t,
(i) => {
this.loadedAssets.set(t, i), this.loadingPromises.delete(t), a(i);
},
void 0,
d
);
});
return this.loadingPromises.set(t, e), (await e).scene.clone();
}
getMaterial(t, e) {
if (!this.materialCache.has(t)) {
const s = new g.MeshStandardMaterial({
map: e.map,
color: e.color,
transparent: e.transparent,
opacity: e.opacity,
side: e.side,
roughness: e.roughness || 1,
metalness: e.metalness || 0
});
this.materialCache.set(t, s);
}
return this.materialCache.get(t).clone();
}
dispose() {
this.loadedAssets.forEach((t) => {
t.dispose && t.dispose(), t.scene && t.scene.traverse((e) => {
e.geometry && e.geometry.dispose(), e.material && (Array.isArray(e.material) ? e.material.forEach((s) => s.dispose()) : e.material.dispose());
});
}), this.materialCache.forEach((t) => t.dispose()), this.loadedAssets.clear(), this.loadingPromises.clear(), this.materialCache.clear();
}
}
class y {
constructor() {
this.tracked = /* @__PURE__ */ new Set(), this.stats = {
geometries: 0,
textures: 0,
materials: 0,
objects: 0
};
}
track(t) {
this.tracked.has(t) || (this.tracked.add(t), this.stats.objects++, t.traverse((e) => {
e.geometry && this.stats.geometries++, e.material && (Array.isArray(e.material) ? (this.stats.materials += e.material.length, e.material.forEach((s) => {
s.map && this.stats.textures++;
})) : (this.stats.materials++, e.material.map && this.stats.textures++));
}));
}
untrack(t) {
this.tracked.has(t) && (this.tracked.delete(t), this.stats.objects--, t.traverse((e) => {
e.geometry && this.stats.geometries--, e.material && (Array.isArray(e.material) ? (this.stats.materials -= e.material.length, e.material.forEach((s) => {
s.map && this.stats.textures--;
})) : (this.stats.materials--, e.material.map && this.stats.textures--));
}));
}
getStats() {
return { ...this.stats };
}
dispose(t) {
this.untrack(t), t.traverse((e) => {
e.geometry && e.geometry.dispose(), e.material && (Array.isArray(e.material) ? e.material.forEach((s) => {
s.map && s.map.dispose(), s.dispose();
}) : (e.material.map && e.material.map.dispose(), e.material.dispose()));
});
}
}
export {
M as AssetManager,
y as MemoryManager,
A as TextureAtlasManager
};