mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-05-02 01:25:00 +00:00
stuff
This commit is contained in:
24
MaterialX/javascript/MaterialXView/example_materials.json
Normal file
24
MaterialX/javascript/MaterialXView/example_materials.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"materials": [
|
||||
{
|
||||
"name": "StandardSurface",
|
||||
"path": "../../resources/Materials/Examples/StandardSurface",
|
||||
"baseURL": "Materials/Examples/StandardSurface"
|
||||
},
|
||||
{
|
||||
"name": "UsdPreviewSurface",
|
||||
"path": "../../resources/Materials/Examples/UsdPreviewSurface",
|
||||
"baseURL": "Materials/Examples/UsdPreviewSurface"
|
||||
},
|
||||
{
|
||||
"name": "GltfPbr",
|
||||
"path": "../../resources/Materials/Examples/GltfPbr",
|
||||
"baseURL": "Materials/Examples/GltfPbr"
|
||||
},
|
||||
{
|
||||
"name": "OpenPbr",
|
||||
"path": "../../resources/Materials/Examples/OpenPbr",
|
||||
"baseURL": "Materials/Examples/OpenPbr"
|
||||
}
|
||||
]
|
||||
}
|
||||
57
MaterialX/javascript/MaterialXView/index.ejs
Normal file
57
MaterialX/javascript/MaterialXView/index.ejs
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<title>MaterialX Web Viewer</title>
|
||||
<link rel="icon" type="image/x-icon" href="public/favicon.ico" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial
|
||||
}
|
||||
|
||||
/* Property editor item color */
|
||||
.peditoritem {
|
||||
background-color: #334444;
|
||||
}
|
||||
/* Property editor folder color */
|
||||
.peditorfolder {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.peditor_material_assigned {
|
||||
background-color: #006cb8;
|
||||
}
|
||||
.peditor_material_assigned:hover {
|
||||
background-color: #32adff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0px; overflow: hidden;">
|
||||
<div id="container">
|
||||
<div style="color:white; position: absolute; top: 0em; margin: 1em">
|
||||
<label for="materials">Material:</label>
|
||||
<select name="materials" id="materials">
|
||||
<% materials.forEach(function(m){ %>
|
||||
<option value="<%-m.value%>">
|
||||
<%-m.name%>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<div style="color:white; position: absolute; top: 1.5em; margin: 1em">
|
||||
<label for="geometry">Geometry:</label>
|
||||
<select name="geometry" id="geometry">
|
||||
<% geometry.forEach(function(m){ %>
|
||||
<option value="<%-m.value%>">
|
||||
<%-m.name%>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<canvas id="webglcanvas" tabindex="1" style="outline: none;"></canvas>
|
||||
</div>
|
||||
<script src="JsMaterialXGenShader.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
4509
MaterialX/javascript/MaterialXView/package-lock.json
generated
Normal file
4509
MaterialX/javascript/MaterialXView/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
MaterialX/javascript/MaterialXView/package.json
Normal file
23
MaterialX/javascript/MaterialXView/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "MaterialXView",
|
||||
"version": "1.0.0",
|
||||
"description": "MaterialX Web Viewer",
|
||||
"main": "source/index.js",
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"build": "webpack"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lil-gui": "^0.19.2",
|
||||
"three": "^0.152.2",
|
||||
"webpack": "^5.103.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"html-webpack-plugin": "^5.6.5",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.2"
|
||||
}
|
||||
}
|
||||
BIN
MaterialX/javascript/MaterialXView/public/favicon.ico
Normal file
BIN
MaterialX/javascript/MaterialXView/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 44C35.046 44 44 35.046 44 24C44 12.954 35.046 4 24 4C12.954 4 4 12.954 4 24C4 35.046 12.954 44 24 44Z" fill="#D8D8D8" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 44C17.372 44 12 35.046 12 24C12 12.954 17.372 4 24 4" fill="#D9D9D9"/>
|
||||
<path d="M24 44C17.372 44 12 35.046 12 24C12 12.954 17.372 4 24 4" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 547 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 44C35.046 44 44 35.046 44 24C44 12.954 35.046 4 24 4C12.954 4 4 12.954 4 24C4 35.046 12.954 44 24 44Z" fill="#46BBD0" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 44C17.372 44 12 35.046 12 24C12 12.954 17.372 4 24 4" fill="#D64274"/>
|
||||
<path d="M24 44C17.372 44 12 35.046 12 24C12 12.954 17.372 4 24 4" fill="#D64274"/>
|
||||
<path d="M24 44C17.372 44 12 35.046 12 24C12 12.954 17.372 4 24 4" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 44C17.372 44 12 35.046 12 24C12 12.954 17.372 4 24 4" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24.9932 42.5C33.9864 42 42.5 34.7699 42.5 24C42.5 13.2302 34 5.5 24.4932 5.5C14.9932 6 21 14.5 20.5 24C20.5 34.7699 16 43 24.9932 42.5Z" fill="#D64274"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 943 B |
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
78
MaterialX/javascript/MaterialXView/webpack.config.js
Normal file
78
MaterialX/javascript/MaterialXView/webpack.config.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
|
||||
// Load material configuration from external JSON file
|
||||
const materialConfig = JSON.parse(fs.readFileSync('./example_materials.json', 'utf8'));
|
||||
|
||||
// Function to process materials from a given path
|
||||
function processMaterialPath(materialPath, baseURL) {
|
||||
const dirent = fs.readdirSync(materialPath).filter(
|
||||
function (file) { if (file.lastIndexOf(".mtlx") > -1) return file; }
|
||||
);
|
||||
return dirent.map((fileName) => ({
|
||||
name: fileName,
|
||||
value: `${baseURL}/${fileName}`
|
||||
}));
|
||||
}
|
||||
|
||||
// Generate materials array from configuration
|
||||
let materials = [];
|
||||
materialConfig.materials.forEach(materialType => {
|
||||
const materialFiles = processMaterialPath(materialType.path, materialType.baseURL);
|
||||
materials = materials.concat(materialFiles);
|
||||
});
|
||||
|
||||
const geometryFiles = "../../resources/Geometry";
|
||||
const geometryFilesURL = "Geometry";
|
||||
dirent = fs.readdirSync(geometryFiles).filter(
|
||||
function (file) { if (file.lastIndexOf(".glb") > -1) return file; }
|
||||
)
|
||||
let geometry = dirent
|
||||
.map((fileName) => ({ name: fileName, value: `${geometryFilesURL}/${fileName}` }));
|
||||
|
||||
module.exports = {
|
||||
entry: './source/index.js',
|
||||
output: {
|
||||
filename: 'main.js',
|
||||
path: path.resolve(__dirname, 'dist')
|
||||
},
|
||||
mode: "development",
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
templateParameters: {
|
||||
materials,
|
||||
geometry
|
||||
},
|
||||
template: 'index.ejs'
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
context: "../../resources/Images",
|
||||
from: "*.*",
|
||||
to: "Images",
|
||||
},
|
||||
{
|
||||
context: "../../resources/Geometry/",
|
||||
from: "*.glb",
|
||||
to: "Geometry",
|
||||
},
|
||||
{ from: "./public", to: 'public' },
|
||||
{ context: "../../resources/Lights", from: "*.*", to: "Lights" },
|
||||
{ context: "../../resources/Lights/irradiance", from: "*.*", to: "Lights/irradiance" },
|
||||
// Dynamically generate material copy patterns from configuration
|
||||
...materialConfig.materials.map(materialType => ({
|
||||
from: materialType.path,
|
||||
to: materialType.baseURL
|
||||
})),
|
||||
{ from: "../build/bin/JsMaterialXCore.wasm" },
|
||||
{ from: "../build/bin/JsMaterialXCore.js" },
|
||||
{ from: "../build/bin/JsMaterialXGenShader.wasm" },
|
||||
{ from: "../build/bin/JsMaterialXGenShader.js" },
|
||||
{ from: "../build/bin/JsMaterialXGenShader.data" },
|
||||
],
|
||||
}),
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user