mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-28 15:45:02 +00:00
stuff
This commit is contained in:
375
MaterialX/javascript/MaterialXView/source/dropHandling.js
Normal file
375
MaterialX/javascript/MaterialXView/source/dropHandling.js
Normal file
@@ -0,0 +1,375 @@
|
||||
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]);
|
||||
}
|
||||
352
MaterialX/javascript/MaterialXView/source/helper.js
Normal file
352
MaterialX/javascript/MaterialXView/source/helper.js
Normal file
@@ -0,0 +1,352 @@
|
||||
//
|
||||
// Copyright Contributors to the MaterialX Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
import * as THREE from 'three';
|
||||
|
||||
const IMAGE_PROPERTY_SEPARATOR = "_";
|
||||
const UADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "uaddressmode";
|
||||
const VADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "vaddressmode";
|
||||
const FILTER_TYPE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "filtertype";
|
||||
const IMAGE_PATH_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* Initialized the environment texture as MaterialX expects it
|
||||
* @param {THREE.Texture} texture
|
||||
* @param {Object} capabilities
|
||||
* @returns {THREE.Texture}
|
||||
*/
|
||||
export function prepareEnvTexture(texture, capabilities)
|
||||
{
|
||||
let newTexture = new THREE.DataTexture(texture.image.data, texture.image.width, texture.image.height, texture.format, texture.type);
|
||||
newTexture.wrapS = THREE.RepeatWrapping;
|
||||
newTexture.anisotropy = capabilities.getMaxAnisotropy();
|
||||
newTexture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||
newTexture.magFilter = THREE.LinearFilter;
|
||||
newTexture.generateMipmaps = true;
|
||||
newTexture.needsUpdate = true;
|
||||
|
||||
return newTexture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Three uniform from MaterialX vector
|
||||
* @param {any} value
|
||||
* @param {any} dimension
|
||||
* @returns {THREE.Uniform}
|
||||
*/
|
||||
function fromVector(value, dimension)
|
||||
{
|
||||
let outValue;
|
||||
if (value)
|
||||
{
|
||||
outValue = value.data();
|
||||
}
|
||||
else
|
||||
{
|
||||
outValue = [];
|
||||
for (let i = 0; i < dimension; ++i)
|
||||
outValue.push(0.0);
|
||||
}
|
||||
|
||||
return outValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Three uniform from MaterialX matrix
|
||||
* @param {mx.matrix} matrix
|
||||
* @param {mx.matrix.size} dimension
|
||||
*/
|
||||
function fromMatrix(matrix, dimension)
|
||||
{
|
||||
let vec = [];
|
||||
if (matrix)
|
||||
{
|
||||
for (let i = 0; i < matrix.numRows(); ++i)
|
||||
{
|
||||
for (let k = 0; k < matrix.numColumns(); ++k)
|
||||
{
|
||||
vec.push(matrix.getItem(i, k));
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
for (let i = 0; i < dimension; ++i)
|
||||
vec.push(0.0);
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Three uniform from MaterialX value
|
||||
* @param {mx.Uniform.type} type
|
||||
* @param {mx.Uniform.value} value
|
||||
* @param {mx.Uniform.name} name
|
||||
* @param {mx.Uniforms} uniforms
|
||||
* @param {THREE.textureLoader} textureLoader
|
||||
*/
|
||||
function toThreeUniform(type, value, name, uniforms, textureLoader, searchPath, flipY)
|
||||
{
|
||||
let outValue = null;
|
||||
switch (type)
|
||||
{
|
||||
case 'float':
|
||||
case 'integer':
|
||||
case 'boolean':
|
||||
outValue = value;
|
||||
break;
|
||||
case 'vector2':
|
||||
outValue = fromVector(value, 2);
|
||||
break;
|
||||
case 'vector3':
|
||||
case 'color3':
|
||||
outValue = fromVector(value, 3);
|
||||
break;
|
||||
case 'vector4':
|
||||
case 'color4':
|
||||
outValue = fromVector(value, 4);
|
||||
break;
|
||||
case 'matrix33':
|
||||
outValue = fromMatrix(value, 9);
|
||||
break;
|
||||
case 'matrix44':
|
||||
outValue = fromMatrix(value, 16);
|
||||
break;
|
||||
case 'filename':
|
||||
if (value)
|
||||
{
|
||||
// Cache / reuse texture to avoid reload overhead.
|
||||
// Note: that data blobs and embedded data textures are not cached as they are transient data.
|
||||
let checkCache = true;
|
||||
let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value;
|
||||
if (value.startsWith('blob:'))
|
||||
{
|
||||
texturePath = value;
|
||||
console.log('Load blob URL:', texturePath);
|
||||
checkCache = false;
|
||||
}
|
||||
else if (value.startsWith('http'))
|
||||
{
|
||||
texturePath = value;
|
||||
console.log('Load HTTP URL:', texturePath);
|
||||
}
|
||||
else if (value.startsWith('data:'))
|
||||
{
|
||||
texturePath = value;
|
||||
checkCache = false;
|
||||
console.log('Load data URL:', texturePath);
|
||||
}
|
||||
const cachedTexture = checkCache && THREE.Cache.get(texturePath);
|
||||
if (cachedTexture)
|
||||
{
|
||||
// Get texture from cache
|
||||
outValue = cachedTexture;
|
||||
console.log('Use cached texture: ', texturePath, outValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
outValue = textureLoader.load(
|
||||
texturePath,
|
||||
function (texture) {
|
||||
console.log('Load new texture: ' + texturePath, texture);
|
||||
outValue = texture;
|
||||
|
||||
// Add texture to ThreeJS cache
|
||||
if (checkCache)
|
||||
THREE.Cache.add(texturePath, texture);
|
||||
},
|
||||
undefined,
|
||||
function (error) {
|
||||
console.error('Error loading texture: ', error);
|
||||
});
|
||||
|
||||
// Set address & filtering mode
|
||||
if (outValue)
|
||||
setTextureParameters(outValue, name, uniforms, flipY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'samplerCube':
|
||||
case 'string':
|
||||
break;
|
||||
default:
|
||||
console.log('Value type not supported: ' + type);
|
||||
outValue = null;
|
||||
}
|
||||
|
||||
return outValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Three wrapping mode
|
||||
* @param {mx.TextureFilter.wrap} mode
|
||||
* @returns {THREE.Wrapping}
|
||||
*/
|
||||
function getWrapping(mode)
|
||||
{
|
||||
let wrap;
|
||||
switch (mode)
|
||||
{
|
||||
case 1:
|
||||
wrap = THREE.ClampToEdgeWrapping;
|
||||
break;
|
||||
case 2:
|
||||
wrap = THREE.RepeatWrapping;
|
||||
break;
|
||||
case 3:
|
||||
wrap = THREE.MirroredRepeatWrapping;
|
||||
break;
|
||||
default:
|
||||
wrap = THREE.RepeatWrapping;
|
||||
break;
|
||||
}
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Three minification filter
|
||||
* @param {mx.TextureFilter.minFilter} type
|
||||
* @param {mx.TextureFilter.generateMipmaps} generateMipmaps
|
||||
*/
|
||||
function getMinFilter(type, generateMipmaps)
|
||||
{
|
||||
const filterType = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
|
||||
if (type === 0)
|
||||
{
|
||||
filterType = generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter;
|
||||
}
|
||||
return filterType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Three texture parameters
|
||||
* @param {THREE.Texture} texture
|
||||
* @param {mx.Uniform.name} name
|
||||
* @param {mx.Uniforms} uniforms
|
||||
* @param {mx.TextureFilter.generateMipmaps} generateMipmaps
|
||||
*/
|
||||
function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true)
|
||||
{
|
||||
const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
|
||||
const base = name.substring(0, idx) || name;
|
||||
|
||||
texture.generateMipmaps = generateMipmaps;
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
texture.wrapT = THREE.RepeatWrapping;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
texture.flipY = flipY;
|
||||
|
||||
if (uniforms.find(base + UADDRESS_MODE_SUFFIX))
|
||||
{
|
||||
const uaddressmode = uniforms.find(base + UADDRESS_MODE_SUFFIX).getValue().getData();
|
||||
texture.wrapS = getWrapping(uaddressmode);
|
||||
}
|
||||
|
||||
if (uniforms.find(base + VADDRESS_MODE_SUFFIX))
|
||||
{
|
||||
const vaddressmode = uniforms.find(base + VADDRESS_MODE_SUFFIX).getValue().getData();
|
||||
texture.wrapT = getWrapping(vaddressmode);
|
||||
}
|
||||
|
||||
const filterType = uniforms.find(base + FILTER_TYPE_SUFFIX) ? uniforms.get(base + FILTER_TYPE_SUFFIX).value : -1;
|
||||
texture.minFilter = getMinFilter(filterType, generateMipmaps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the global light rotation matrix
|
||||
*/
|
||||
export function getLightRotation()
|
||||
{
|
||||
return new THREE.Matrix4().makeRotationY(Math.PI / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all lights nodes in a MaterialX document
|
||||
* @param {mx.Document} doc
|
||||
* @returns {Array.<mx.Node>}
|
||||
*/
|
||||
export function findLights(doc)
|
||||
{
|
||||
let lights = [];
|
||||
for (let node of doc.getNodes())
|
||||
{
|
||||
if (node.getType() === "lightshader")
|
||||
lights.push(node);
|
||||
}
|
||||
return lights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register lights in shader generation context
|
||||
* @param {Object} mx MaterialX Module
|
||||
* @param {Array.<mx.Node>} lights Light nodes
|
||||
* @param {mx.GenContext} genContext Shader generation context
|
||||
* @returns {Array.<mx.Node>}
|
||||
*/
|
||||
export function registerLights(mx, lights, genContext)
|
||||
{
|
||||
mx.HwShaderGenerator.unbindLightShaders(genContext);
|
||||
|
||||
const lightTypesBound = {};
|
||||
const lightData = [];
|
||||
let lightId = 1;
|
||||
for (let light of lights)
|
||||
{
|
||||
let nodeDef = light.getNodeDef();
|
||||
let nodeName = nodeDef.getName();
|
||||
if (!lightTypesBound[nodeName])
|
||||
{
|
||||
lightTypesBound[nodeName] = lightId;
|
||||
mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
|
||||
}
|
||||
|
||||
const lightDirection = light.getValueElement("direction").getValue().getData().data();
|
||||
const lightColor = light.getValueElement("color").getValue().getData().data();
|
||||
const lightIntensity = light.getValueElement("intensity").getValue().getData();
|
||||
|
||||
let rotatedLightDirection = new THREE.Vector3(...lightDirection)
|
||||
rotatedLightDirection.transformDirection(getLightRotation())
|
||||
|
||||
lightData.push({
|
||||
type: lightTypesBound[nodeName],
|
||||
direction: rotatedLightDirection,
|
||||
color: new THREE.Vector3(...lightColor),
|
||||
intensity: lightIntensity
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure max light count is large enough
|
||||
genContext.getOptions().hwMaxActiveLightSources = Math.max(genContext.getOptions().hwMaxActiveLightSources, lights.length);
|
||||
|
||||
return lightData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uniform values for a shader
|
||||
* @param {mx.shaderStage} shaderStage
|
||||
* @param {THREE.TextureLoader} textureLoader
|
||||
*/
|
||||
export function getUniformValues(shaderStage, textureLoader, searchPath, flipY)
|
||||
{
|
||||
let threeUniforms = {};
|
||||
|
||||
const uniformBlocks = Object.values(shaderStage.getUniformBlocks());
|
||||
uniformBlocks.forEach(uniforms =>
|
||||
{
|
||||
if (!uniforms.empty())
|
||||
{
|
||||
for (let i = 0; i < uniforms.size(); ++i)
|
||||
{
|
||||
const variable = uniforms.get(i);
|
||||
const value = variable.getValue()?.getData();
|
||||
const name = variable.getVariable();
|
||||
threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
|
||||
textureLoader, searchPath, flipY));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return threeUniforms;
|
||||
}
|
||||
198
MaterialX/javascript/MaterialXView/source/index.js
Normal file
198
MaterialX/javascript/MaterialXView/source/index.js
Normal file
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// Copyright Contributors to the MaterialX Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { Viewer } from './viewer.js'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
import { dropHandler, dragOverHandler, setLoadingCallback, setSceneLoadingCallback } from './dropHandling.js';
|
||||
|
||||
let renderer, orbitControls;
|
||||
|
||||
// Turntable option. For now the step size is fixed.
|
||||
let turntableEnabled = false;
|
||||
let turntableSteps = 360;
|
||||
let turntableStep = 0;
|
||||
|
||||
let captureRequested = false;
|
||||
|
||||
// Get URL options. Fallback to defaults if not specified.
|
||||
let materialFilename = new URLSearchParams(document.location.search).get("file");
|
||||
if (!materialFilename)
|
||||
{
|
||||
materialFilename = 'Materials/Examples/StandardSurface/standard_surface_default.mtlx';
|
||||
}
|
||||
|
||||
let viewer = Viewer.create();
|
||||
init();
|
||||
viewer.getEditor().updateProperties(0.9);
|
||||
|
||||
// Capture the current frame and save an image file.
|
||||
function captureFrame()
|
||||
{
|
||||
let canvas = document.getElementById('webglcanvas');
|
||||
var url = canvas.toDataURL();
|
||||
var link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('download', 'screenshot.png');
|
||||
link.click();
|
||||
}
|
||||
|
||||
function init()
|
||||
{
|
||||
let canvas = document.getElementById('webglcanvas');
|
||||
|
||||
// Handle material selection changes
|
||||
let materialsSelect = document.getElementById('materials');
|
||||
materialsSelect.value = materialFilename;
|
||||
materialsSelect.addEventListener('change', (e) =>
|
||||
{
|
||||
materialFilename = e.target.value;
|
||||
viewer.getEditor().initialize();
|
||||
viewer.getMaterial().loadMaterials(viewer, materialFilename);
|
||||
viewer.getEditor().updateProperties(0.9);
|
||||
});
|
||||
|
||||
// Handle geometry selection changes
|
||||
const scene = viewer.getScene();
|
||||
let geometrySelect = document.getElementById('geometry');
|
||||
geometrySelect.value = scene.getGeometryURL();
|
||||
geometrySelect.addEventListener('change', (e) =>
|
||||
{
|
||||
console.log('Change geometry to:', e.target.value);
|
||||
scene.setGeometryURL(e.target.value);
|
||||
scene.loadGeometry(viewer, orbitControls);
|
||||
});
|
||||
|
||||
// Set up scene
|
||||
scene.initialize();
|
||||
|
||||
// Set up renderer
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true, canvas });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.debug.checkShaderErrors = false;
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
// Set up controls
|
||||
orbitControls = new OrbitControls(scene.getCamera(), renderer.domElement);
|
||||
|
||||
// Add hotkey 'f' to capture the current frame and save an image file.
|
||||
// See check inside the render loop when a capture can be performed.
|
||||
document.addEventListener('keydown', (event) =>
|
||||
{
|
||||
if (event.key === 'f')
|
||||
{
|
||||
captureRequested = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize editor
|
||||
viewer.getEditor().initialize();
|
||||
|
||||
const hdrLoader = viewer.getHdrLoader();
|
||||
const fileLoader = viewer.getFileLoader();
|
||||
Promise.all([
|
||||
new Promise(resolve => hdrLoader.load('Lights/san_giuseppe_bridge_split.hdr', resolve)),
|
||||
new Promise(resolve => hdrLoader.load('Lights/irradiance/san_giuseppe_bridge_split.hdr', resolve)),
|
||||
new Promise(resolve => fileLoader.load('Lights/san_giuseppe_bridge_split.mtlx', resolve)),
|
||||
new Promise(function (resolve)
|
||||
{
|
||||
MaterialX().then((module) =>
|
||||
{
|
||||
resolve(module);
|
||||
});
|
||||
})
|
||||
]).then(async ([radianceTexture, irradianceTexture, lightRigXml, mxIn]) =>
|
||||
{
|
||||
// Initialize viewer + lighting
|
||||
await viewer.initialize(mxIn, renderer, radianceTexture, irradianceTexture, lightRigXml);
|
||||
|
||||
// Load geometry
|
||||
let scene = viewer.getScene();
|
||||
scene.loadGeometry(viewer, orbitControls);
|
||||
|
||||
// Load materials
|
||||
viewer.getMaterial().loadMaterials(viewer, materialFilename);
|
||||
|
||||
// Update assignments
|
||||
viewer.getMaterial().updateMaterialAssignments(viewer);
|
||||
|
||||
canvas.addEventListener("keydown", handleKeyEvents, true);
|
||||
|
||||
}).then(() =>
|
||||
{
|
||||
animate();
|
||||
}).catch(err =>
|
||||
{
|
||||
console.error(Number.isInteger(err) ? this.getMx().getExceptionMessage(err) : err);
|
||||
})
|
||||
|
||||
// allow dropping files and directories
|
||||
document.addEventListener('drop', dropHandler, false);
|
||||
document.addEventListener('dragover', dragOverHandler, false);
|
||||
|
||||
setLoadingCallback(file =>
|
||||
{
|
||||
materialFilename = file.fullPath || file.name;
|
||||
viewer.getEditor().initialize();
|
||||
viewer.getMaterial().loadMaterials(viewer, materialFilename);
|
||||
viewer.getEditor().updateProperties(0.9);
|
||||
});
|
||||
|
||||
setSceneLoadingCallback(file =>
|
||||
{
|
||||
let glbFileName = file.fullPath || file.name;
|
||||
console.log('Drop geometry to:', glbFileName);
|
||||
scene.setGeometryURL(glbFileName);
|
||||
scene.loadGeometry(viewer, orbitControls);
|
||||
});
|
||||
|
||||
// enable three.js Cache so that dropped files can reference each other
|
||||
THREE.Cache.enabled = true;
|
||||
}
|
||||
|
||||
function onWindowResize()
|
||||
{
|
||||
viewer.getScene().updateCamera();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate()
|
||||
{
|
||||
requestAnimationFrame(animate);
|
||||
const scene = viewer.getScene();
|
||||
|
||||
if (turntableEnabled)
|
||||
{
|
||||
turntableStep = (turntableStep + 1) % 360;
|
||||
var turntableAngle = turntableStep * (360.0 / turntableSteps) / 180.0 * Math.PI;
|
||||
scene._scene.rotation.y = turntableAngle;
|
||||
}
|
||||
|
||||
scene.updateTimeUniforms();
|
||||
renderer.render(scene.getScene(), scene.getCamera());
|
||||
|
||||
if (captureRequested)
|
||||
{
|
||||
captureFrame();
|
||||
captureRequested = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyEvents(event)
|
||||
{
|
||||
const V_KEY = 86;
|
||||
const P_KEY = 80;
|
||||
|
||||
if (event.keyCode == V_KEY)
|
||||
{
|
||||
viewer.getScene().toggleBackgroundTexture();
|
||||
}
|
||||
else if (event.keyCode == P_KEY)
|
||||
{
|
||||
turntableEnabled = !turntableEnabled;
|
||||
}
|
||||
}
|
||||
1679
MaterialX/javascript/MaterialXView/source/viewer.js
Normal file
1679
MaterialX/javascript/MaterialXView/source/viewer.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user