375 lines
10 KiB
JavaScript
375 lines
10 KiB
JavaScript
import * as THREE from 'three';
|
|
import * as fflate from 'three/examples/jsm/libs/fflate.module.js';
|
|
|
|
const debugFileHandling = false;
|
|
let loadingCallback = null;
|
|
let sceneLoadingCallback = null;
|
|
|
|
export function setLoadingCallback(cb)
|
|
{
|
|
loadingCallback = cb;
|
|
}
|
|
|
|
export function setSceneLoadingCallback(cb)
|
|
{
|
|
sceneLoadingCallback = cb;
|
|
}
|
|
|
|
export function dropHandler(ev)
|
|
{
|
|
if (debugFileHandling) console.log('File(s) dropped', ev.dataTransfer.items, ev.dataTransfer.files);
|
|
|
|
// Prevent default behavior (Prevent file from being opened)
|
|
ev.preventDefault();
|
|
|
|
if (ev.dataTransfer.items)
|
|
{
|
|
const allEntries = [];
|
|
|
|
let haveGetAsEntry = false;
|
|
if (ev.dataTransfer.items.length > 0)
|
|
haveGetAsEntry =
|
|
("getAsEntry" in ev.dataTransfer.items[0]) ||
|
|
("webkitGetAsEntry" in ev.dataTransfer.items[0]);
|
|
|
|
// Useful for debugging file handling on platforms that don't support newer file system APIs
|
|
// haveGetAsEntry = false;
|
|
|
|
if (haveGetAsEntry)
|
|
{
|
|
for (var i = 0; i < ev.dataTransfer.items.length; i++)
|
|
{
|
|
let item = ev.dataTransfer.items[i];
|
|
let entry = ("getAsEntry" in item) ? item.getAsEntry() : item.webkitGetAsEntry();
|
|
allEntries.push(entry);
|
|
}
|
|
handleFilesystemEntries(allEntries);
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < ev.dataTransfer.items.length; i++)
|
|
{
|
|
let item = ev.dataTransfer.items[i];
|
|
|
|
// API when there's no "getAsEntry" support
|
|
console.log(item.kind, item);
|
|
if (item.kind === 'file')
|
|
{
|
|
var file = item.getAsFile();
|
|
testAndLoadFile(file);
|
|
}
|
|
// could also be a directory
|
|
else if (item.kind === 'directory')
|
|
{
|
|
var dirReader = item.createReader();
|
|
dirReader.readEntries(function (entries)
|
|
{
|
|
for (var i = 0; i < entries.length; i++)
|
|
{
|
|
console.log(entries[i].name);
|
|
var entry = entries[i];
|
|
if (entry.isFile)
|
|
{
|
|
entry.file(function (file)
|
|
{
|
|
testAndLoadFile(file);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
for (var i = 0; i < ev.dataTransfer.files.length; i++)
|
|
{
|
|
let file = ev.dataTransfer.files[i];
|
|
testAndLoadFile(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function dragOverHandler(ev)
|
|
{
|
|
ev.preventDefault();
|
|
}
|
|
|
|
async function getBufferFromFile(fileEntry)
|
|
{
|
|
|
|
if (fileEntry instanceof ArrayBuffer) return fileEntry;
|
|
if (fileEntry instanceof String) return fileEntry;
|
|
|
|
const name = fileEntry.fullPath || fileEntry.name;
|
|
const ext = name.split('.').pop();
|
|
const readAsText = ext === 'mtlx';
|
|
|
|
if (debugFileHandling) console.log("reading ", fileEntry, "as text?", readAsText);
|
|
|
|
if (debugFileHandling) console.log("getBufferFromFile", fileEntry);
|
|
const buffer = await new Promise((resolve, reject) =>
|
|
{
|
|
function readFile(file)
|
|
{
|
|
var reader = new FileReader();
|
|
reader.onloadend = function (e)
|
|
{
|
|
if (debugFileHandling) console.log("loaded", "should be text?", readAsText, this.result);
|
|
resolve(this.result);
|
|
};
|
|
|
|
if (readAsText)
|
|
reader.readAsText(file);
|
|
else
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
|
|
if ("file" in fileEntry)
|
|
{
|
|
fileEntry.file(function (file)
|
|
{
|
|
readFile(file);
|
|
}, (e) =>
|
|
{
|
|
console.error("Error reading file ", e);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
readFile(fileEntry);
|
|
}
|
|
});
|
|
return buffer;
|
|
}
|
|
|
|
async function handleFilesystemEntries(entries)
|
|
{
|
|
const allFiles = [];
|
|
const fileIgnoreList = [
|
|
'.gitignore',
|
|
'README.md',
|
|
'.DS_Store',
|
|
]
|
|
const dirIgnoreList = [
|
|
'.git',
|
|
'node_modules',
|
|
]
|
|
|
|
let isGLB = false;
|
|
let haveMtlx = false;
|
|
for (let entry of entries)
|
|
{
|
|
if (debugFileHandling) console.log("file entry", entry)
|
|
if (entry.isFile)
|
|
{
|
|
if (debugFileHandling)
|
|
console.log("single file", entry);
|
|
if (fileIgnoreList.includes(entry.name))
|
|
{
|
|
continue;
|
|
}
|
|
allFiles.push(entry);
|
|
|
|
if (entry.name.endsWith('glb'))
|
|
{
|
|
isGLB = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (entry.isDirectory)
|
|
{
|
|
if (dirIgnoreList.includes(entry.name))
|
|
{
|
|
continue;
|
|
}
|
|
const files = await readDirectory(entry);
|
|
if (debugFileHandling) console.log("all files", files);
|
|
for (const file of files)
|
|
{
|
|
if (fileIgnoreList.includes(file.name))
|
|
{
|
|
continue;
|
|
}
|
|
allFiles.push(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
const imageLoader = new THREE.ImageLoader();
|
|
|
|
// unpack zip files first
|
|
for (const fileEntry of allFiles)
|
|
{
|
|
// special case: zip archives
|
|
if (fileEntry.fullPath.toLowerCase().endsWith('.zip'))
|
|
{
|
|
await new Promise(async (resolve, reject) =>
|
|
{
|
|
const arrayBuffer = await getBufferFromFile(fileEntry);
|
|
|
|
// use fflate to unpack them and add the files to the cache
|
|
fflate.unzip(new Uint8Array(arrayBuffer), (error, unzipped) =>
|
|
{
|
|
// push these files into allFiles
|
|
for (const [filePath, buffer] of Object.entries(unzipped))
|
|
{
|
|
|
|
// mock FileEntry for easier usage downstream
|
|
const blob = new Blob([buffer]);
|
|
const newFileEntry = {
|
|
fullPath: "/" + filePath,
|
|
name: filePath.split('/').pop(),
|
|
file: (callback) =>
|
|
{
|
|
callback(blob);
|
|
},
|
|
isFile: true,
|
|
};
|
|
allFiles.push(newFileEntry);
|
|
}
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// sort so mtlx files come first
|
|
allFiles.sort((a, b) =>
|
|
{
|
|
if (a.name.endsWith('.mtlx') && !b.name.endsWith('.mtlx'))
|
|
{
|
|
return -1;
|
|
}
|
|
if (!a.name.endsWith('.mtlx') && b.name.endsWith('.mtlx'))
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
if (isGLB)
|
|
{
|
|
console.log("Load GLB file", allFiles[0]);
|
|
|
|
const rootFile = allFiles[0];
|
|
THREE.Cache.add(rootFile.fullPath, await getBufferFromFile(rootFile));
|
|
|
|
if (debugFileHandling) console.log("CACHE", THREE.Cache.files);
|
|
|
|
sceneLoadingCallback(rootFile);
|
|
return;
|
|
}
|
|
|
|
if (!allFiles[0].name.endsWith('mtlx'))
|
|
{
|
|
console.log("No MaterialX files dropped. Skipping content.");
|
|
return;
|
|
}
|
|
|
|
if (debugFileHandling)
|
|
{
|
|
console.log("- All files", allFiles);
|
|
}
|
|
|
|
// put all files in three' Cache
|
|
for (const fileEntry of allFiles)
|
|
{
|
|
|
|
const allowedFileTypes = [
|
|
'png', 'jpg', 'jpeg'
|
|
];
|
|
|
|
const ext = fileEntry.fullPath.split('.').pop();
|
|
if (!allowedFileTypes.includes(ext))
|
|
{
|
|
// console.log("skipping file", fileEntry.fullPath);
|
|
continue;
|
|
}
|
|
|
|
const buffer = await getBufferFromFile(fileEntry);
|
|
const img = await imageLoader.loadAsync(URL.createObjectURL(new Blob([buffer])));
|
|
if (debugFileHandling) console.log("caching file", fileEntry.fullPath, img);
|
|
THREE.Cache.add(fileEntry.fullPath, img);
|
|
}
|
|
|
|
// TODO we could also allow dropping of multiple MaterialX files (or folders with them inside)
|
|
// and seed the dropdown from that.
|
|
// At that point, actually reading files and textures into memory should be deferred until they are actually used.
|
|
if (allFiles.length > 0)
|
|
{
|
|
const rootFile = allFiles[0];
|
|
THREE.Cache.add(rootFile.fullPath, await getBufferFromFile(rootFile));
|
|
|
|
if (debugFileHandling) console.log("CACHE", THREE.Cache.files);
|
|
|
|
loadingCallback(rootFile);
|
|
}
|
|
else
|
|
{
|
|
console.log('No files to add cache.')
|
|
}
|
|
}
|
|
|
|
async function readDirectory(directory)
|
|
{
|
|
let entries = [];
|
|
let getEntries = async (directory) =>
|
|
{
|
|
let dirReader = directory.createReader();
|
|
await new Promise((resolve, reject) =>
|
|
{
|
|
dirReader.readEntries(
|
|
async (results) =>
|
|
{
|
|
if (results.length)
|
|
{
|
|
// entries = entries.concat(results);
|
|
for (let entry of results)
|
|
{
|
|
if (entry.isDirectory)
|
|
{
|
|
await getEntries(entry);
|
|
}
|
|
else
|
|
{
|
|
entries.push(entry);
|
|
}
|
|
}
|
|
}
|
|
resolve();
|
|
},
|
|
(error) =>
|
|
{
|
|
/* handle error — error is a FileError object */
|
|
},
|
|
)
|
|
}
|
|
)
|
|
};
|
|
|
|
await getEntries(directory);
|
|
return entries;
|
|
}
|
|
|
|
async function testAndLoadFile(file)
|
|
{
|
|
let ext = file.name.split('.').pop();
|
|
if (debugFileHandling) console.log(file.name + ", " + file.size + ", " + ext);
|
|
|
|
const arrayBuffer = await getBufferFromFile(file);
|
|
console.log(arrayBuffer)
|
|
|
|
// mock a fileEntry and pass through the same loading logic
|
|
const newFileEntry = {
|
|
fullPath: "/" + file.name,
|
|
name: file.name.split('/').pop(),
|
|
isFile: true,
|
|
file: (callback) =>
|
|
{
|
|
callback(file);
|
|
}
|
|
};
|
|
|
|
handleFilesystemEntries([newFileEntry]);
|
|
} |