render mii and shader with mii-unsecure glTF and three.js

by arian_

HTML

<!-- UI for inputting the model URL and controlling rotation -->
<div id="uiContainer" class="resizable">
    <button id="showUiButton" class="floating" type="button" style="display: none;">Show UI</button>
    <div id="ui" class="floating">
        <form id="modelForm">
            <div class="controls">
                <label for="modelUrl">glTF Model URL:</label>
              
                <!--<label for="shaderSelector">Select Shader:</label>-->
                <select id="shaderSelector">
                  <option value="FFLShaderMaterial" selected>FFLShaderMaterial</option>
                  <option value="LUTShaderMaterial">LUTShaderMaterial</option>
                </select>
              
                <button id="hideUiButton" type="button">Hide UI</button>
            </div>
            <input type="text" id="modelUrl" value="https://mii-unsecure.ariankordi.net/miis/image.glb?nnid=JasmineChlora">
            <small class="hint">Hint: To find this, go to <a href="https://mii-unsecure.ariankordi.net" target="_blank">my Mii renderer site</a>, copy your Mii's image URL, then replace "image.png" with "image.glb" and paste it into here.</small>
            <button id="loadModelButton" type="submit">Load Model</button>
        </form>
        <div class="controls">

            <div id="lightDirectionControl">
                <details>
                    <summary>Light Direction Control</summary>
                    <label for="lightDirX">Light Direction X:</label>
                    <input type="range" id="lightDirX" min="-1" max="1" step="0.01" value="-0.453">

                    <label for="lightDirY">Light Direction Y:</label>
                    <input type="range" id="lightDirY" min="-1" max="1" step="0.01" value="0.423">

                    <label for="lightDirZ">Light Direction Z:</label>
                    <input type="range" id="lightDirZ" min="-1" max="1" step="0.01" value="0.785">

                    <button id="resetLightButton">Reset...

CSS

body { margin: 0; overflow: hidden; }
canvas { display: block; }

.floating {
    position: absolute;
    top: 10px;
    left: 10px;
}

#ui {
    background: rgba(255,255,255,0.8);
    padding: 10px;
    border-radius: 4px;
    font-family: Arial, sans-serif;
    z-index: 1;
    overflow: hidden; /* Ensure content remains within the boundaries */
    resize: both;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); /* Optional shadow for better visibility */
}
#ui input, #ui button, #ui label {
    display: block;
    margin: 5px 0;
}
#modelUrl {
    width: 100%; /* Ensure input field takes up available width within the constrained size */
}
/* Flexbox layout for aligning buttons */
.controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.hint {
    color: gray;
    display: block;
    margin-top: 5px;
    font-size: 12px;
    max-width: 430px; /* Constrain the width of the hint text */
    word-wrap: break-word; /* Ensure long text wraps inside the constrained width */
}

JavaScript

// @ts-check
// import * as THREE from 'three';
// import * as FFLShaderMaterial from './FFLShaderMaterial.js';
// import * as LUTShaderMaterial from './LUTShaderMaterial.js';

/* eslint @stylistic/indent: ['error', 2] -- Define indent rules. */

/* NOTglobals FFLShaderMaterial LUTShaderMaterial -- Global dependencies. */

/** @type {Object<string, function(new: THREE.Material, ...*): THREE.Material>} */
const shaders = { FFLShaderMaterial, LUTShaderMaterial };

// // ---------------------------------------------------------------------
// //  Active Shader Class Handling
// // ---------------------------------------------------------------------
/** The name of the active shader class, initialized to the first. */
let activeShaderClassName = Object.keys(shaders)[0];

/**
 * Applies the current shader material to the mesh.
 * @param {THREE.Mesh} mesh - The mesh to apply the shader material to.
 * @param {{modulateType: number, modulateMode: number, modulateColor: Array<number>}} userData -
 * The userData from the glTF.
 * @param {THREE.MeshBasicMaterial & {_side: THREE.Side|undefined}} originalMaterial -
 * The original material of the mesh.
 */
function applyShaderMaterial(mesh, userData, originalMaterial) {
  const ShaderClass = shaders[activeShaderClassName];
  if (!ShaderClass) {
    console.error(`Shader class ${activeShaderClassName} not found.`);
    return;
  }

  /** Color bound to the material. Defaults to red if missing. */
  const modulateColor = Array.isArray(userData.modulateColor)
    ? new THREE.Color(userData.modulateColor[0],
      userData.modulateColor[1], userData.modulateColor[2])
    : new THREE.Color(1, 0, 0);

  // Create the shader material
  const shaderMaterial = new ShaderClass({
    // _side = original side from LUTShaderMaterial, must be set first
    side: (originalMaterial._side !== undefined) ? originalMaterial._side : originalMaterial.side,
    modulateMode: userData.modulateMode,
    modulateType: userData.modulateType, //...