AI 應用中,3D 網格模型的語意標註越來越重要—— 牙齒分割、器官辨識、場景理解,都需要在網格的每個頂點上記錄分類標籤(label)。 然而現有主流格式都沒有為此設計,於是有了 .lobj。
結構最簡單,但頂點大量重複,無法與標籤一一對應。
有獨立頂點清單,結構良好,但無標籤欄位。
可自訂屬性,但定義繁瑣,互通性差。
.obj 天生將頂點與面分開儲存,只需附加一個等長的標籤陣列, 即可對應每個頂點的語意,無需改動面的結構。
# 原始 .obj 結構
v x y z ← 頂點清單
f i j k ← 面清單(引用頂點索引)
# .lobj 只需多一份
label n ← 標籤清單(與頂點一一對應)
int8-1 作為「未標記(unlabeled)」的慣例值counts[0] int32 頂點數 cntv 必要
counts[1] int32 法向量數 0 或 cntv
counts[2] int32 顏色數 0 或 cntv
counts[3] int32 標籤數 0 或 cntv
counts[4] int32 紋理座標數 0 或 M
counts[5] int32 三角形數 cntt 必要
counts[6] int32 紋理ID數 0 或 cntt
vertices float32 cntv × 3 必要
normals float32 若 > 0
colors float32 若 > 0
labels int8 若 > 0
tex2d float32 若 > 0
triangles uint32 cntt × 3 必要
texId uint32 若 > 0
.lobj 最初設計時,以盡量相容原始 .obj 為目標, 因此保留了法向量、顏色、紋理座標等多個選填欄位。
實際使用後發現:絕大多數場景只需要 vertices、triangles、labels, 其他欄位的使用率極低。
這反映了「通用性 vs. 精簡性」的設計取捨。 對於只需核心三欄位的使用者,忽略其他欄位即可,格式本身不強制填寫。
import { isValidLobj, parseLobjBinary } from '@cadcam/lobj';
// 從 File 物件載入(例如 input[type=file] 或 drag-drop)
async function loadFile(file) {
try {
const buf = await file.arrayBuffer();
if (isValidLobj(buf)) {
const lobjdata = parseLobjBinary(buf);
}
} catch (e) {
alert('解析失敗: ' + e.message);
}
}
// 拖曳 drag-drop 方式
function add_event_drop_lobj(container) {
container.addEventListener('dragover', e => e.preventDefault());
container.addEventListener('drop', e => {
e.preventDefault();
const file = e.dataTransfer.files[0];
if (file) loadFile(file);
});
}
// input[type=file] 方式
document.getElementById('fileInput').addEventListener('change', e => {
const file = e.target.files[0];
if (file) loadFile(file);
});
// 從伺服器 fetch 載入
async function fetch_from_url(url) {
const resp = await fetch(url);
if (!resp.ok) throw new Error(`Failed to load ${url}: ${resp.status}`);
const buf = await resp.arrayBuffer();
if (isValidLobj(buf)) return parseLobjBinary(buf);
throw new Error(`Invalid .lobj file: ${url}`);
}