diff --git a/config/parseSave2Obj.js b/config/parseSave2Obj.js new file mode 100644 index 0000000..af9df20 --- /dev/null +++ b/config/parseSave2Obj.js @@ -0,0 +1,300 @@ +const PATH = require("path"); +const FS = require("fs"); +const MINIMIST = require("minimist"); +const READLINE = require("readline-sync"); + +const ARGS = MINIMIST(process.argv.slice(2), { + alias: {i: ["input", "save"], o: ["output", "obj"]} +}); + +// 获取路径 +const inputFilePath = PATH.resolve(ARGS.i ?? "./save.ltss"); +const outputFilePath = PATH.resolve(ARGS.o ?? "./output.obj"); + +// 读取文件 +const fileString = FS.readFileSync(inputFilePath); +console.log("文件读取成功...\r\n"); + +/** + * 引入所需类型 + * @typedef {import("../source/Model/Archive").IArchiveObject} IArchiveObject + * @typedef {import("../source/Model/Clip").IArchiveClip} IArchiveClip + */ + +/** + * 解析文件 + * @type {IArchiveObject} + */ +const archive = JSON.parse(fileString); +console.log("文件解析成功...\r\n"); + +// 打印全部的剪辑列表 +if (archive.clipPool.length > 0) { + console.log("这个存档中存在以下剪辑:"); +} +archive.clipPool.map((item, index) => { + console.log(" \033[44;30m" + (index + 1) + "\033[40;32m " + item.name + " [" + item.id + "]\033[0m"); +}); + +/** + * 选择剪辑 + * @type {IArchiveClip} + */ +let clip; +if (archive.clipPool.length <= 0) { + console.log("存档中没有剪辑, 退出程序...\r\n"); + process.exit(); +} else if (archive.clipPool.length === 1) { + console.log("\r\n存档中只有一个剪辑, 自动选择...\r\n"); + clip = archive.clipPool[0]; +} else { + console.log("\r\n请选择一个剪辑: "); + let userInput = READLINE.question(); + for (let i = 0; i < archive.clipPool.length; i++) { + if ((i + 1) == userInput) { + clip = archive.clipPool[i]; + break; + } + } +} + +// 选择提示 +if (clip) { + console.log("已选择剪辑: " + clip.name + "\r\n"); +} else { + console.log("没有选择任何剪辑, 退出程序...\r\n"); + process.exit(); +} + +// 解压缩文件 +console.log("正在还原压缩剪辑记录...\r\n"); +const frames = clip.frames; + +/** + * @type {Map { + /** + * @type {IArchiveClip["frames"][number]["commands"]} + */ + const FCS = []; + frame.commands.forEach((command) => { + + // 压缩指令 + const FC = { + id: command.id, + type: command.type + }; + + /** + * 上一帧 + * @type {IArchiveClip["frames"][number]} + */ + const lastFrame = F[F.length - 1]; + + /** + * 上一帧指令 + * @type {IArchiveClip["frames"][number]["commands"][number]} + */ + const lastCommand = lastFrame?.commands.filter((c) => { + if (c.type === command.type && c.id === command.id) { + return true; + } else { + return false; + } + })[0]; + + // 记录 + FC.name = (LastFrameData === command.name) ? lastCommand?.name : command.name; + + FC.data = (LastFrameData === command.data) ? lastCommand?.data : command.data; + + FC.mapId = (LastFrameData === command.mapId) ? lastCommand?.mapId : command.mapId; + + FC.position = (LastFrameData === command.position) ? lastCommand?.position : command.position; + + FC.radius = (LastFrameData === command.radius) ? lastCommand?.radius : command.radius; + + // 获取 Mapper + const mapper = objectMapper.get(FC.id); + if (mapper) { + mapper.type = FC.type ?? mapper.type; + mapper.name = FC.name ?? mapper.name; + } else { + objectMapper.set(FC.id, { + type: FC.type, + name: FC.name + }); + } + + FCS.push(FC); + }); + + F.push({ + duration: frame.duration, + process: frame.process, + commands: FCS + }); +}); + +console.log("剪辑记录还原成功...\r\n"); +console.log("剪辑共 " + F.length + " 帧, 对象 " + objectMapper.size + " 个\r\n"); +if (objectMapper.size) { + console.log("剪辑记录中存在以下对象:"); +} else { + console.log("剪辑记录中没有任何对象,退出程序..."); + process.exit(); +} +let objectMapperForEachIndex = 1; +objectMapper.forEach((item, key) => { + console.log(" \033[44;30m" + (objectMapperForEachIndex ++) + "\033[40;32m " + item.type + " " + item.name + " [" + key + "]\033[0m"); +}); + +/** + * @type {number[]} + */ +const pointMapper = []; + +/** + * @param {number} x + * @param {number} y + * @param {number} z + * @returns {number} + */ +function getPointID(x, y, z) { + let search = -1; + let pointMapperLength = (pointMapper.length / 3); + // for (let i = 0; i < pointMapperLength; i++) { + // if ( + // pointMapper[i * 3 + 0] === x && + // pointMapper[i * 3 + 1] === y && + // pointMapper[i * 3 + 2] === z + // ) { + // search = i; + // } + // } + if (search >= 0) { + return search; + } else { + pointMapper.push(x); + pointMapper.push(y); + pointMapper.push(z); + return pointMapperLength + 1; + } +} + +let frameId = 0; +/** + * @type {Map} + */ +const objectLineMapper = new Map(); +/** + * @param {string} obj + * @param {number} id + * @param {number} point + */ +function recordPoint(obj, id, point) { + let searchObj = objectLineMapper.get(obj); + if (searchObj) { + + /** + * @type {{id: number, start: number, last: number, point: number[]}} + */ + let search; + for (let i = 0; i < searchObj.length; i++) { + if (searchObj[i].id === id && searchObj[i].last === (frameId - 1)) { + search = searchObj[i]; + } + } + + if (search) { + search.point.push(point); + search.last = frameId; + } else { + searchObj.push({ + id: id, + start: frameId, + last: frameId, + point: [point] + }); + } + } else { + objectLineMapper.set(obj, [{ + id: id, + start: frameId, + last: frameId, + point: [point] + }]); + } +} + +console.log("\r\n正在收集多边形数据...\r\n"); +for (frameId = 0; frameId < F.length; frameId ++) { + F[frameId].commands.forEach((command) => { + + if (command.type === "points" && command.mapId && command.data) { + command.mapId.forEach((pid, index) => { + + const x = command.data[index * 3 + 0]; + const y = command.data[index * 3 + 1] + const z = command.data[index * 3 + 2] + + if ( + x !== undefined && + y !== undefined && + z !== undefined + ) { + recordPoint(command.id, pid, getPointID(x, y, z)); + } + }) + } + }); +} + +let pointCount = (pointMapper.length / 3); + +console.log("收集点数据 " + pointCount + "个\r\n"); +console.log("收集样条:"); +let objectLineMapperIndexPrint = 1; +objectLineMapper.forEach((item, key) => { + let iName = objectMapper.get(key).name; + console.log(" \033[44;30m" + (objectLineMapperIndexPrint ++) + "\033[40;32m " + item.length + " " + iName + " [" + key + "]\033[0m"); +}); + +console.log("\r\n正在生成 .obj 文件...\r\n"); + +let fileStr = ""; let fileStrVec = ""; + +objectLineMapper.forEach((item, key) => { + + for (let i = 0; i < item.length; i++) { + fileStr += "\r\n"; + fileStr += ("o " + objectMapper.get(key).name + " " + item[i].id + "\r\n"); + fileStr += "usemtl default\r\n"; + fileStr += "l "; + // fileStr += (getPointID(item[i].id, item[i].start, item[i].last) + " "); + fileStr += item[i].point.join(" "); + fileStr += "\r\n"; + } + + fileStr += "\r\n"; +}); + +pointCount = (pointMapper.length / 3); +for (let i = 0; i < pointCount; i++) { + fileStrVec += ("v " + pointMapper[i * 3 + 0] + " " + pointMapper[i * 3 + 1] + " " + pointMapper[i * 3 + 2] + "\r\n"); +} + +const file = "# Create with Living Together (Parse from .ltss file)\r\n\r\n" + fileStrVec + fileStr; + +console.log("正在生成保存文件...\r\n"); +FS.writeFileSync(outputFilePath, file); +console.log("成功\r\n"); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b8eb1b3..7b31c0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^17.0.2", + "readline-sync": "^1.4.10", "uuid": "^8.3.2" }, "devDependencies": { @@ -6367,6 +6368,14 @@ "node": ">=8.10.0" } }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmmirror.com/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -13309,6 +13318,11 @@ "picomatch": "^2.2.1" } }, + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmmirror.com/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==" + }, "rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", diff --git a/package.json b/package.json index 8a4ba53..afef7db 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^17.0.2", + "readline-sync": "^1.4.10", "uuid": "^8.3.2" } } diff --git a/source/Model/Archive.ts b/source/Model/Archive.ts index e9e102e..50125ef 100644 --- a/source/Model/Archive.ts +++ b/source/Model/Archive.ts @@ -301,4 +301,4 @@ class Archive extends Emitter { } } -export { Archive }; \ No newline at end of file +export { Archive, IArchiveObject }; \ No newline at end of file diff --git a/source/Model/Clip.ts b/source/Model/Clip.ts index f73a3c2..b792d43 100644 --- a/source/Model/Clip.ts +++ b/source/Model/Clip.ts @@ -7,6 +7,7 @@ import { archiveObject2Parameter, IArchiveParseFn, parameter2ArchiveObject } fro interface IDrawCommand { type: "points" | "cube"; id: string; + name?: string; data?: Float32Array; mapId?: number[]; position?: number[]; @@ -221,6 +222,7 @@ class Clip { const recodeData: IDrawCommand = { type: "points", id: object.id, + name: object.displayName, data: dataBuffer[0] } @@ -249,7 +251,8 @@ class Clip { // 记录 const recodeData: IDrawCommand = { type: "cube", - id: object.id + id: object.id, + name: object.displayName } // 释放上一帧的内存 @@ -288,7 +291,7 @@ class Clip { return frame; } - public readonly LastFrameData: "@L" = "@L"; + public readonly LastFrameData: "@" = "@"; /** * 压缩帧数据 @@ -315,6 +318,9 @@ class Clip { undefined; // 记录 + command.name = (lastCommand?.name === commands[j].name) ? + this.LastFrameData as any : commands[j].name; + command.data = (lastCommand?.data === commands[j].data) ? this.LastFrameData as any : Array.from(commands[j].data ?? []); @@ -368,6 +374,9 @@ class Clip { undefined; // 记录 + command.name = (this.LastFrameData as any === commands[j].name) ? + lastCommand?.name : commands[j].name; + command.data = (this.LastFrameData as any === commands[j].data) ? lastCommand?.data : new Float32Array(commands[j].data ?? []);