Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "babylonjs-editor-cli",
"version": "5.4.1-rc.7",
"version": "5.4.1-rc.8",
"description": "Babylon.js Editor CLI is a command line interface to help you package your scenes made using the Babylon.js Editor",
"productName": "Babylon.js Editor CLI",
"scripts": {
Expand Down
129 changes: 118 additions & 11 deletions cli/src/pack/assets/collect.mts
Original file line number Diff line number Diff line change
@@ -1,15 +1,62 @@
import { extname, join } from "node:path/posix";

import sharp from "sharp";
import { pathExists } from "fs-extra";

import { supportedExtensions } from "./process.mjs";
import { unique } from "../../tools/array.mjs";
import { getPowerOfTwoUntil } from "../../tools/scalar.mjs";

import { DownscaledTextureSize } from "./texture.mjs";
import { supportedExtensions, supportedImagesExtensions } from "./process.mjs";
import { allKtxFormats, getCompressedTextureFilename, ktxSupportedextensions } from "./ktx.mjs";

const collectedSupportedExtensions: string[] = [...supportedExtensions, ".babylonbinarymeshdata"];
const binaryGeometryExtension = ".babylonbinarymeshdata";
const collectedSupportedExtensions: string[] = [...supportedExtensions, binaryGeometryExtension];

export function traverseAndReplaceInSceneObject(scene: any, callback: (key: string, value: string) => string | undefined) {
function recursivelyCollect(root: any) {
for (const thing in root) {
if (!root.hasOwnProperty(thing)) {
continue;
}

const value = root[thing];

if (typeof value === "string") {
const extension = extname(value).toLowerCase();
if (extension && collectedSupportedExtensions.includes(extension)) {
const newValue = callback(thing, value);
if (newValue !== undefined) {
root[thing] = newValue;
}
}
} else if (typeof value === "object") {
if (Array.isArray(value)) {
value.forEach((v) => recursivelyCollect(v));
} else {
recursivelyCollect(value);
}
}
}
}

recursivelyCollect(scene);
}

async function _checkKtxSupportForTexture(value: string, publicDir: string, result: string[]) {
for (const format of allKtxFormats) {
const compressedTexturePath = join(publicDir, getCompressedTextureFilename(value, format));
const finalName = getCompressedTextureFilename(value, format);

if (!result.includes(finalName) && (await pathExists(compressedTexturePath))) {
result.push(finalName);
}
}
}

export async function collectUsedAssetsForScene(scene: any, publicDir: string) {
const result: string[] = [];
const stringValues: string[] = [];
let stringValues: string[] = [];

function recursivelyCollect(root: any) {
for (const thing in root) {
Expand All @@ -25,27 +72,87 @@ export async function collectUsedAssetsForScene(scene: any, publicDir: string) {
stringValues.push(value);
}
} else if (typeof value === "object") {
recursivelyCollect(value);
if (Array.isArray(value)) {
value.forEach((v) => recursivelyCollect(v));
} else {
recursivelyCollect(value);
}
}
}
}

recursivelyCollect(scene);

stringValues = unique(stringValues);

await Promise.all(
stringValues.map(async (value) => {
const extension = extname(value).toLowerCase();
if (extension === binaryGeometryExtension) {
return result.push(value);
}

const absolutePath = join(publicDir, value);
if (await pathExists(absolutePath)) {
result.push(value);
if (!result.includes(value)) {
result.push(value);
}

const extension = extname(value).toLowerCase();
if (ktxSupportedextensions.includes(extension)) {
for (const format of allKtxFormats) {
const compressedTexturePath = join(publicDir, getCompressedTextureFilename(value, format));
if (await pathExists(compressedTexturePath)) {
result.push(getCompressedTextureFilename(value, format));
}
await _checkKtxSupportForTexture(value, publicDir, result);
}

if (supportedImagesExtensions.includes(extension)) {
const sharpImage = sharp(absolutePath);
const { width, height } = await sharpImage.metadata();
if (!width || !height) {
return;
}

const availableSizes: DownscaledTextureSize[] = [];
const isPowerOfTwo = width === getPowerOfTwoUntil(width) || height === getPowerOfTwoUntil(height);

let midWidth = (width * 0.66) >> 0;
let midHeight = (height * 0.66) >> 0;

if (isPowerOfTwo) {
midWidth = getPowerOfTwoUntil(midWidth);
midHeight = getPowerOfTwoUntil(midHeight);
}

availableSizes.push({
width: midWidth,
height: midHeight,
});

let lowWidth = (width * 0.33) >> 0;
let lowHeight = (height * 0.33) >> 0;

if (isPowerOfTwo) {
lowWidth = getPowerOfTwoUntil(lowWidth);
lowHeight = getPowerOfTwoUntil(lowHeight);
}

availableSizes.push({
width: lowWidth,
height: lowHeight,
});

await Promise.all(
availableSizes.map(async (size) => {
const nameWithoutExtension = value.substring(0, value.lastIndexOf("."));
const finalName = `${nameWithoutExtension}_${size.width}_${size.height}${extension}`;
const finalPath = join(publicDir, finalName);

if (!result.includes(finalName) && (await pathExists(finalPath))) {
result.push(finalName);
}

if (ktxSupportedextensions.includes(extension)) {
await _checkKtxSupportForTexture(finalName, publicDir, result);
}
})
);
}
}
})
Expand Down
10 changes: 5 additions & 5 deletions cli/src/pack/assets/process.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { processExportedTexture } from "./texture.mjs";
import { processExportedMaterial } from "./material.mjs";
import { processExportedNodeParticleSystemSet } from "./particle-system.mjs";

const supportedImagesExtensions: string[] = [".jpg", ".jpeg", ".webp", ".png", ".bmp"];
const supportedCubeTexturesExtensions: string[] = [".env", ".dds", ".hdr"];
const supportedAudioExtensions: string[] = [".mp3", ".wav", ".wave", ".ogg"];
const supportedJsonExtensions: string[] = [".material", ".gui", ".cinematic", ".npss", ".ragdoll", ".json"];
const supportedMiscExtensions: string[] = [".3dl", ".exr", ".hdr"];
export const supportedImagesExtensions: string[] = [".jpg", ".jpeg", ".webp", ".png", ".bmp"];
export const supportedCubeTexturesExtensions: string[] = [".env", ".dds", ".hdr"];
export const supportedAudioExtensions: string[] = [".mp3", ".wav", ".wave", ".ogg"];
export const supportedJsonExtensions: string[] = [".material", ".gui", ".cinematic", ".npss", ".ragdoll", ".json"];
export const supportedMiscExtensions: string[] = [".3dl", ".exr", ".hdr"];

export const supportedExtensions: string[] = [
...supportedImagesExtensions,
Expand Down
12 changes: 6 additions & 6 deletions cli/src/pack/assets/texture.mts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export function getExtractedTextureOutputPath(publicDir: string) {
return join(publicDir, "assets", "editor-generated_extracted-textures");
}

export type DownscaledTextureSize = {
width: number;
height: number;
};

export interface IComputeExportedTextureOptions extends IProcessAssetFileOptions {
force: boolean;
exportedAssets: string[];
Expand All @@ -31,12 +36,7 @@ export async function processExportedTexture(absolutePath: string, options: ICom

const isPowerOfTwo = width === getPowerOfTwoUntil(width) || height === getPowerOfTwoUntil(height);

type _DownscaledTextureSize = {
width: number;
height: number;
};

const availableSizes: _DownscaledTextureSize[] = [];
const availableSizes: DownscaledTextureSize[] = [];

let midWidth = (width * 0.66) >> 0;
let midHeight = (height * 0.66) >> 0;
Expand Down
3 changes: 2 additions & 1 deletion cli/src/pack/geometry.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface ICreateGeometryFilesOptions {
sceneFile: string;
sceneName: string;
publicDir: string;
geometryFiles: string[];
exportedAssets: string[];
babylonjsEditorToolsVersion: string;

Expand All @@ -18,7 +19,7 @@ export async function createGeometryFiles(options: ICreateGeometryFilesOptions)
await fs.ensureDir(join(options.publicDir, options.sceneName));

await Promise.all(
options.directories.geometryFiles.map(async (file) => {
options.geometryFiles.map(async (file) => {
const destination = join(options.publicDir, options.sceneName, file);
await fs.copyFile(join(options.sceneFile, "geometries", file), destination);
options.exportedAssets.push(destination);
Expand Down
22 changes: 18 additions & 4 deletions cli/src/pack/pack.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs, { pathExists } from "fs-extra";
import { basename, extname, join } from "node:path/posix";

import ora from "ora";
import chalk from "chalk";
import cliSpinners from "cli-spinners";

import { CancellationToken } from "../tools/cancel.mjs";
Expand Down Expand Up @@ -33,6 +34,8 @@ export interface IPackOptions {
export async function pack(projectDir: string, options: IPackOptions) {
projectDir = getProjectDir(projectDir);

const buildTime = Date.now();

// Load project configuration
const projectFiles = await fs.readdir(projectDir);
const projectConfigurationFile = projectFiles.find((file) => extname(file).toLowerCase() === ".bjseditor");
Expand Down Expand Up @@ -113,6 +116,8 @@ export async function pack(projectDir: string, options: IPackOptions) {
}

// Pack scenes
let totalReusedGeometriesCount = 0;

const scenesUsedFiles: Record<string, string[]> = {};

const sceneFiles = await normalizedGlob(`${assetsDirectory}/**/*`, {
Expand Down Expand Up @@ -161,26 +166,30 @@ export async function pack(projectDir: string, options: IPackOptions) {
message: `Packing scene ${sceneName}...`,
});

const sceneUsedFiles = await createBabylonScene({
const sceneFiles = await createBabylonScene({
...options,
...projectConfiguration,
config,
directories,
publicDir,
sceneFile,
sceneName,
buildTime,
exportedAssets,
babylonjsEditorToolsVersion,
});

scenesUsedFiles[`${sceneName}.scene`] = sceneUsedFiles.map((asset) => asset.replace(publicDir + "/", ""));
totalReusedGeometriesCount += sceneFiles.reusedGeometriesCount;

scenesUsedFiles[`${sceneName}.scene`] = sceneFiles.usedFiles.map((asset) => asset.replace(publicDir + "/", ""));

options.onStepChanged?.("scenes", {
message: `Packing scene ${sceneName} geometries...`,
});

// Copy geometry files
await createGeometryFiles({
...sceneFiles,
directories,
publicDir,
sceneFile,
Expand Down Expand Up @@ -229,7 +238,7 @@ export async function pack(projectDir: string, options: IPackOptions) {
exportedAssets.map((asset) => asset.replace(publicDir + "/", "")),
{
spaces: "\t",
encoding: "utf-8",
// encoding: "utf-8",
}
);

Expand All @@ -238,7 +247,7 @@ export async function pack(projectDir: string, options: IPackOptions) {
const scenesUsedFilesPath = join(publicDir, "scenes-used-files.json");
await fs.writeJSON(scenesUsedFilesPath, scenesUsedFiles, {
spaces: "\t",
encoding: "utf-8",
// encoding: "utf-8",
});

exportedAssets.push(scenesUsedFilesPath);
Expand All @@ -255,5 +264,10 @@ export async function pack(projectDir: string, options: IPackOptions) {
}
});
}

// Performance
if (totalReusedGeometriesCount > 0) {
console.log(chalk.green(`Total reused geometries: ${totalReusedGeometriesCount}`));
}
}
}
Loading