Initial project import
This commit is contained in:
13
node_modules/three/examples/jsm/inspector/Extension.js
generated
vendored
Normal file
13
node_modules/three/examples/jsm/inspector/Extension.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Tab } from 'three/addons/inspector/ui/Tab.js';
|
||||
|
||||
export class Extension extends Tab {
|
||||
|
||||
constructor( name, options = {} ) {
|
||||
|
||||
super( name, options );
|
||||
|
||||
this.isExtension = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
542
node_modules/three/examples/jsm/inspector/Inspector.js
generated
vendored
Normal file
542
node_modules/three/examples/jsm/inspector/Inspector.js
generated
vendored
Normal file
@@ -0,0 +1,542 @@
|
||||
|
||||
import { RendererInspector } from './RendererInspector.js';
|
||||
import { Profiler } from './ui/Profiler.js';
|
||||
import { Performance } from './tabs/Performance.js';
|
||||
import { Memory } from './tabs/Memory.js';
|
||||
import { Console } from './tabs/Console.js';
|
||||
import { Parameters } from './tabs/Parameters.js';
|
||||
import { Settings } from './tabs/Settings.js';
|
||||
import { Viewer } from './tabs/Viewer.js';
|
||||
import { Timeline } from './tabs/Timeline.js';
|
||||
import { setText } from './ui/utils.js';
|
||||
|
||||
import { setConsoleFunction, REVISION } from 'three/webgpu';
|
||||
|
||||
class Inspector extends RendererInspector {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
// init profiler
|
||||
|
||||
const profiler = new Profiler( this );
|
||||
profiler.addEventListener( 'resize', ( e ) => this.dispatchEvent( e ) );
|
||||
|
||||
const parameters = new Parameters( {
|
||||
builtin: true,
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 6l8 0" /><path d="M16 6l4 0" /><path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 12l2 0" /><path d="M10 12l10 0" /><path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M4 18l11 0" /><path d="M19 18l1 0" /></svg>'
|
||||
} );
|
||||
parameters.hide();
|
||||
profiler.addTab( parameters );
|
||||
|
||||
const viewer = new Viewer();
|
||||
viewer.hide();
|
||||
profiler.addTab( viewer );
|
||||
|
||||
const performance = new Performance();
|
||||
profiler.addTab( performance );
|
||||
|
||||
const memory = new Memory();
|
||||
profiler.addTab( memory );
|
||||
|
||||
const timeline = new Timeline();
|
||||
profiler.addTab( timeline );
|
||||
|
||||
const consoleTab = new Console();
|
||||
profiler.addTab( consoleTab );
|
||||
|
||||
const settings = new Settings();
|
||||
profiler.addTab( settings );
|
||||
|
||||
profiler.loadLayout();
|
||||
|
||||
if ( ! profiler.activeTabId ) {
|
||||
|
||||
profiler.setActiveTab( performance.id );
|
||||
|
||||
}
|
||||
|
||||
this.statsData = new Map();
|
||||
this.profiler = profiler;
|
||||
this.performance = performance;
|
||||
this.memory = memory;
|
||||
this.console = consoleTab;
|
||||
this.parameters = parameters;
|
||||
this.viewer = viewer;
|
||||
this.timeline = timeline;
|
||||
this.settings = settings;
|
||||
this.once = {};
|
||||
this.extensionsData = new WeakMap();
|
||||
|
||||
this.displayCycle = {
|
||||
text: {
|
||||
needsUpdate: false,
|
||||
duration: .25,
|
||||
time: 0
|
||||
},
|
||||
graph: {
|
||||
needsUpdate: false,
|
||||
duration: .02,
|
||||
time: 0
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
get domElement() {
|
||||
|
||||
return this.profiler.domElement;
|
||||
|
||||
}
|
||||
|
||||
onExtension( name, callback ) {
|
||||
|
||||
const extensionAdded = ( e ) => {
|
||||
|
||||
if ( e.name === name ) {
|
||||
|
||||
callback( e.tab );
|
||||
|
||||
this.settings.removeEventListener( 'extensionadded', extensionAdded );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if ( this.settings.extensions[ name ] && this.settings.extensions[ name ].loaded ) {
|
||||
|
||||
callback( this.settings.extensions[ name ] );
|
||||
|
||||
} else {
|
||||
|
||||
this.settings.addEventListener( 'extensionadded', extensionAdded );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
hide() {
|
||||
|
||||
this.profiler.hide();
|
||||
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
this.profiler.show();
|
||||
|
||||
}
|
||||
|
||||
getSize() {
|
||||
|
||||
return this.profiler.getSize();
|
||||
|
||||
}
|
||||
|
||||
setActiveTab( tab ) {
|
||||
|
||||
this.profiler.setActiveTab( tab.id );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
addTab( tab ) {
|
||||
|
||||
this.profiler.addTab( tab );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
removeTab( tab ) {
|
||||
|
||||
this.profiler.removeTab( tab );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
setActiveExtension( name, value ) {
|
||||
|
||||
this.settings.setActiveExtension( name, value );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
resolveConsoleOnce( type, message ) {
|
||||
|
||||
const key = type + message;
|
||||
|
||||
if ( this.once[ key ] !== true ) {
|
||||
|
||||
this.resolveConsole( type, message );
|
||||
this.once[ key ] = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resolveConsole( type, message, stackTrace = null ) {
|
||||
|
||||
switch ( type ) {
|
||||
|
||||
case 'log':
|
||||
|
||||
this.console.addMessage( 'info', message );
|
||||
|
||||
console.log( message );
|
||||
|
||||
break;
|
||||
|
||||
case 'warn':
|
||||
|
||||
this.console.addMessage( 'warn', message );
|
||||
|
||||
if ( stackTrace && stackTrace.isStackTrace ) {
|
||||
|
||||
console.warn( stackTrace.getError( message ) );
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( message );
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
|
||||
this.console.addMessage( 'error', message );
|
||||
|
||||
if ( stackTrace && stackTrace.isStackTrace ) {
|
||||
|
||||
console.error( stackTrace.getError( message ) );
|
||||
|
||||
} else {
|
||||
|
||||
console.error( message );
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
const renderer = this.getRenderer();
|
||||
|
||||
let sign = `THREE.WebGPURenderer: ${ REVISION } [ "`;
|
||||
|
||||
if ( renderer.backend.isWebGPUBackend ) {
|
||||
|
||||
sign += 'WebGPU';
|
||||
|
||||
} else if ( renderer.backend.isWebGLBackend ) {
|
||||
|
||||
sign += 'WebGL2';
|
||||
|
||||
}
|
||||
|
||||
sign += '" ]';
|
||||
|
||||
this.console.addMessage( 'info', sign );
|
||||
|
||||
//
|
||||
|
||||
if ( renderer.inspector.domElement.parentElement === null && renderer.domElement.parentElement !== null ) {
|
||||
|
||||
renderer.domElement.parentElement.appendChild( renderer.inspector.domElement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setRenderer( renderer ) {
|
||||
|
||||
super.setRenderer( renderer );
|
||||
|
||||
if ( renderer !== null ) {
|
||||
|
||||
setConsoleFunction( this.resolveConsole.bind( this ) );
|
||||
|
||||
if ( this.isAvailable ) {
|
||||
|
||||
renderer.init().then( () => {
|
||||
|
||||
renderer.backend.trackTimestamp = true;
|
||||
|
||||
if ( renderer.hasFeature( 'timestamp-query' ) !== true ) {
|
||||
|
||||
this.console.addMessage( 'error', 'THREE.Inspector: GPU Timestamp Queries not available.' );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
this.timeline.setRenderer( renderer );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
createParameters( name ) {
|
||||
|
||||
if ( this.parameters.isVisible === false ) {
|
||||
|
||||
this.parameters.show();
|
||||
|
||||
}
|
||||
|
||||
return this.parameters.createGroup( name );
|
||||
|
||||
}
|
||||
|
||||
getStatsData( cid ) {
|
||||
|
||||
let data = this.statsData.get( cid );
|
||||
|
||||
if ( data === undefined ) {
|
||||
|
||||
data = {};
|
||||
|
||||
this.statsData.set( cid, data );
|
||||
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
resolveStats( stats ) {
|
||||
|
||||
const data = this.getStatsData( stats.cid );
|
||||
|
||||
if ( data.initialized !== true ) {
|
||||
|
||||
data.cpu = stats.cpu;
|
||||
data.gpu = stats.gpu;
|
||||
data.stats = [];
|
||||
|
||||
data.initialized = true;
|
||||
|
||||
}
|
||||
|
||||
// store stats
|
||||
|
||||
if ( data.stats.length > this.maxFrames ) {
|
||||
|
||||
data.stats.shift();
|
||||
|
||||
}
|
||||
|
||||
data.stats.push( stats );
|
||||
|
||||
// compute averages
|
||||
|
||||
data.cpu = this.getAverageDeltaTime( data, 'cpu' );
|
||||
data.gpu = this.getAverageDeltaTime( data, 'gpu' );
|
||||
data.total = data.cpu + data.gpu;
|
||||
|
||||
// children
|
||||
|
||||
for ( const child of stats.children ) {
|
||||
|
||||
this.resolveStats( child );
|
||||
|
||||
const childData = this.getStatsData( child.cid );
|
||||
|
||||
data.cpu += childData.cpu;
|
||||
data.gpu += childData.gpu;
|
||||
data.total += childData.total;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getNodes() {
|
||||
|
||||
return this.currentNodes;
|
||||
|
||||
}
|
||||
|
||||
getAverageDeltaTime( statsData, property, frames = this.fps ) {
|
||||
|
||||
const statsArray = statsData.stats;
|
||||
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
|
||||
for ( let i = statsArray.length - 1; i >= 0 && count < frames; i -- ) {
|
||||
|
||||
const stats = statsArray[ i ];
|
||||
const value = stats[ property ];
|
||||
|
||||
if ( value > 0 ) {
|
||||
|
||||
// ignore invalid values
|
||||
|
||||
sum += value;
|
||||
count ++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return count > 0 ? sum / count : 0;
|
||||
|
||||
}
|
||||
|
||||
updateTabs() {
|
||||
|
||||
// tabs
|
||||
|
||||
const tabs = Object.values( this.profiler.tabs );
|
||||
|
||||
for ( const tab of tabs ) {
|
||||
|
||||
let tabData = this.extensionsData.get( tab );
|
||||
|
||||
if ( tabData === undefined ) {
|
||||
|
||||
tab.init( this );
|
||||
|
||||
tabData = {};
|
||||
|
||||
this.extensionsData.set( tab, tabData );
|
||||
|
||||
}
|
||||
|
||||
tab.update( this );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resolveFrame( frame ) {
|
||||
|
||||
const nextFrame = this.getFrameById( frame.frameId + 1 );
|
||||
|
||||
if ( ! nextFrame ) return;
|
||||
|
||||
frame.cpu = 0;
|
||||
frame.gpu = 0;
|
||||
frame.total = 0;
|
||||
|
||||
for ( const stats of frame.children ) {
|
||||
|
||||
this.resolveStats( stats );
|
||||
|
||||
const data = this.getStatsData( stats.cid );
|
||||
|
||||
frame.cpu += data.cpu;
|
||||
frame.gpu += data.gpu;
|
||||
frame.total += data.total;
|
||||
|
||||
}
|
||||
|
||||
// improve stats using next frame
|
||||
|
||||
frame.deltaTime = nextFrame.startTime - frame.startTime;
|
||||
frame.miscellaneous = frame.deltaTime - frame.total;
|
||||
|
||||
if ( frame.miscellaneous < 0 ) {
|
||||
|
||||
// Frame desync, probably due to async GPU timing.
|
||||
|
||||
frame.miscellaneous = 0;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
this.updateCycle( this.displayCycle.text );
|
||||
this.updateCycle( this.displayCycle.graph );
|
||||
|
||||
if ( this.displayCycle.text.needsUpdate ) {
|
||||
|
||||
setText( 'fps-counter', this.fps.toFixed() );
|
||||
|
||||
this.performance.updateText( this, frame );
|
||||
this.memory.updateText( this );
|
||||
|
||||
}
|
||||
|
||||
if ( this.displayCycle.graph.needsUpdate ) {
|
||||
|
||||
this.performance.updateGraph( this, frame );
|
||||
this.memory.updateGraph( this );
|
||||
|
||||
}
|
||||
|
||||
this.displayCycle.text.needsUpdate = false;
|
||||
this.displayCycle.graph.needsUpdate = false;
|
||||
|
||||
}
|
||||
|
||||
updateCycle( cycle ) {
|
||||
|
||||
cycle.time += this.nodeFrame.deltaTime;
|
||||
|
||||
if ( cycle.time >= cycle.duration ) {
|
||||
|
||||
cycle.needsUpdate = true;
|
||||
cycle.time = 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static getItem( id ) {
|
||||
|
||||
console.warn( 'Inspector.getItem is deprecated. Use getItem directly instead.' );
|
||||
return getItem( id );
|
||||
|
||||
}
|
||||
|
||||
static setItem( id, state ) {
|
||||
|
||||
console.warn( 'Inspector.setItem is deprecated. Use setItem directly instead.' );
|
||||
setItem( id, state );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getItem( id ) {
|
||||
|
||||
const data = JSON.parse( localStorage.getItem( 'threejs-inspector' ) || '{}' );
|
||||
return data[ id ] || {};
|
||||
|
||||
}
|
||||
|
||||
function setItem( id, state ) {
|
||||
|
||||
const data = JSON.parse( localStorage.getItem( 'threejs-inspector' ) || '{}' );
|
||||
|
||||
if ( state === null ) {
|
||||
|
||||
delete data[ id ];
|
||||
|
||||
} else {
|
||||
|
||||
data[ id ] = state;
|
||||
|
||||
}
|
||||
|
||||
localStorage.setItem( 'threejs-inspector', JSON.stringify( data ) );
|
||||
|
||||
}
|
||||
|
||||
export { Inspector, getItem, setItem };
|
||||
425
node_modules/three/examples/jsm/inspector/RendererInspector.js
generated
vendored
Normal file
425
node_modules/three/examples/jsm/inspector/RendererInspector.js
generated
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
|
||||
import { InspectorBase, TimestampQuery, warnOnce } from 'three/webgpu';
|
||||
|
||||
class ObjectStats {
|
||||
|
||||
constructor( uid, name ) {
|
||||
|
||||
this.uid = uid;
|
||||
this.cid = uid.match( /^(.*):f(\d+)$/ )[ 1 ]; // call id
|
||||
this.name = name;
|
||||
this.timestamp = 0;
|
||||
this.cpu = 0;
|
||||
this.gpu = 0;
|
||||
this.fps = 0;
|
||||
|
||||
this.children = [];
|
||||
this.parent = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RenderStats extends ObjectStats {
|
||||
|
||||
constructor( uid, scene, camera, renderTarget ) {
|
||||
|
||||
let name = scene.name;
|
||||
|
||||
if ( name === '' ) {
|
||||
|
||||
if ( scene.isScene ) {
|
||||
|
||||
name = 'Scene';
|
||||
|
||||
} else if ( scene.isQuadMesh ) {
|
||||
|
||||
name = 'QuadMesh';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
super( uid, name );
|
||||
|
||||
this.scene = scene;
|
||||
this.camera = camera;
|
||||
this.renderTarget = renderTarget;
|
||||
|
||||
this.isRenderStats = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ComputeStats extends ObjectStats {
|
||||
|
||||
constructor( uid, computeNode ) {
|
||||
|
||||
super( uid, computeNode.name );
|
||||
|
||||
this.computeNode = computeNode;
|
||||
|
||||
this.isComputeStats = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class RendererInspector extends InspectorBase {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.currentFrame = null;
|
||||
this.currentRender = null;
|
||||
this.currentNodes = null;
|
||||
this.lastFrame = null;
|
||||
|
||||
this.frames = [];
|
||||
this.framesLib = {};
|
||||
this.maxFrames = 512;
|
||||
|
||||
this._lastFinishTime = 0;
|
||||
this._resolveTimestampPromise = null;
|
||||
|
||||
this.isRendererInspector = true;
|
||||
|
||||
}
|
||||
|
||||
getParent() {
|
||||
|
||||
return this.currentRender || this.getFrame();
|
||||
|
||||
}
|
||||
|
||||
begin() {
|
||||
|
||||
this.currentFrame = this._createFrame();
|
||||
this.currentRender = this.currentFrame;
|
||||
this.currentNodes = [];
|
||||
|
||||
}
|
||||
|
||||
finish() {
|
||||
|
||||
const now = performance.now();
|
||||
|
||||
const frame = this.currentFrame;
|
||||
frame.finishTime = now;
|
||||
frame.deltaTime = now - ( this._lastFinishTime > 0 ? this._lastFinishTime : now );
|
||||
|
||||
this.addFrame( frame );
|
||||
|
||||
this.fps = this._getFPS();
|
||||
|
||||
this.lastFrame = frame;
|
||||
|
||||
this.currentFrame = null;
|
||||
this.currentRender = null;
|
||||
this.currentNodes = null;
|
||||
|
||||
this._lastFinishTime = now;
|
||||
|
||||
}
|
||||
|
||||
_getFPS() {
|
||||
|
||||
let frameSum = 0;
|
||||
let timeSum = 0;
|
||||
|
||||
for ( let i = this.frames.length - 1; i >= 0; i -- ) {
|
||||
|
||||
const frame = this.frames[ i ];
|
||||
|
||||
frameSum ++;
|
||||
timeSum += frame.deltaTime;
|
||||
|
||||
if ( timeSum >= 1000 ) break;
|
||||
|
||||
}
|
||||
|
||||
return ( frameSum * 1000 ) / timeSum;
|
||||
|
||||
}
|
||||
|
||||
_createFrame() {
|
||||
|
||||
return {
|
||||
frameId: this.nodeFrame.frameId,
|
||||
resolvedCompute: false,
|
||||
resolvedRender: false,
|
||||
deltaTime: 0,
|
||||
startTime: performance.now(),
|
||||
finishTime: 0,
|
||||
miscellaneous: 0,
|
||||
children: [],
|
||||
renders: [],
|
||||
computes: []
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
getFrame() {
|
||||
|
||||
return this.currentFrame || this.lastFrame;
|
||||
|
||||
}
|
||||
|
||||
getFrameById( frameId ) {
|
||||
|
||||
return this.framesLib[ frameId ] || null;
|
||||
|
||||
}
|
||||
|
||||
updateTabs() { }
|
||||
|
||||
resolveFrame( /*frame*/ ) { }
|
||||
|
||||
async resolveTimestamp() {
|
||||
|
||||
if ( this._resolveTimestampPromise !== null ) {
|
||||
|
||||
return this._resolveTimestampPromise;
|
||||
|
||||
}
|
||||
|
||||
this._resolveTimestampPromise = new Promise( ( resolve ) => {
|
||||
|
||||
requestAnimationFrame( async () => {
|
||||
|
||||
const renderer = this.getRenderer();
|
||||
|
||||
await renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE );
|
||||
await renderer.resolveTimestampsAsync( TimestampQuery.RENDER );
|
||||
|
||||
const computeFrames = renderer.backend.getTimestampFrames( TimestampQuery.COMPUTE );
|
||||
const renderFrames = renderer.backend.getTimestampFrames( TimestampQuery.RENDER );
|
||||
|
||||
const frameIds = [ ...new Set( [ ...computeFrames, ...renderFrames ] ) ];
|
||||
|
||||
for ( const frameId of frameIds ) {
|
||||
|
||||
const frame = this.getFrameById( frameId );
|
||||
|
||||
if ( frame !== null ) {
|
||||
|
||||
// resolve compute timestamps
|
||||
|
||||
if ( frame.resolvedCompute === false ) {
|
||||
|
||||
if ( frame.computes.length > 0 ) {
|
||||
|
||||
if ( computeFrames.includes( frameId ) ) {
|
||||
|
||||
for ( const stats of frame.computes ) {
|
||||
|
||||
if ( renderer.backend.hasTimestamp( stats.uid ) ) {
|
||||
|
||||
stats.gpu = renderer.backend.getTimestamp( stats.uid );
|
||||
|
||||
} else {
|
||||
|
||||
stats.gpu = 0;
|
||||
stats.gpuNotAvailable = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
frame.resolvedCompute = true;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
frame.resolvedCompute = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// resolve render timestamps
|
||||
|
||||
if ( frame.resolvedRender === false ) {
|
||||
|
||||
if ( frame.renders.length > 0 ) {
|
||||
|
||||
if ( renderFrames.includes( frameId ) ) {
|
||||
|
||||
for ( const stats of frame.renders ) {
|
||||
|
||||
if ( renderer.backend.hasTimestamp( stats.uid ) ) {
|
||||
|
||||
stats.gpu = renderer.backend.getTimestamp( stats.uid );
|
||||
|
||||
} else {
|
||||
|
||||
stats.gpu = 0;
|
||||
stats.gpuNotAvailable = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
frame.resolvedRender = true;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
frame.resolvedRender = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( frame.resolvedCompute === true && frame.resolvedRender === true ) {
|
||||
|
||||
this.resolveFrame( frame );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._resolveTimestampPromise = null;
|
||||
|
||||
resolve();
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
return this._resolveTimestampPromise;
|
||||
|
||||
}
|
||||
|
||||
get isAvailable() {
|
||||
|
||||
const renderer = this.getRenderer();
|
||||
|
||||
return renderer !== null;
|
||||
|
||||
}
|
||||
|
||||
addFrame( frame ) {
|
||||
|
||||
// Limit to max frames.
|
||||
|
||||
if ( this.frames.length >= this.maxFrames ) {
|
||||
|
||||
const removedFrame = this.frames.shift();
|
||||
delete this.framesLib[ removedFrame.frameId ];
|
||||
|
||||
}
|
||||
|
||||
this.frames.push( frame );
|
||||
this.framesLib[ frame.frameId ] = frame;
|
||||
|
||||
if ( this.isAvailable ) {
|
||||
|
||||
this.updateTabs();
|
||||
this.resolveTimestamp();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inspect( node ) {
|
||||
|
||||
const currentNodes = this.currentNodes;
|
||||
|
||||
if ( currentNodes !== null ) {
|
||||
|
||||
currentNodes.push( node );
|
||||
|
||||
} else {
|
||||
|
||||
warnOnce( 'RendererInspector: Unable to inspect node outside of frame scope. Use "renderer.setAnimationLoop()".' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
beginCompute( uid, computeNode ) {
|
||||
|
||||
const frame = this.getFrame();
|
||||
|
||||
if ( ! frame ) return;
|
||||
|
||||
const currentCompute = new ComputeStats( uid, computeNode );
|
||||
currentCompute.timestamp = performance.now();
|
||||
currentCompute.parent = this.currentCompute || this.getParent();
|
||||
|
||||
frame.computes.push( currentCompute );
|
||||
|
||||
if ( this.currentRender !== null ) {
|
||||
|
||||
this.currentRender.children.push( currentCompute );
|
||||
|
||||
} else {
|
||||
|
||||
frame.children.push( currentCompute );
|
||||
|
||||
}
|
||||
|
||||
this.currentCompute = currentCompute;
|
||||
|
||||
}
|
||||
|
||||
finishCompute() {
|
||||
|
||||
const frame = this.getFrame();
|
||||
|
||||
if ( ! frame ) return;
|
||||
|
||||
const currentCompute = this.currentCompute;
|
||||
currentCompute.cpu = performance.now() - currentCompute.timestamp;
|
||||
|
||||
this.currentCompute = currentCompute.parent.isComputeStats ? currentCompute.parent : null;
|
||||
|
||||
}
|
||||
|
||||
beginRender( uid, scene, camera, renderTarget ) {
|
||||
|
||||
const frame = this.getFrame();
|
||||
|
||||
if ( ! frame ) return;
|
||||
|
||||
const currentRender = new RenderStats( uid, scene, camera, renderTarget );
|
||||
currentRender.timestamp = performance.now();
|
||||
currentRender.parent = this.getParent();
|
||||
|
||||
frame.renders.push( currentRender );
|
||||
|
||||
if ( this.currentRender !== null ) {
|
||||
|
||||
this.currentRender.children.push( currentRender );
|
||||
|
||||
} else {
|
||||
|
||||
frame.children.push( currentRender );
|
||||
|
||||
}
|
||||
|
||||
this.currentRender = currentRender;
|
||||
|
||||
}
|
||||
|
||||
finishRender() {
|
||||
|
||||
const frame = this.getFrame();
|
||||
|
||||
if ( ! frame ) return;
|
||||
|
||||
const currentRender = this.currentRender;
|
||||
currentRender.cpu = performance.now() - currentRender.timestamp;
|
||||
|
||||
this.currentRender = currentRender.parent;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
6
node_modules/three/examples/jsm/inspector/extensions/extensions.json
generated
vendored
Normal file
6
node_modules/three/examples/jsm/inspector/extensions/extensions.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"name": "TSL Graph",
|
||||
"url": "./tsl-graph/TSLGraphEditor.js"
|
||||
}
|
||||
]
|
||||
916
node_modules/three/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js
generated
vendored
Normal file
916
node_modules/three/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js
generated
vendored
Normal file
@@ -0,0 +1,916 @@
|
||||
import { Raycaster, Vector2, BoxHelper, error, warn } from 'three/webgpu';
|
||||
import { Extension } from 'three/addons/inspector/Extension.js';
|
||||
import { TSLGraphLoader } from './TSLGraphLoader.js';
|
||||
|
||||
const HOST_SOURCE = 'tsl-graph-host';
|
||||
const EDITOR_SOURCE = 'tsl-graph-editor';
|
||||
|
||||
const _resposeByCommand = {
|
||||
'tsl:command:get-code': 'tsl:response:get-code',
|
||||
'tsl:command:set-root-material': 'tsl:response:set-root-material',
|
||||
'tsl:command:get-graph': 'tsl:response:get-graph',
|
||||
'tsl:command:load': 'tsl:response:load',
|
||||
'tsl:command:clear-graph': 'tsl:response:clear-graph'
|
||||
};
|
||||
|
||||
const _refMaterials = new WeakMap();
|
||||
|
||||
class TSLGraphEditor extends Extension {
|
||||
|
||||
constructor( options = {} ) {
|
||||
|
||||
super( 'TSL Graph', options );
|
||||
|
||||
const editorUrl = new URL( 'https://www.tsl-graph.xyz/editor/standalone' );
|
||||
editorUrl.searchParams.set( 'graphs', 'material' );
|
||||
editorUrl.searchParams.set( 'targetOrigin', '*' );
|
||||
|
||||
// UI Setup
|
||||
this.content.style.display = 'flex';
|
||||
this.content.style.flexDirection = 'column';
|
||||
this.content.style.position = 'relative';
|
||||
|
||||
const headerDiv = document.createElement( 'div' );
|
||||
headerDiv.style.padding = '4px';
|
||||
headerDiv.style.backgroundColor = 'var(--profiler-header-bg, #2a2a33aa)';
|
||||
headerDiv.style.borderBottom = '1px solid var(--profiler-border, #4a4a5a)';
|
||||
headerDiv.style.display = 'flex';
|
||||
headerDiv.style.justifyContent = 'center';
|
||||
headerDiv.style.gap = '4px';
|
||||
headerDiv.style.position = 'relative';
|
||||
|
||||
const importBtn = document.createElement( 'button' );
|
||||
importBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>';
|
||||
importBtn.className = 'panel-action-btn';
|
||||
importBtn.title = 'Import';
|
||||
importBtn.style.padding = '5px 8px';
|
||||
importBtn.onclick = () => this._importData();
|
||||
|
||||
const exportBtn = document.createElement( 'button' );
|
||||
exportBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>';
|
||||
exportBtn.className = 'panel-action-btn';
|
||||
exportBtn.title = 'Export';
|
||||
exportBtn.style.padding = '5px 8px';
|
||||
exportBtn.onclick = () => this._exportData();
|
||||
|
||||
const manageBtn = document.createElement( 'button' );
|
||||
manageBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="14" width="7" height="7" rx="1"></rect><rect x="3" y="3" width="7" height="7" rx="1"></rect><path d="M14 4h7"></path><path d="M14 9h7"></path><path d="M14 15h7"></path><path d="M14 20h7"></path></svg>';
|
||||
manageBtn.className = 'panel-action-btn';
|
||||
manageBtn.title = 'Saved Materials';
|
||||
manageBtn.style.padding = '5px 8px';
|
||||
manageBtn.onclick = () => this._showManagerModal();
|
||||
|
||||
const autoIdBtn = document.createElement( 'button' );
|
||||
autoIdBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3c.132 5.466 2.534 7.868 8 8-5.466.132-7.868 2.534-8 8-.132-5.466-2.534-7.868-8-8 5.466-.132 7.868-2.534 8-8z"></path></svg>';
|
||||
autoIdBtn.className = 'panel-action-btn';
|
||||
autoIdBtn.title = 'Auto-Generate Graph ID';
|
||||
autoIdBtn.style.padding = '5px 8px';
|
||||
autoIdBtn.style.position = 'absolute';
|
||||
autoIdBtn.style.right = '4px';
|
||||
autoIdBtn.style.top = '4px';
|
||||
|
||||
this.autoGraphId = false;
|
||||
|
||||
autoIdBtn.onclick = () => {
|
||||
|
||||
this.autoGraphId = ! this.autoGraphId;
|
||||
|
||||
if ( this.autoGraphId ) {
|
||||
|
||||
autoIdBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
|
||||
autoIdBtn.style.color = '#fff';
|
||||
|
||||
} else {
|
||||
|
||||
autoIdBtn.style.backgroundColor = '';
|
||||
autoIdBtn.style.color = '';
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
headerDiv.appendChild( importBtn );
|
||||
headerDiv.appendChild( exportBtn );
|
||||
headerDiv.appendChild( manageBtn );
|
||||
headerDiv.appendChild( autoIdBtn );
|
||||
|
||||
this.content.appendChild( headerDiv );
|
||||
|
||||
this.iframe = document.createElement( 'iframe' );
|
||||
this.iframe.style.width = '100%';
|
||||
this.iframe.style.height = '100%';
|
||||
this.iframe.style.border = 'none';
|
||||
this.iframe.src = editorUrl.toString();
|
||||
this.editorOrigin = new URL( this.iframe.src ).origin;
|
||||
|
||||
this.content.appendChild( this.iframe );
|
||||
|
||||
this.material = null;
|
||||
this.uniforms = null;
|
||||
|
||||
this.isReady = false;
|
||||
|
||||
this._codeData = null;
|
||||
this._codeSaveTimeout = null;
|
||||
|
||||
this._pending = new Map();
|
||||
|
||||
this._resolveReady = null;
|
||||
this._editorReady = new Promise( ( resolve ) => {
|
||||
|
||||
this._resolveReady = resolve;
|
||||
|
||||
} );
|
||||
|
||||
window.addEventListener( 'message', this.onMessage.bind( this ) );
|
||||
|
||||
}
|
||||
|
||||
get hasGraphs() {
|
||||
|
||||
return TSLGraphLoader.hasGraphs;
|
||||
|
||||
}
|
||||
|
||||
_initPicker( inspector ) {
|
||||
|
||||
const renderer = inspector.getRenderer();
|
||||
|
||||
let boundingBox = null;
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
const pointer = new Vector2();
|
||||
|
||||
const removeBoundingBox = () => {
|
||||
|
||||
if ( boundingBox ) {
|
||||
|
||||
boundingBox.removeFromParent();
|
||||
boundingBox.dispose();
|
||||
boundingBox = null;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.addEventListener( 'change', ( { material } ) => {
|
||||
|
||||
if ( material === null ) {
|
||||
|
||||
removeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
this.addEventListener( 'remove', ( { graphId } ) => {
|
||||
|
||||
const frame = inspector.getFrame();
|
||||
const scene = frame && frame.renders.length > 0 ? frame.renders[ 0 ].scene : null;
|
||||
|
||||
if ( scene ) {
|
||||
|
||||
scene.traverse( ( object ) => {
|
||||
|
||||
if ( object.material && object.material.userData && object.material.userData.graphId === graphId ) {
|
||||
|
||||
this.restoreMaterial( object.material );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
const pointerDownPosition = new Vector2();
|
||||
|
||||
renderer.domElement.addEventListener( 'pointerdown', ( e ) => {
|
||||
|
||||
pointerDownPosition.set( e.clientX, e.clientY );
|
||||
|
||||
} );
|
||||
|
||||
renderer.domElement.addEventListener( 'pointerup', ( e ) => {
|
||||
|
||||
const frame = inspector.getFrame();
|
||||
|
||||
for ( const render of frame.renders ) {
|
||||
|
||||
const scene = render.scene;
|
||||
|
||||
if ( scene.isScene !== true ) continue;
|
||||
|
||||
const camera = render.camera;
|
||||
|
||||
if ( pointerDownPosition.distanceTo( pointer.set( e.clientX, e.clientY ) ) > 2 ) return;
|
||||
|
||||
const rect = renderer.domElement.getBoundingClientRect();
|
||||
pointer.x = ( ( e.clientX - rect.left ) / rect.width ) * 2 - 1;
|
||||
pointer.y = - ( ( e.clientY - rect.top ) / rect.height ) * 2 + 1;
|
||||
|
||||
raycaster.setFromCamera( pointer, camera );
|
||||
|
||||
const intersects = raycaster.intersectObjects( scene.children, true );
|
||||
|
||||
let graphMaterial = null;
|
||||
|
||||
if ( intersects.length > 0 ) {
|
||||
|
||||
for ( const intersect of intersects ) {
|
||||
|
||||
const object = intersect.object;
|
||||
const material = object.material;
|
||||
|
||||
if ( material && material.isNodeMaterial ) {
|
||||
|
||||
removeBoundingBox();
|
||||
|
||||
boundingBox = new BoxHelper( object, 0xffff00 );
|
||||
scene.add( boundingBox );
|
||||
|
||||
graphMaterial = material;
|
||||
|
||||
}
|
||||
|
||||
if ( object.isMesh || object.isSprite ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.setMaterial( graphMaterial );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
apply( scene ) {
|
||||
|
||||
const loader = new TSLGraphLoader();
|
||||
const applier = loader.parse( TSLGraphLoader.getCodes() );
|
||||
applier.apply( scene );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
restoreMaterial( material ) {
|
||||
|
||||
material.copy( new material.constructor() );
|
||||
material.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
init( inspector ) {
|
||||
|
||||
this._initPicker( inspector );
|
||||
|
||||
}
|
||||
|
||||
async setMaterial( material ) {
|
||||
|
||||
if ( this.material === material ) return;
|
||||
|
||||
await this._setMaterial( material );
|
||||
|
||||
this.dispatchEvent( { type: 'change', material } );
|
||||
|
||||
}
|
||||
|
||||
async loadGraph( graphData ) {
|
||||
|
||||
await this.command( 'load', { graphData } );
|
||||
|
||||
}
|
||||
|
||||
async command( type, payload ) {
|
||||
|
||||
type = 'tsl:command:' + type;
|
||||
|
||||
await this._editorReady;
|
||||
|
||||
const requestId = this._makeRequestId();
|
||||
const expectedType = _resposeByCommand[ type ];
|
||||
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
|
||||
const timer = window.setTimeout( () => {
|
||||
|
||||
if ( ! this._pending.has( requestId ) ) return;
|
||||
this._pending.delete( requestId );
|
||||
reject( new Error( `Timeout for ${type}` ) );
|
||||
|
||||
}, 5000 );
|
||||
|
||||
this._pending.set( requestId, { expectedType, resolve, reject, timer } );
|
||||
|
||||
const message = { source: HOST_SOURCE, type, requestId };
|
||||
if ( payload !== undefined ) message.payload = payload;
|
||||
|
||||
this._post( message );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
async getCode() {
|
||||
|
||||
return this.command( 'get-code' );
|
||||
|
||||
}
|
||||
|
||||
async getTSLFunction() {
|
||||
|
||||
const graphLoader = new TSLGraphLoader();
|
||||
const applier = graphLoader.parse( await this.getCode() );
|
||||
|
||||
return applier.tslGraphFns[ 'tslGraph' ];
|
||||
|
||||
}
|
||||
|
||||
async getGraph() {
|
||||
|
||||
return ( await this.command( 'get-graph' ) ).graphData;
|
||||
|
||||
}
|
||||
|
||||
async onResponse( /*type, payload*/ ) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
async onEvent( type, payload ) {
|
||||
|
||||
if ( type === 'ready' ) {
|
||||
|
||||
if ( ! this.isReady ) {
|
||||
|
||||
this.isReady = true;
|
||||
|
||||
this._resolveReady();
|
||||
|
||||
}
|
||||
|
||||
} else if ( type === 'graph-changed' ) {
|
||||
|
||||
if ( this.material === null ) return;
|
||||
|
||||
await this._updateMaterial();
|
||||
|
||||
const graphData = await this.getGraph();
|
||||
|
||||
const graphId = this.material.userData.graphId;
|
||||
|
||||
TSLGraphLoader.setGraph( graphId, graphData );
|
||||
|
||||
} else if ( type === 'uniforms-changed' ) {
|
||||
|
||||
this._updateUniforms( payload.uniforms );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async onMessage( event ) {
|
||||
|
||||
if ( event.origin !== this.editorOrigin ) return;
|
||||
if ( ! this._isEditorMessage( event.data ) ) return;
|
||||
|
||||
const msg = event.data;
|
||||
|
||||
if ( msg.requestId && msg.type.startsWith( 'tsl:response:' ) ) {
|
||||
|
||||
const waiter = this._pending.get( msg.requestId );
|
||||
if ( ! waiter ) return;
|
||||
if ( msg.type !== waiter.expectedType ) return;
|
||||
|
||||
this._pending.delete( msg.requestId );
|
||||
window.clearTimeout( waiter.timer );
|
||||
|
||||
if ( msg.error ) waiter.reject( new Error( msg.error ) );
|
||||
else waiter.resolve( msg.payload );
|
||||
|
||||
this.onResponse( msg.type.substring( 'tsl:response:'.length ), msg.payload );
|
||||
|
||||
} else if ( msg.type.startsWith( 'tsl:event:' ) ) {
|
||||
|
||||
this.onEvent( msg.type.substring( 'tsl:event:'.length ), msg.payload );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async _setMaterial( material ) {
|
||||
|
||||
if ( ! material ) {
|
||||
|
||||
this.material = null;
|
||||
this.materialDefault = null;
|
||||
this.uniforms = null;
|
||||
|
||||
await this.command( 'clear-graph' );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( material.isNodeMaterial !== true ) {
|
||||
|
||||
error( 'TSLGraphEditor: "Material" needs be a "NodeMaterial".' );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( material.userData.graphId === undefined ) {
|
||||
|
||||
if ( this.autoGraphId ) {
|
||||
|
||||
material.userData.graphId = material.name || 'id:' + material.id;
|
||||
|
||||
} else {
|
||||
|
||||
warn( 'TSLGraphEditor: "NodeMaterial" has no graphId. Set a "graphId" for the material in "material.userData.graphId".' );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let materialDefault = _refMaterials.get( material );
|
||||
|
||||
if ( materialDefault === undefined ) {
|
||||
|
||||
//materialDefault = material.clone();
|
||||
materialDefault = new material.constructor();
|
||||
materialDefault.userData = material.userData;
|
||||
|
||||
_refMaterials.set( material, materialDefault );
|
||||
|
||||
}
|
||||
|
||||
this.material = material;
|
||||
this.materialDefault = materialDefault;
|
||||
this.uniforms = null;
|
||||
|
||||
const graphData = TSLGraphLoader.getGraph( this.material.userData.graphId );
|
||||
|
||||
if ( graphData ) {
|
||||
|
||||
await this.loadGraph( graphData );
|
||||
|
||||
} else {
|
||||
|
||||
await this.command( 'clear-graph' );
|
||||
|
||||
await this.command( 'set-root-material', { materialType: this._getGraphType( this.material ) } );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_getGraphType( material ) {
|
||||
|
||||
if ( material.isMeshPhysicalNodeMaterial ) return 'material/physical';
|
||||
if ( material.isMeshStandardNodeMaterial ) return 'material/standard';
|
||||
if ( material.isMeshPhongNodeMaterial ) return 'material/phong';
|
||||
if ( material.isMeshBasicNodeMaterial ) return 'material/basic';
|
||||
if ( material.isSpriteNodeMaterial ) return 'material/sprite';
|
||||
|
||||
return 'material/node';
|
||||
|
||||
}
|
||||
|
||||
_showManagerModal() {
|
||||
|
||||
const overlay = document.createElement( 'div' );
|
||||
overlay.style.position = 'absolute';
|
||||
overlay.style.top = '0';
|
||||
overlay.style.left = '0';
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
|
||||
overlay.style.zIndex = '100';
|
||||
overlay.style.display = 'flex';
|
||||
overlay.style.justifyContent = 'center';
|
||||
overlay.style.alignItems = 'center';
|
||||
overlay.onclick = ( e ) => {
|
||||
|
||||
if ( e.target === overlay ) {
|
||||
|
||||
this.content.removeChild( overlay );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const modal = document.createElement( 'div' );
|
||||
modal.style.width = '80%';
|
||||
modal.style.maxWidth = '500px';
|
||||
modal.style.height = '400px';
|
||||
modal.style.backgroundColor = 'var(--profiler-bg, #1e1e24f5)';
|
||||
modal.style.border = '1px solid var(--profiler-border, #4a4a5a)';
|
||||
modal.style.borderRadius = '8px';
|
||||
modal.style.display = 'flex';
|
||||
modal.style.flexDirection = 'column';
|
||||
|
||||
const header = document.createElement( 'div' );
|
||||
header.style.padding = '15px';
|
||||
header.style.borderBottom = '1px solid var(--profiler-border, #4a4a5a)';
|
||||
header.style.display = 'flex';
|
||||
header.style.justifyContent = 'space-between';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.gap = '15px';
|
||||
|
||||
const filterInput = document.createElement( 'input' );
|
||||
filterInput.type = 'text';
|
||||
filterInput.className = 'console-filter-input';
|
||||
filterInput.placeholder = 'Filter...';
|
||||
filterInput.style.flex = '1';
|
||||
|
||||
const closeBtn = document.createElement( 'button' );
|
||||
closeBtn.innerHTML = '✕';
|
||||
closeBtn.style.background = 'transparent';
|
||||
closeBtn.style.border = 'none';
|
||||
closeBtn.style.color = 'var(--text-secondary, #9a9aab)';
|
||||
closeBtn.style.cursor = 'pointer';
|
||||
closeBtn.style.fontSize = '16px';
|
||||
closeBtn.onmouseover = () => closeBtn.style.color = 'var(--text-primary, #e0e0e0)';
|
||||
closeBtn.onmouseout = () => closeBtn.style.color = 'var(--text-secondary, #9a9aab)';
|
||||
closeBtn.onclick = () => this.content.removeChild( overlay );
|
||||
|
||||
header.appendChild( filterInput );
|
||||
header.appendChild( closeBtn );
|
||||
|
||||
const codes = this.getCodes();
|
||||
const materialIds = Object.keys( codes.materials || {} );
|
||||
|
||||
if ( materialIds.length === 0 ) {
|
||||
|
||||
const listContainer = document.createElement( 'div' );
|
||||
listContainer.style.padding = '10px';
|
||||
listContainer.style.flex = '1';
|
||||
|
||||
const emptyMsg = document.createElement( 'div' );
|
||||
emptyMsg.textContent = 'No saved materials found.';
|
||||
emptyMsg.style.color = 'var(--text-secondary, #9a9aab)';
|
||||
emptyMsg.style.padding = '10px';
|
||||
emptyMsg.style.textAlign = 'center';
|
||||
emptyMsg.style.fontFamily = 'var(--font-family, sans-serif)';
|
||||
emptyMsg.style.fontSize = '12px';
|
||||
listContainer.appendChild( emptyMsg );
|
||||
|
||||
modal.appendChild( header );
|
||||
modal.appendChild( listContainer );
|
||||
|
||||
} else {
|
||||
|
||||
const listHeaderContainer = document.createElement( 'div' );
|
||||
listHeaderContainer.style.display = 'grid';
|
||||
listHeaderContainer.style.gridTemplateColumns = '1fr 80px';
|
||||
listHeaderContainer.style.gap = '10px';
|
||||
listHeaderContainer.style.padding = '10px 15px 8px 15px';
|
||||
listHeaderContainer.style.borderBottom = '1px solid var(--profiler-border, #4a4a5a)';
|
||||
listHeaderContainer.style.backgroundColor = 'var(--profiler-bg, #1e1e24f5)';
|
||||
listHeaderContainer.style.fontFamily = 'var(--font-family, sans-serif)';
|
||||
listHeaderContainer.style.fontSize = '11px';
|
||||
listHeaderContainer.style.fontWeight = 'bold';
|
||||
listHeaderContainer.style.textTransform = 'uppercase';
|
||||
listHeaderContainer.style.letterSpacing = '0.5px';
|
||||
listHeaderContainer.style.color = 'var(--text-secondary, #9a9aab)';
|
||||
listHeaderContainer.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
|
||||
listHeaderContainer.style.zIndex = '1';
|
||||
|
||||
const col1 = document.createElement( 'div' );
|
||||
col1.textContent = 'Material Name / ID';
|
||||
const col2 = document.createElement( 'div' );
|
||||
col2.textContent = 'Action';
|
||||
col2.style.textAlign = 'right';
|
||||
|
||||
listHeaderContainer.appendChild( col1 );
|
||||
listHeaderContainer.appendChild( col2 );
|
||||
|
||||
const scrollWrapper = document.createElement( 'div' );
|
||||
scrollWrapper.style.flex = '1';
|
||||
scrollWrapper.style.overflowY = 'auto';
|
||||
scrollWrapper.style.padding = '0';
|
||||
|
||||
const rows = [];
|
||||
|
||||
for ( const id of materialIds ) {
|
||||
|
||||
const itemRow = document.createElement( 'div' );
|
||||
itemRow.style.display = 'grid';
|
||||
itemRow.style.gridTemplateColumns = '1fr 80px';
|
||||
itemRow.style.gap = '10px';
|
||||
itemRow.style.alignItems = 'center';
|
||||
itemRow.style.padding = '8px 15px';
|
||||
itemRow.style.borderBottom = '1px solid rgba(74, 74, 90, 0.4)';
|
||||
itemRow.onmouseover = () => itemRow.style.backgroundColor = 'rgba(255, 255, 255, 0.04)';
|
||||
itemRow.onmouseout = () => itemRow.style.backgroundColor = 'transparent';
|
||||
|
||||
const nameSpan = document.createElement( 'span' );
|
||||
const materialData = codes.materials[ id ];
|
||||
const materialName = materialData.name || id;
|
||||
nameSpan.textContent = materialName;
|
||||
nameSpan.style.fontFamily = 'var(--font-mono, monospace)';
|
||||
nameSpan.style.fontSize = '12px';
|
||||
nameSpan.style.color = 'var(--text-primary, #e0e0e0)';
|
||||
nameSpan.style.userSelect = 'all';
|
||||
nameSpan.style.overflow = 'hidden';
|
||||
nameSpan.style.textOverflow = 'ellipsis';
|
||||
nameSpan.style.whiteSpace = 'nowrap';
|
||||
|
||||
const actionContainer = document.createElement( 'div' );
|
||||
actionContainer.style.textAlign = 'right';
|
||||
|
||||
const removeBtn = document.createElement( 'button' );
|
||||
removeBtn.textContent = 'Remove';
|
||||
removeBtn.style.background = 'rgba(244, 67, 54, 0.1)';
|
||||
removeBtn.style.border = '1px solid var(--color-red, #f44336)';
|
||||
removeBtn.style.color = 'var(--color-red, #f44336)';
|
||||
removeBtn.style.borderRadius = '4px';
|
||||
removeBtn.style.padding = '4px 8px';
|
||||
removeBtn.style.cursor = 'pointer';
|
||||
removeBtn.style.fontSize = '11px';
|
||||
removeBtn.onmouseover = () => removeBtn.style.background = 'rgba(244, 67, 54, 0.2)';
|
||||
removeBtn.onmouseout = () => removeBtn.style.background = 'rgba(244, 67, 54, 0.1)';
|
||||
|
||||
actionContainer.appendChild( removeBtn );
|
||||
|
||||
itemRow.appendChild( nameSpan );
|
||||
itemRow.appendChild( actionContainer );
|
||||
|
||||
scrollWrapper.appendChild( itemRow );
|
||||
|
||||
rows.push( { element: itemRow, text: materialName.toLowerCase() } );
|
||||
|
||||
removeBtn.onclick = async () => {
|
||||
|
||||
delete codes.materials[ id ];
|
||||
TSLGraphLoader.setCodes( codes );
|
||||
TSLGraphLoader.deleteGraph( id );
|
||||
scrollWrapper.removeChild( itemRow );
|
||||
|
||||
const index = rows.findIndex( r => r.element === itemRow );
|
||||
if ( index > - 1 ) rows.splice( index, 1 );
|
||||
|
||||
if ( rows.length === 0 ) {
|
||||
|
||||
modal.removeChild( listHeaderContainer );
|
||||
modal.removeChild( scrollWrapper );
|
||||
|
||||
const listContainer = document.createElement( 'div' );
|
||||
listContainer.style.padding = '10px';
|
||||
listContainer.style.flex = '1';
|
||||
|
||||
const emptyMsg = document.createElement( 'div' );
|
||||
emptyMsg.textContent = 'No saved materials found.';
|
||||
emptyMsg.style.color = 'var(--text-secondary, #9a9aab)';
|
||||
emptyMsg.style.padding = '10px';
|
||||
emptyMsg.style.textAlign = 'center';
|
||||
emptyMsg.style.fontFamily = 'var(--font-family, sans-serif)';
|
||||
emptyMsg.style.fontSize = '12px';
|
||||
|
||||
listContainer.appendChild( emptyMsg );
|
||||
modal.appendChild( listContainer );
|
||||
|
||||
}
|
||||
|
||||
_refMaterials.delete( this.material );
|
||||
|
||||
if ( this.material && this.material.userData.graphId === id ) {
|
||||
|
||||
this.restoreMaterial( this.material );
|
||||
|
||||
await this.setMaterial( null );
|
||||
|
||||
}
|
||||
|
||||
this.dispatchEvent( { type: 'remove', graphId: id } );
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
filterInput.addEventListener( 'input', ( e ) => {
|
||||
|
||||
const term = e.target.value.toLowerCase();
|
||||
for ( const row of rows ) {
|
||||
|
||||
row.element.style.display = row.text.includes( term ) ? 'grid' : 'none';
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
modal.appendChild( header );
|
||||
modal.appendChild( listHeaderContainer );
|
||||
modal.appendChild( scrollWrapper );
|
||||
|
||||
}
|
||||
|
||||
overlay.appendChild( modal );
|
||||
|
||||
this.content.appendChild( overlay );
|
||||
|
||||
}
|
||||
|
||||
_exportData() {
|
||||
|
||||
const codes = this.getCodes();
|
||||
const materialIds = Object.keys( codes.materials || {} );
|
||||
|
||||
const exportPayload = {
|
||||
codes: codes,
|
||||
graphs: {}
|
||||
};
|
||||
|
||||
for ( const id of materialIds ) {
|
||||
|
||||
const graphData = TSLGraphLoader.getGraph( id );
|
||||
|
||||
if ( graphData ) {
|
||||
|
||||
exportPayload.graphs[ id ] = graphData;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent( JSON.stringify( exportPayload, null, '\t' ) );
|
||||
const downloadAnchorNode = document.createElement( 'a' );
|
||||
downloadAnchorNode.setAttribute( 'href', dataStr );
|
||||
downloadAnchorNode.setAttribute( 'download', 'tsl-graphs.json' );
|
||||
document.body.appendChild( downloadAnchorNode );
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
|
||||
}
|
||||
|
||||
_importData() {
|
||||
|
||||
const fileInput = document.createElement( 'input' );
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.json';
|
||||
|
||||
fileInput.onchange = e => {
|
||||
|
||||
const file = e.target.files[ 0 ];
|
||||
|
||||
if ( ! file ) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async ( event ) => {
|
||||
|
||||
try {
|
||||
|
||||
const importedData = TSLGraphLoader.setGraphs( JSON.parse( event.target.result ) );
|
||||
|
||||
this._codeData = importedData.codes;
|
||||
|
||||
// Reload visual state if we have a material open
|
||||
if ( this.material ) {
|
||||
|
||||
// refresh material
|
||||
await this._setMaterial( this.material );
|
||||
|
||||
}
|
||||
|
||||
} catch ( err ) {
|
||||
|
||||
error( 'TSLGraphEditor: Failed to parse or load imported JSON.', err );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
reader.readAsText( file );
|
||||
|
||||
};
|
||||
|
||||
fileInput.click();
|
||||
|
||||
}
|
||||
|
||||
getCodes() {
|
||||
|
||||
if ( this._codeData === null ) {
|
||||
|
||||
this._codeData = TSLGraphLoader.getCodes();
|
||||
|
||||
}
|
||||
|
||||
return this._codeData;
|
||||
|
||||
}
|
||||
|
||||
_saveCode() {
|
||||
|
||||
const graphId = this.material.userData.graphId;
|
||||
|
||||
clearTimeout( this._codeSaveTimeout );
|
||||
|
||||
this._codeSaveTimeout = setTimeout( async () => {
|
||||
|
||||
if ( this.material === null || graphId !== this.material.userData.graphId ) return;
|
||||
|
||||
const codes = this.getCodes();
|
||||
const codeData = await this.getCode();
|
||||
|
||||
codes.materials[ graphId ] = codeData.material;
|
||||
|
||||
TSLGraphLoader.setCodes( codes );
|
||||
|
||||
}, 1000 );
|
||||
|
||||
}
|
||||
|
||||
_restoreMaterial() {
|
||||
|
||||
this.material.copy( this.materialDefault );
|
||||
|
||||
}
|
||||
|
||||
async _updateMaterial() {
|
||||
|
||||
this._restoreMaterial();
|
||||
|
||||
const applyNodes = await this.getTSLFunction();
|
||||
|
||||
const { uniforms } = applyNodes( this.material );
|
||||
|
||||
this.uniforms = uniforms;
|
||||
this.material.needsUpdate = true;
|
||||
|
||||
this._saveCode();
|
||||
|
||||
}
|
||||
|
||||
_updateUniforms( uniforms ) {
|
||||
|
||||
if ( this.uniforms === null ) return;
|
||||
|
||||
for ( const uniform of uniforms ) {
|
||||
|
||||
const uniformNode = this.uniforms[ uniform.name ];
|
||||
const uniformType = uniform.uniformType;
|
||||
|
||||
const value = uniform.value;
|
||||
|
||||
if ( uniformType.startsWith( 'vec' ) ) {
|
||||
|
||||
uniformNode.value.fromArray( value );
|
||||
|
||||
} else if ( uniformType.startsWith( 'color' ) ) {
|
||||
|
||||
uniformNode.value.setHex( parseInt( value.slice( 1 ), 16 ) );
|
||||
|
||||
} else {
|
||||
|
||||
uniformNode.value = value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._saveCode();
|
||||
|
||||
}
|
||||
|
||||
_isEditorMessage( value ) {
|
||||
|
||||
if ( ! value || typeof value !== 'object' ) return false;
|
||||
return value.source === EDITOR_SOURCE && typeof value.type === 'string';
|
||||
|
||||
}
|
||||
|
||||
_makeRequestId() {
|
||||
|
||||
return `${Date.now()}-${Math.random().toString( 36 ).slice( 2, 10 )}`;
|
||||
|
||||
}
|
||||
|
||||
_post( message ) {
|
||||
|
||||
if ( this.iframe.contentWindow ) {
|
||||
|
||||
this.iframe.contentWindow.postMessage( message, this.editorOrigin );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TSLGraphEditor;
|
||||
281
node_modules/three/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js
generated
vendored
Normal file
281
node_modules/three/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
import { FileLoader, error } from 'three';
|
||||
|
||||
import * as THREE from 'three';
|
||||
import * as TSL from 'three/tsl';
|
||||
|
||||
const _library = {
|
||||
'three/tsl': { ...TSL }
|
||||
};
|
||||
|
||||
const STORAGE_PREFIX = 'tsl-graph';
|
||||
const STORAGE_CODE = 'tsl-graph-code';
|
||||
|
||||
function _storageKey( graphId ) {
|
||||
|
||||
return `${STORAGE_PREFIX}:${graphId}`;
|
||||
|
||||
}
|
||||
|
||||
class TSLGraphLoaderApplier {
|
||||
|
||||
constructor( tslGraphFns ) {
|
||||
|
||||
this.tslGraphFns = tslGraphFns;
|
||||
|
||||
}
|
||||
|
||||
apply( scene ) {
|
||||
|
||||
const tslGraphFns = this.tslGraphFns;
|
||||
|
||||
scene.traverse( ( object ) => {
|
||||
|
||||
if ( object.material && object.material.userData.graphId ) {
|
||||
|
||||
if ( tslGraphFns[ object.material.userData.graphId ] ) {
|
||||
|
||||
tslGraphFns[ object.material.userData.graphId ]( object.material );
|
||||
|
||||
object.material.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class TSLGraphLoader extends FileLoader {
|
||||
|
||||
constructor( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
}
|
||||
|
||||
load( url, onLoad, onProgress, onError ) {
|
||||
|
||||
super.load( url, ( text ) => {
|
||||
|
||||
let json;
|
||||
|
||||
try {
|
||||
|
||||
json = JSON.parse( text );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
if ( onError ) onError( e );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
const applier = this.parse( json );
|
||||
|
||||
if ( onLoad ) onLoad( applier );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
}
|
||||
|
||||
parseMaterial( json ) {
|
||||
|
||||
const baseFn = 'tslGraph';
|
||||
|
||||
const imports = {};
|
||||
const materials = [ this._generateMaterialCode( json, baseFn, imports ) ];
|
||||
const code = this._generateCode( materials, imports );
|
||||
|
||||
const tslFunction = new Function( code )()( THREE, imports );
|
||||
|
||||
return tslFunction;
|
||||
|
||||
}
|
||||
|
||||
parseMaterials( json ) {
|
||||
|
||||
const imports = {};
|
||||
const materials = [];
|
||||
|
||||
for ( const [ name, material ] of Object.entries( json ) ) {
|
||||
|
||||
materials.push( this._generateMaterialCode( material, name, imports ) );
|
||||
|
||||
}
|
||||
|
||||
const code = this._generateCode( materials, imports );
|
||||
|
||||
const tslFunction = new Function( code )()( THREE, imports );
|
||||
|
||||
return tslFunction;
|
||||
|
||||
}
|
||||
|
||||
parse( json ) {
|
||||
|
||||
let result;
|
||||
|
||||
if ( json.material && json.material.code ) {
|
||||
|
||||
result = this.parseMaterial( json.material );
|
||||
|
||||
} else if ( json.materials ) {
|
||||
|
||||
result = this.parseMaterials( json.materials );
|
||||
|
||||
} else if ( json.codes && json.graphs ) {
|
||||
|
||||
result = this.parseMaterials( json.codes.materials );
|
||||
|
||||
TSLGraphLoader.setGraphs( json );
|
||||
|
||||
}
|
||||
|
||||
return new TSLGraphLoaderApplier( result );
|
||||
|
||||
}
|
||||
|
||||
_generateMaterialCode( json, name = 'tslGraph', imports = {} ) {
|
||||
|
||||
const code = json.code.replace( 'function tslGraph', `materials[ '${ name }' ] = function` ).replace( /\n|^/g, '\n\t' );
|
||||
|
||||
for ( const importData of json.imports ) {
|
||||
|
||||
if ( _library[ importData.from ] ) {
|
||||
|
||||
for ( const importName of importData.imports ) {
|
||||
|
||||
if ( _library[ importData.from ][ importName ] ) {
|
||||
|
||||
imports[ importName ] = _library[ importData.from ][ importName ];
|
||||
|
||||
} else {
|
||||
|
||||
error( `TSLGraph: Import ${ importName } not found in ${ importData.from }.` );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
error( `TSLGraph: Library ${ importData.from } not found.` );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
_generateCode( materials, imports ) {
|
||||
|
||||
const fnCode = `return ( THREE, { ${ Object.keys( imports ).join( ', ' ) } } ) => {\n\n\tconst materials = {};\n${ materials.join( '\n' ) }\n\n\treturn materials;\n\n}`;
|
||||
|
||||
return fnCode;
|
||||
|
||||
}
|
||||
|
||||
static get hasGraphs() {
|
||||
|
||||
return Object.keys( TSLGraphLoader.getCodes().materials ).length > 0;
|
||||
|
||||
}
|
||||
|
||||
static getCodes() {
|
||||
|
||||
const code = window.localStorage.getItem( STORAGE_CODE );
|
||||
|
||||
return code ? JSON.parse( code ) : { materials: {} };
|
||||
|
||||
}
|
||||
|
||||
static setCodes( codes ) {
|
||||
|
||||
window.localStorage.setItem( STORAGE_CODE, JSON.stringify( codes ) );
|
||||
|
||||
}
|
||||
|
||||
static setGraph( graphId, graphData ) {
|
||||
|
||||
window.localStorage.setItem( _storageKey( graphId ), JSON.stringify( graphData ) );
|
||||
|
||||
}
|
||||
|
||||
static getGraph( graphId ) {
|
||||
|
||||
const raw = window.localStorage.getItem( _storageKey( graphId ) );
|
||||
|
||||
if ( ! raw ) return null;
|
||||
|
||||
try {
|
||||
|
||||
return JSON.parse( raw );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
error( 'TSLGraph: Invalid graph JSON in localStorage, ignoring.', e );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static deleteGraph( graphId ) {
|
||||
|
||||
window.localStorage.removeItem( _storageKey( graphId ) );
|
||||
|
||||
}
|
||||
|
||||
static setGraphs( json ) {
|
||||
|
||||
if ( ! json.codes || ! json.graphs ) {
|
||||
|
||||
throw new Error( 'TSLGraph: Invalid import file structure.' );
|
||||
|
||||
}
|
||||
|
||||
TSLGraphLoader.clearGraphs();
|
||||
|
||||
// Save imported graph visualizations
|
||||
for ( const [ id, graphData ] of Object.entries( json.graphs ) ) {
|
||||
|
||||
TSLGraphLoader.setGraph( id, graphData );
|
||||
|
||||
}
|
||||
|
||||
// Fully overwrite codes
|
||||
TSLGraphLoader.setCodes( json.codes );
|
||||
|
||||
return json;
|
||||
|
||||
}
|
||||
|
||||
static clearGraphs() {
|
||||
|
||||
const keysToRemove = [];
|
||||
for ( let i = 0; i < window.localStorage.length; i ++ ) {
|
||||
|
||||
const key = window.localStorage.key( i );
|
||||
if ( key.startsWith( STORAGE_PREFIX ) ) {
|
||||
|
||||
keysToRemove.push( key );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( const key of keysToRemove ) {
|
||||
|
||||
window.localStorage.removeItem( key );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
238
node_modules/three/examples/jsm/inspector/tabs/Console.js
generated
vendored
Normal file
238
node_modules/three/examples/jsm/inspector/tabs/Console.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
import { Tab } from '../ui/Tab.js';
|
||||
|
||||
class Console extends Tab {
|
||||
|
||||
constructor( options = {} ) {
|
||||
|
||||
super( 'Console', options );
|
||||
|
||||
this.filters = { info: true, warn: true, error: true };
|
||||
this.filterText = '';
|
||||
|
||||
this.buildHeader();
|
||||
|
||||
this.logContainer = document.createElement( 'div' );
|
||||
this.logContainer.id = 'console-log';
|
||||
this.content.appendChild( this.logContainer );
|
||||
|
||||
}
|
||||
|
||||
buildHeader() {
|
||||
|
||||
const header = document.createElement( 'div' );
|
||||
header.className = 'console-header';
|
||||
|
||||
const filterInput = document.createElement( 'input' );
|
||||
filterInput.type = 'text';
|
||||
filterInput.className = 'console-filter-input';
|
||||
filterInput.placeholder = 'Filter...';
|
||||
filterInput.addEventListener( 'input', ( e ) => {
|
||||
|
||||
this.filterText = e.target.value.toLowerCase();
|
||||
this.applyFilters();
|
||||
|
||||
} );
|
||||
|
||||
const copyButton = document.createElement( 'button' );
|
||||
copyButton.className = 'console-copy-button';
|
||||
copyButton.title = 'Copy all';
|
||||
copyButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
|
||||
copyButton.addEventListener( 'click', () => this.copyAll( copyButton ) );
|
||||
|
||||
const buttonsGroup = document.createElement( 'div' );
|
||||
buttonsGroup.className = 'console-buttons-group';
|
||||
|
||||
Object.keys( this.filters ).forEach( type => {
|
||||
|
||||
const label = document.createElement( 'label' );
|
||||
label.className = 'custom-checkbox';
|
||||
label.style.color = `var(--${type === 'info' ? 'text-primary' : 'color-' + ( type === 'warn' ? 'yellow' : 'red' )})`;
|
||||
|
||||
const checkbox = document.createElement( 'input' );
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = this.filters[ type ];
|
||||
checkbox.dataset.type = type;
|
||||
|
||||
const checkmark = document.createElement( 'span' );
|
||||
checkmark.className = 'checkmark';
|
||||
|
||||
label.appendChild( checkbox );
|
||||
label.appendChild( checkmark );
|
||||
label.append( type.charAt( 0 ).toUpperCase() + type.slice( 1 ) );
|
||||
buttonsGroup.appendChild( label );
|
||||
|
||||
} );
|
||||
|
||||
buttonsGroup.addEventListener( 'change', ( e ) => {
|
||||
|
||||
const type = e.target.dataset.type;
|
||||
if ( type in this.filters ) {
|
||||
|
||||
this.filters[ type ] = e.target.checked;
|
||||
this.applyFilters();
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
buttonsGroup.appendChild( copyButton );
|
||||
|
||||
header.appendChild( filterInput );
|
||||
header.appendChild( buttonsGroup );
|
||||
this.content.appendChild( header );
|
||||
|
||||
}
|
||||
|
||||
applyFilters() {
|
||||
|
||||
const messages = this.logContainer.querySelectorAll( '.log-message' );
|
||||
messages.forEach( msg => {
|
||||
|
||||
const type = msg.dataset.type;
|
||||
const text = msg.dataset.rawText.toLowerCase();
|
||||
|
||||
const showByType = this.filters[ type ];
|
||||
const showByText = text.includes( this.filterText );
|
||||
|
||||
msg.classList.toggle( 'hidden', ! ( showByType && showByText ) );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
copyAll( button ) {
|
||||
|
||||
const win = this.logContainer.ownerDocument.defaultView;
|
||||
const selection = win.getSelection();
|
||||
const selectedText = selection.toString();
|
||||
const textInConsole = selectedText && this.logContainer.contains( selection.anchorNode );
|
||||
|
||||
let text;
|
||||
if ( textInConsole ) {
|
||||
|
||||
text = selectedText;
|
||||
|
||||
} else {
|
||||
|
||||
const messages = this.logContainer.querySelectorAll( '.log-message:not(.hidden)' );
|
||||
text = Array.from( messages ).map( msg => msg.dataset.rawText ).join( '\n' );
|
||||
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText( text );
|
||||
|
||||
button.classList.add( 'copied' );
|
||||
setTimeout( () => button.classList.remove( 'copied' ), 350 );
|
||||
|
||||
}
|
||||
|
||||
_getIcon( type, subType ) {
|
||||
|
||||
let icon;
|
||||
|
||||
if ( subType === 'tip' ) {
|
||||
|
||||
icon = '💭';
|
||||
|
||||
} else if ( subType === 'tsl' ) {
|
||||
|
||||
icon = '✨';
|
||||
|
||||
} else if ( subType === 'webgpurenderer' ) {
|
||||
|
||||
icon = '🎨';
|
||||
|
||||
} else if ( type === 'warn' ) {
|
||||
|
||||
icon = '⚠️';
|
||||
|
||||
} else if ( type === 'error' ) {
|
||||
|
||||
icon = '🔴';
|
||||
|
||||
} else if ( type === 'info' ) {
|
||||
|
||||
icon = 'ℹ️';
|
||||
|
||||
}
|
||||
|
||||
return icon;
|
||||
|
||||
}
|
||||
|
||||
_formatMessage( type, text ) {
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const prefixMatch = text.match( /^([\w\.]+:\s)/ );
|
||||
let content = text;
|
||||
|
||||
if ( prefixMatch ) {
|
||||
|
||||
const fullPrefix = prefixMatch[ 0 ];
|
||||
const parts = fullPrefix.slice( 0, - 2 ).split( '.' );
|
||||
const shortPrefix = ( parts.length > 1 ? parts[ parts.length - 1 ] : parts[ 0 ] ) + ':';
|
||||
|
||||
const icon = this._getIcon( type, shortPrefix.split( ':' )[ 0 ].toLowerCase() );
|
||||
|
||||
fragment.appendChild( document.createTextNode( icon + ' ' ) );
|
||||
|
||||
const prefixSpan = document.createElement( 'span' );
|
||||
prefixSpan.className = 'log-prefix';
|
||||
prefixSpan.textContent = shortPrefix;
|
||||
fragment.appendChild( prefixSpan );
|
||||
content = text.substring( fullPrefix.length );
|
||||
|
||||
}
|
||||
|
||||
const parts = content.split( /(".*?"|'.*?'|`.*?`)/g ).map( p => p.trim() ).filter( Boolean );
|
||||
|
||||
parts.forEach( ( part, index ) => {
|
||||
|
||||
if ( /^("|'|`)/.test( part ) ) {
|
||||
|
||||
const codeSpan = document.createElement( 'span' );
|
||||
codeSpan.className = 'log-code';
|
||||
codeSpan.textContent = part.slice( 1, - 1 );
|
||||
fragment.appendChild( codeSpan );
|
||||
|
||||
} else {
|
||||
|
||||
if ( index > 0 ) part = ' ' + part; // add space before parts except the first
|
||||
if ( index < parts.length - 1 ) part += ' '; // add space between parts
|
||||
|
||||
fragment.appendChild( document.createTextNode( part ) );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return fragment;
|
||||
|
||||
}
|
||||
|
||||
addMessage( type, text ) {
|
||||
|
||||
const msg = document.createElement( 'div' );
|
||||
msg.className = `log-message ${type}`;
|
||||
msg.dataset.type = type;
|
||||
msg.dataset.rawText = text;
|
||||
|
||||
msg.appendChild( this._formatMessage( type, text ) );
|
||||
|
||||
const showByType = this.filters[ type ];
|
||||
const showByText = text.toLowerCase().includes( this.filterText );
|
||||
msg.classList.toggle( 'hidden', ! ( showByType && showByText ) );
|
||||
|
||||
this.logContainer.appendChild( msg );
|
||||
this.logContainer.scrollTop = this.logContainer.scrollHeight;
|
||||
if ( this.logContainer.children.length > 200 ) {
|
||||
|
||||
this.logContainer.removeChild( this.logContainer.firstChild );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Console };
|
||||
128
node_modules/three/examples/jsm/inspector/tabs/Memory.js
generated
vendored
Normal file
128
node_modules/three/examples/jsm/inspector/tabs/Memory.js
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Tab } from '../ui/Tab.js';
|
||||
import { List } from '../ui/List.js';
|
||||
import { Graph } from '../ui/Graph.js';
|
||||
import { Item } from '../ui/Item.js';
|
||||
import { createValueSpan, setText, formatBytes } from '../ui/utils.js';
|
||||
|
||||
class Memory extends Tab {
|
||||
|
||||
constructor( options = {} ) {
|
||||
|
||||
super( 'Memory', options );
|
||||
|
||||
const memoryList = new List( 'Name', 'Count', 'Size' );
|
||||
memoryList.setGridStyle( 'minmax(200px, 2fr) 60px 100px' );
|
||||
memoryList.domElement.style.minWidth = '300px';
|
||||
|
||||
const scrollWrapper = document.createElement( 'div' );
|
||||
scrollWrapper.className = 'list-scroll-wrapper';
|
||||
scrollWrapper.appendChild( memoryList.domElement );
|
||||
this.content.appendChild( scrollWrapper );
|
||||
|
||||
// graph
|
||||
|
||||
const graphContainer = document.createElement( 'div' );
|
||||
graphContainer.className = 'graph-container';
|
||||
|
||||
const graph = new Graph();
|
||||
graph.addLine( 'total', 'var( --color-yellow )' );
|
||||
graphContainer.append( graph.domElement );
|
||||
|
||||
// stats
|
||||
|
||||
const graphStats = new Item( 'Graph Stats', '', '' );
|
||||
memoryList.add( graphStats );
|
||||
|
||||
const graphItem = new Item( graphContainer );
|
||||
graphItem.itemRow.childNodes[ 0 ].style.gridColumn = '1 / -1';
|
||||
graphStats.add( graphItem );
|
||||
|
||||
// info
|
||||
|
||||
this.memoryStats = new Item( 'Renderer Info', '', createValueSpan() );
|
||||
this.memoryStats.domElement.firstChild.classList.add( 'no-hover' );
|
||||
memoryList.add( this.memoryStats );
|
||||
|
||||
this.attributes = new Item( 'Attributes', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.attributes );
|
||||
|
||||
this.geometries = new Item( 'Geometries', createValueSpan(), 'N/A' );
|
||||
this.memoryStats.add( this.geometries );
|
||||
|
||||
this.indexAttributes = new Item( 'Index Attributes', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.indexAttributes );
|
||||
|
||||
this.indirectStorageAttributes = new Item( 'Indirect Storage Attributes', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.indirectStorageAttributes );
|
||||
|
||||
this.programs = new Item( 'Programs', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.programs );
|
||||
|
||||
this.readbackBuffers = new Item( 'Readback Buffers', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.readbackBuffers );
|
||||
|
||||
this.renderTargets = new Item( 'Render Targets', createValueSpan(), 'N/A' );
|
||||
this.memoryStats.add( this.renderTargets );
|
||||
|
||||
this.storageAttributes = new Item( 'Storage Attributes', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.storageAttributes );
|
||||
|
||||
this.textures = new Item( 'Textures', createValueSpan(), createValueSpan() );
|
||||
this.memoryStats.add( this.textures );
|
||||
|
||||
this.graph = graph;
|
||||
|
||||
}
|
||||
|
||||
updateGraph( inspector ) {
|
||||
|
||||
const renderer = inspector.getRenderer();
|
||||
if ( ! renderer ) return;
|
||||
|
||||
const memory = renderer.info.memory;
|
||||
|
||||
this.graph.addPoint( 'total', memory.total );
|
||||
|
||||
if ( this.graph.limit === 0 ) this.graph.limit = 1;
|
||||
|
||||
this.graph.update();
|
||||
|
||||
}
|
||||
|
||||
updateText( inspector ) {
|
||||
|
||||
const renderer = inspector.getRenderer();
|
||||
if ( ! renderer ) return;
|
||||
|
||||
const memory = renderer.info.memory;
|
||||
|
||||
setText( this.memoryStats.data[ 2 ], formatBytes( memory.total ) );
|
||||
|
||||
setText( this.attributes.data[ 1 ], memory.attributes.toString() );
|
||||
setText( this.attributes.data[ 2 ], formatBytes( memory.attributesSize ) );
|
||||
setText( this.geometries.data[ 1 ], memory.geometries.toString() );
|
||||
|
||||
setText( this.indexAttributes.data[ 1 ], memory.indexAttributes.toString() );
|
||||
setText( this.indexAttributes.data[ 2 ], formatBytes( memory.indexAttributesSize ) );
|
||||
|
||||
setText( this.indirectStorageAttributes.data[ 1 ], memory.indirectStorageAttributes.toString() );
|
||||
setText( this.indirectStorageAttributes.data[ 2 ], formatBytes( memory.indirectStorageAttributesSize ) );
|
||||
|
||||
setText( this.programs.data[ 1 ], memory.programs.toString() );
|
||||
setText( this.programs.data[ 2 ], formatBytes( memory.programsSize ) );
|
||||
|
||||
setText( this.readbackBuffers.data[ 1 ], memory.readbackBuffers.toString() );
|
||||
setText( this.readbackBuffers.data[ 2 ], formatBytes( memory.readbackBuffersSize ) );
|
||||
|
||||
setText( this.renderTargets.data[ 1 ], memory.renderTargets.toString() );
|
||||
|
||||
setText( this.storageAttributes.data[ 1 ], memory.storageAttributes.toString() );
|
||||
setText( this.storageAttributes.data[ 2 ], formatBytes( memory.storageAttributesSize ) );
|
||||
setText( this.textures.data[ 1 ], memory.textures.toString() );
|
||||
setText( this.textures.data[ 2 ], formatBytes( memory.texturesSize ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Memory };
|
||||
380
node_modules/three/examples/jsm/inspector/tabs/Parameters.js
generated
vendored
Normal file
380
node_modules/three/examples/jsm/inspector/tabs/Parameters.js
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
import { Tab } from '../ui/Tab.js';
|
||||
import { List } from '../ui/List.js';
|
||||
import { Item } from '../ui/Item.js';
|
||||
import { createValueSpan } from '../ui/utils.js';
|
||||
import { ValueString, ValueNumber, ValueSlider, ValueSelect, ValueCheckbox, ValueColor, ValueButton } from '../ui/Values.js';
|
||||
|
||||
class ParametersGroup {
|
||||
|
||||
constructor( parameters, name ) {
|
||||
|
||||
this.parameters = parameters;
|
||||
this.name = name;
|
||||
|
||||
this.paramList = new Item( name );
|
||||
|
||||
this.objects = [];
|
||||
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
this.paramList.close();
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
add( object, property, ...params ) {
|
||||
|
||||
const value = object[ property ];
|
||||
const type = typeof value;
|
||||
|
||||
let item = null;
|
||||
|
||||
if ( typeof params[ 0 ] === 'object' ) {
|
||||
|
||||
item = this.addSelect( object, property, params[ 0 ] );
|
||||
|
||||
} else if ( type === 'number' ) {
|
||||
|
||||
if ( params.length >= 2 ) {
|
||||
|
||||
item = this.addSlider( object, property, ...params );
|
||||
|
||||
} else {
|
||||
|
||||
item = this.addNumber( object, property, ...params );
|
||||
|
||||
}
|
||||
|
||||
} else if ( type === 'boolean' ) {
|
||||
|
||||
item = this.addBoolean( object, property );
|
||||
|
||||
} else if ( type === 'string' ) {
|
||||
|
||||
item = this.addString( object, property );
|
||||
|
||||
} else if ( type === 'function' ) {
|
||||
|
||||
item = this.addButton( object, property, ...params );
|
||||
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
_addParameter( object, property, editor, subItem ) {
|
||||
|
||||
editor.name = ( name ) => {
|
||||
|
||||
subItem.data[ 0 ].textContent = name;
|
||||
|
||||
return editor;
|
||||
|
||||
};
|
||||
|
||||
editor.listen = () => {
|
||||
|
||||
const update = () => {
|
||||
|
||||
const value = editor.getValue();
|
||||
const propertyValue = object[ property ];
|
||||
|
||||
if ( value !== propertyValue ) {
|
||||
|
||||
editor.setValue( propertyValue );
|
||||
|
||||
}
|
||||
|
||||
requestAnimationFrame( update );
|
||||
|
||||
};
|
||||
|
||||
requestAnimationFrame( update );
|
||||
|
||||
return editor;
|
||||
|
||||
};
|
||||
|
||||
|
||||
this._registerParameter( object, property, editor, subItem );
|
||||
|
||||
}
|
||||
|
||||
_registerParameter( object, property, editor, subItem ) {
|
||||
|
||||
this.objects.push( { object: object, key: property, editor: editor, subItem: subItem } );
|
||||
|
||||
}
|
||||
|
||||
addString( object, property ) {
|
||||
|
||||
const value = object[ property ];
|
||||
|
||||
const editor = new ValueString( { value } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const description = createValueSpan();
|
||||
description.textContent = property;
|
||||
|
||||
const subItem = new Item( description, editor.domElement );
|
||||
this.paramList.add( subItem );
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
itemRow.classList.add( 'actionable' );
|
||||
|
||||
// extend object property
|
||||
|
||||
this._addParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
addFolder( name ) {
|
||||
|
||||
const group = new ParametersGroup( this.parameters, name );
|
||||
|
||||
this.paramList.add( group.paramList );
|
||||
|
||||
return group;
|
||||
|
||||
}
|
||||
|
||||
addBoolean( object, property ) {
|
||||
|
||||
const value = object[ property ];
|
||||
|
||||
const editor = new ValueCheckbox( { value } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const description = createValueSpan();
|
||||
description.textContent = property;
|
||||
|
||||
const subItem = new Item( description, editor.domElement );
|
||||
this.paramList.add( subItem );
|
||||
|
||||
// extends logic to toggle checkbox when clicking on the row
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
|
||||
itemRow.classList.add( 'actionable' );
|
||||
itemRow.addEventListener( 'click', ( e ) => {
|
||||
|
||||
if ( e.target.closest( 'label' ) ) return;
|
||||
|
||||
const checkbox = itemRow.querySelector( 'input[type="checkbox"]' );
|
||||
|
||||
if ( checkbox ) {
|
||||
|
||||
checkbox.checked = ! checkbox.checked;
|
||||
checkbox.dispatchEvent( new Event( 'change' ) );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
// extend object property
|
||||
|
||||
this._addParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
addSelect( object, property, options ) {
|
||||
|
||||
const value = object[ property ];
|
||||
|
||||
const editor = new ValueSelect( { options, value } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const description = createValueSpan();
|
||||
description.textContent = property;
|
||||
|
||||
const subItem = new Item( description, editor.domElement );
|
||||
this.paramList.add( subItem );
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
itemRow.classList.add( 'actionable' );
|
||||
|
||||
// extend object property
|
||||
|
||||
this._addParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
addColor( object, property ) {
|
||||
|
||||
const value = object[ property ];
|
||||
|
||||
const editor = new ValueColor( { value } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const description = createValueSpan();
|
||||
description.textContent = property;
|
||||
|
||||
const subItem = new Item( description, editor.domElement );
|
||||
this.paramList.add( subItem );
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
itemRow.classList.add( 'actionable' );
|
||||
|
||||
// extend object property
|
||||
|
||||
this._addParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
addSlider( object, property, min = 0, max = 1, step = 0.01 ) {
|
||||
|
||||
const value = object[ property ];
|
||||
|
||||
const editor = new ValueSlider( { value, min, max, step } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const description = createValueSpan();
|
||||
description.textContent = property;
|
||||
|
||||
const subItem = new Item( description, editor.domElement );
|
||||
this.paramList.add( subItem );
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
itemRow.classList.add( 'actionable' );
|
||||
|
||||
// extend object property
|
||||
|
||||
this._addParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
addNumber( object, property, ...params ) {
|
||||
|
||||
const value = object[ property ];
|
||||
const [ min, max ] = params;
|
||||
|
||||
const editor = new ValueNumber( { value, min, max } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const description = createValueSpan();
|
||||
description.textContent = property;
|
||||
|
||||
const subItem = new Item( description, editor.domElement );
|
||||
this.paramList.add( subItem );
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
itemRow.classList.add( 'actionable' );
|
||||
|
||||
// extend object property
|
||||
|
||||
this._addParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
addButton( object, property ) {
|
||||
|
||||
const value = object[ property ];
|
||||
|
||||
const editor = new ValueButton( { text: property, value } );
|
||||
editor.addEventListener( 'change', ( { value } ) => {
|
||||
|
||||
object[ property ] = value;
|
||||
|
||||
} );
|
||||
|
||||
const subItem = new Item( editor.domElement );
|
||||
subItem.itemRow.childNodes[ 0 ].style.gridColumn = '1 / -1';
|
||||
this.paramList.add( subItem );
|
||||
|
||||
const itemRow = subItem.domElement.firstChild;
|
||||
itemRow.classList.add( 'actionable' );
|
||||
|
||||
// extend object property
|
||||
|
||||
editor.name = ( name ) => {
|
||||
|
||||
editor.domElement.childNodes[ 0 ].textContent = name;
|
||||
|
||||
return editor;
|
||||
|
||||
};
|
||||
|
||||
this._registerParameter( object, property, editor, subItem );
|
||||
|
||||
return editor;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Parameters extends Tab {
|
||||
|
||||
constructor( options = {} ) {
|
||||
|
||||
super( options.name || 'Parameters', options );
|
||||
|
||||
const paramList = new List( 'Property', 'Value' );
|
||||
paramList.domElement.classList.add( 'parameters' );
|
||||
paramList.setGridStyle( '.5fr 1fr' );
|
||||
paramList.domElement.style.minWidth = '300px';
|
||||
|
||||
const scrollWrapper = document.createElement( 'div' );
|
||||
scrollWrapper.className = 'list-scroll-wrapper';
|
||||
scrollWrapper.appendChild( paramList.domElement );
|
||||
this.content.appendChild( scrollWrapper );
|
||||
|
||||
this.paramList = paramList;
|
||||
|
||||
this.groups = [];
|
||||
|
||||
}
|
||||
|
||||
createGroup( name ) {
|
||||
|
||||
const group = new ParametersGroup( this, name );
|
||||
|
||||
this.paramList.add( group.paramList );
|
||||
this.groups.push( group );
|
||||
|
||||
return group;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Parameters };
|
||||
268
node_modules/three/examples/jsm/inspector/tabs/Performance.js
generated
vendored
Normal file
268
node_modules/three/examples/jsm/inspector/tabs/Performance.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
import { Tab } from '../ui/Tab.js';
|
||||
import { List } from '../ui/List.js';
|
||||
import { Graph } from '../ui/Graph.js';
|
||||
import { Item } from '../ui/Item.js';
|
||||
import { createValueSpan, setText } from '../ui/utils.js';
|
||||
|
||||
class Performance extends Tab {
|
||||
|
||||
constructor( options = {} ) {
|
||||
|
||||
super( 'Performance', options );
|
||||
|
||||
const perfList = new List( 'Name', 'CPU', 'GPU', 'Total' );
|
||||
perfList.setGridStyle( 'minmax(200px, 2fr) 80px 80px 80px' );
|
||||
perfList.domElement.style.minWidth = '600px';
|
||||
|
||||
const scrollWrapper = document.createElement( 'div' );
|
||||
scrollWrapper.className = 'list-scroll-wrapper';
|
||||
scrollWrapper.appendChild( perfList.domElement );
|
||||
this.content.appendChild( scrollWrapper );
|
||||
|
||||
//
|
||||
|
||||
const graphContainer = document.createElement( 'div' );
|
||||
graphContainer.className = 'graph-container';
|
||||
|
||||
const graph = new Graph();
|
||||
graph.addLine( 'fps', 'var( --color-fps )' );
|
||||
//graph.addLine( 'gpu', 'var( --color-yellow )' );
|
||||
graphContainer.append( graph.domElement );
|
||||
|
||||
//
|
||||
|
||||
/*
|
||||
const label = document.createElement( 'label' );
|
||||
label.className = 'custom-checkbox';
|
||||
|
||||
const checkbox = document.createElement( 'input' );
|
||||
checkbox.type = 'checkbox';
|
||||
|
||||
const checkmark = document.createElement( 'span' );
|
||||
checkmark.className = 'checkmark';
|
||||
|
||||
label.appendChild( checkbox );
|
||||
label.appendChild( checkmark );
|
||||
*/
|
||||
|
||||
const graphStats = new Item( 'Graph Stats', createValueSpan(), createValueSpan(), createValueSpan( 'graph-fps-counter' ) );
|
||||
perfList.add( graphStats );
|
||||
|
||||
const graphItem = new Item( graphContainer );
|
||||
graphItem.itemRow.childNodes[ 0 ].style.gridColumn = '1 / -1';
|
||||
graphStats.add( graphItem );
|
||||
|
||||
//
|
||||
|
||||
const frameStats = new Item( 'Frame Stats', createValueSpan(), createValueSpan(), createValueSpan() );
|
||||
perfList.add( frameStats );
|
||||
|
||||
const miscellaneous = new Item( 'Miscellaneous & Idle', createValueSpan(), createValueSpan(), createValueSpan() );
|
||||
miscellaneous.domElement.firstChild.style.backgroundColor = '#00ff0b1a';
|
||||
miscellaneous.domElement.firstChild.classList.add( 'no-hover' );
|
||||
frameStats.add( miscellaneous );
|
||||
|
||||
//
|
||||
|
||||
this.notInUse = new Map();
|
||||
this.frameStats = frameStats;
|
||||
this.graphStats = graphStats;
|
||||
this.graph = graph;
|
||||
this.miscellaneous = miscellaneous;
|
||||
|
||||
//
|
||||
|
||||
this.currentRender = null;
|
||||
this.currentItem = null;
|
||||
this.frameItems = new Map();
|
||||
|
||||
}
|
||||
|
||||
resolveStats( inspector, stats ) {
|
||||
|
||||
const data = inspector.getStatsData( stats.cid );
|
||||
|
||||
let item = data.item;
|
||||
|
||||
if ( item === undefined ) {
|
||||
|
||||
item = new Item( createValueSpan(), createValueSpan(), createValueSpan(), createValueSpan() );
|
||||
|
||||
if ( stats.name ) {
|
||||
|
||||
if ( stats.isComputeStats === true ) {
|
||||
|
||||
stats.name = `${ stats.name } [ Compute ]`;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
stats.name = `Unnamed ${ stats.cid }`;
|
||||
|
||||
}
|
||||
|
||||
item.userData.name = stats.name;
|
||||
|
||||
this.currentItem.add( item );
|
||||
data.item = item;
|
||||
|
||||
} else {
|
||||
|
||||
item.userData.name = stats.name;
|
||||
|
||||
if ( this.notInUse.has( stats.cid ) ) {
|
||||
|
||||
item.domElement.firstElementChild.classList.remove( 'alert' );
|
||||
|
||||
this.notInUse.delete( stats.cid );
|
||||
|
||||
}
|
||||
|
||||
const statsIndex = stats.parent.children.indexOf( stats );
|
||||
|
||||
if ( item.parent === null || item.parent.children.indexOf( item ) !== statsIndex ) {
|
||||
|
||||
this.currentItem.add( item, statsIndex );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let name = item.userData.name;
|
||||
|
||||
if ( stats.isComputeStats ) {
|
||||
|
||||
name += ' [ Compute ]';
|
||||
|
||||
}
|
||||
|
||||
setText( item.data[ 0 ], name );
|
||||
setText( item.data[ 1 ], data.cpu.toFixed( 2 ) );
|
||||
setText( item.data[ 2 ], stats.gpuNotAvailable === true ? '-' : data.gpu.toFixed( 2 ) );
|
||||
setText( item.data[ 3 ], data.total.toFixed( 2 ) );
|
||||
|
||||
//
|
||||
|
||||
const previousItem = this.currentItem;
|
||||
|
||||
this.currentItem = item;
|
||||
|
||||
for ( const child of stats.children ) {
|
||||
|
||||
this.resolveStats( inspector, child );
|
||||
|
||||
}
|
||||
|
||||
this.currentItem = previousItem;
|
||||
|
||||
this.frameItems.set( stats.cid, item );
|
||||
|
||||
}
|
||||
|
||||
updateGraph( inspector/*, frame*/ ) {
|
||||
|
||||
this.graph.addPoint( 'fps', inspector.fps );
|
||||
this.graph.update();
|
||||
|
||||
}
|
||||
|
||||
addNotInUse( cid, item ) {
|
||||
|
||||
item.domElement.firstElementChild.classList.add( 'alert' );
|
||||
|
||||
this.notInUse.set( cid, {
|
||||
item,
|
||||
time: performance.now()
|
||||
} );
|
||||
|
||||
this.updateNotInUse( cid );
|
||||
|
||||
}
|
||||
|
||||
updateNotInUse( cid ) {
|
||||
|
||||
const { item, time } = this.notInUse.get( cid );
|
||||
|
||||
const current = performance.now();
|
||||
const duration = 5;
|
||||
const remaining = duration - Math.floor( ( current - time ) / 1000 );
|
||||
|
||||
if ( remaining >= 0 ) {
|
||||
|
||||
const counter = '*'.repeat( Math.max( 0, remaining ) );
|
||||
const element = item.domElement.querySelector( '.list-item-cell .value' );
|
||||
|
||||
setText( element, item.userData.name + ' (not in use) ' + counter );
|
||||
|
||||
} else {
|
||||
|
||||
item.domElement.firstElementChild.classList.remove( 'alert' );
|
||||
item.parent.remove( item );
|
||||
|
||||
this.notInUse.delete( cid );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateText( inspector, frame ) {
|
||||
|
||||
const oldFrameItems = new Map( this.frameItems );
|
||||
|
||||
this.frameItems.clear();
|
||||
this.currentItem = this.frameStats;
|
||||
|
||||
for ( const child of frame.children ) {
|
||||
|
||||
this.resolveStats( inspector, child );
|
||||
|
||||
}
|
||||
|
||||
// remove unused frame items
|
||||
|
||||
for ( const [ cid, item ] of oldFrameItems ) {
|
||||
|
||||
if ( ! this.frameItems.has( cid ) ) {
|
||||
|
||||
this.addNotInUse( cid, item );
|
||||
|
||||
oldFrameItems.delete( cid );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// update not in use items
|
||||
|
||||
for ( const cid of this.notInUse.keys() ) {
|
||||
|
||||
this.updateNotInUse( cid );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
setText( 'graph-fps-counter', inspector.fps.toFixed() + ' FPS' );
|
||||
|
||||
//
|
||||
|
||||
setText( this.frameStats.data[ 1 ], frame.cpu.toFixed( 2 ) );
|
||||
setText( this.frameStats.data[ 2 ], frame.gpu.toFixed( 2 ) );
|
||||
setText( this.frameStats.data[ 3 ], frame.total.toFixed( 2 ) );
|
||||
|
||||
//
|
||||
|
||||
setText( this.miscellaneous.data[ 1 ], frame.miscellaneous.toFixed( 2 ) );
|
||||
setText( this.miscellaneous.data[ 2 ], '-' );
|
||||
setText( this.miscellaneous.data[ 3 ], frame.miscellaneous.toFixed( 2 ) );
|
||||
|
||||
//
|
||||
|
||||
this.currentItem = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Performance };
|
||||
264
node_modules/three/examples/jsm/inspector/tabs/Settings.js
generated
vendored
Normal file
264
node_modules/three/examples/jsm/inspector/tabs/Settings.js
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
import { Parameters } from './Parameters.js';
|
||||
import { WebGPURenderer, WebGLBackend, Node } from 'three/webgpu';
|
||||
import { getItem, setItem } from '../Inspector.js';
|
||||
|
||||
const _EXTENSIONS_PATH = '../extensions/extensions.json';
|
||||
|
||||
const _init = WebGPURenderer.prototype.init;
|
||||
|
||||
function forceWebGL( enable ) {
|
||||
|
||||
if ( enable ) {
|
||||
|
||||
WebGPURenderer.prototype.init = async function () {
|
||||
|
||||
if ( this.backend.isWebGLBackend !== true ) {
|
||||
|
||||
const parameters = this.backend.parameters;
|
||||
|
||||
this.backend = new WebGLBackend( parameters );
|
||||
|
||||
}
|
||||
|
||||
return _init.call( this );
|
||||
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
WebGPURenderer.prototype.init = _init;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let _state = null;
|
||||
|
||||
function _loadState() {
|
||||
|
||||
if ( _state !== null ) return _state;
|
||||
|
||||
const settings = getItem( 'settings' );
|
||||
|
||||
_state = {
|
||||
forceWebGL: settings.forceWebGL !== undefined ? settings.forceWebGL : false,
|
||||
captureStackTrace: settings.captureStackTrace !== undefined ? settings.captureStackTrace : false,
|
||||
activeExtensions: settings.activeExtensions !== undefined ? settings.activeExtensions : {}
|
||||
};
|
||||
|
||||
if ( _state.forceWebGL ) {
|
||||
|
||||
forceWebGL( true );
|
||||
|
||||
}
|
||||
|
||||
if ( _state.captureStackTrace ) {
|
||||
|
||||
Node.captureStackTrace = true;
|
||||
|
||||
}
|
||||
|
||||
return _state;
|
||||
|
||||
}
|
||||
|
||||
function _saveState() {
|
||||
|
||||
setItem( 'settings', {
|
||||
forceWebGL: _state.forceWebGL,
|
||||
captureStackTrace: _state.captureStackTrace,
|
||||
activeExtensions: _state.activeExtensions
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
_loadState();
|
||||
|
||||
//
|
||||
|
||||
class Settings extends Parameters {
|
||||
|
||||
constructor() {
|
||||
|
||||
super( { name: 'Settings' } );
|
||||
|
||||
this.extensions = {};
|
||||
|
||||
const currentState = _loadState();
|
||||
|
||||
// UI
|
||||
|
||||
const rendererGroup = this.createGroup( 'Renderer' );
|
||||
|
||||
rendererGroup.add( currentState, 'forceWebGL' ).name( 'Force WebGL' ).onChange( ( enable ) => {
|
||||
|
||||
forceWebGL( enable );
|
||||
_saveState();
|
||||
|
||||
location.reload();
|
||||
|
||||
} );
|
||||
|
||||
rendererGroup.add( currentState, 'captureStackTrace' ).name( 'Capture Stack Trace' ).onChange( ( enable ) => {
|
||||
|
||||
Node.captureStackTrace = enable;
|
||||
_saveState();
|
||||
|
||||
location.reload();
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
const extensionsGroup = this.createGroup( 'Extensions' );
|
||||
|
||||
this._getExtensions().then( extensions => {
|
||||
|
||||
for ( const extension of extensions ) {
|
||||
|
||||
extension.active = false;
|
||||
extension.loaded = false;
|
||||
extension.tab = null;
|
||||
|
||||
this.extensions[ extension.name ] = extension;
|
||||
|
||||
extension.ui = extensionsGroup.add( { [ extension.name ]: false }, extension.name ).onChange( async ( value ) => {
|
||||
|
||||
this.setActiveExtension( extension.name, value );
|
||||
|
||||
// User preference
|
||||
|
||||
if ( value ) {
|
||||
|
||||
_state.activeExtensions[ extension.name ] = {
|
||||
name: extension.name,
|
||||
url: extension.url
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
delete _state.activeExtensions[ extension.name ];
|
||||
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
this._updateExtensionUI( extension );
|
||||
|
||||
_saveState();
|
||||
|
||||
} );
|
||||
|
||||
// Set user-defined state
|
||||
|
||||
if ( _state.activeExtensions[ extension.name ] !== undefined ) {
|
||||
|
||||
extension.ui.setValue( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
async setActiveExtension( name, value ) {
|
||||
|
||||
const extension = this.extensions[ name ];
|
||||
const inspector = this.inspector;
|
||||
|
||||
if ( extension ) {
|
||||
|
||||
if ( value ) {
|
||||
|
||||
await this._loadExtension( inspector, extension );
|
||||
|
||||
} else {
|
||||
|
||||
await this._unloadExtension( inspector, extension );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_updateExtensionUI( extension ) {
|
||||
|
||||
const forceActive = extension.active && _state.activeExtensions[ extension.name ] === undefined;
|
||||
|
||||
if ( forceActive ) {
|
||||
|
||||
extension.ui.checkbox.checked = true;
|
||||
extension.ui.domElement.style.setProperty( '--accent-color', 'var(--color-green)' );
|
||||
|
||||
} else {
|
||||
|
||||
extension.ui.domElement.style.removeProperty( '--accent-color' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async _unloadExtension( inspector, extension ) {
|
||||
|
||||
if ( extension.active === false ) return;
|
||||
|
||||
//
|
||||
|
||||
inspector.removeTab( extension.tab );
|
||||
|
||||
extension.active = false;
|
||||
extension.loaded = false;
|
||||
extension.tab = null;
|
||||
|
||||
this._updateExtensionUI( extension );
|
||||
|
||||
this.dispatchEvent( { type: 'extensionremoved', name: extension.name } );
|
||||
|
||||
}
|
||||
|
||||
async _loadExtension( inspector, extension ) {
|
||||
|
||||
if ( extension.active === true ) return;
|
||||
|
||||
//
|
||||
|
||||
extension.active = true;
|
||||
|
||||
const extUrl = new URL( extension.url, new URL( _EXTENSIONS_PATH, import.meta.url ) ).href;
|
||||
|
||||
const module = await import( extUrl );
|
||||
|
||||
const keys = Object.keys( module );
|
||||
const ExtensionClass = module[ keys[ 0 ] ];
|
||||
const extensionTab = new ExtensionClass();
|
||||
|
||||
inspector.addTab( extensionTab );
|
||||
|
||||
extension.loaded = true;
|
||||
extension.tab = extensionTab;
|
||||
|
||||
this._updateExtensionUI( extension );
|
||||
|
||||
this.dispatchEvent( { type: 'extensionadded', name: extension.name, tab: extensionTab } );
|
||||
|
||||
}
|
||||
|
||||
async _getExtensions() {
|
||||
|
||||
const url = new URL( _EXTENSIONS_PATH, import.meta.url );
|
||||
|
||||
const extensions = await fetch( url ).then( res => res.json() );
|
||||
|
||||
return extensions;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Settings };
|
||||
1611
node_modules/three/examples/jsm/inspector/tabs/Timeline.js
generated
vendored
Normal file
1611
node_modules/three/examples/jsm/inspector/tabs/Timeline.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
268
node_modules/three/examples/jsm/inspector/tabs/Viewer.js
generated
vendored
Normal file
268
node_modules/three/examples/jsm/inspector/tabs/Viewer.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
import { Tab } from '../ui/Tab.js';
|
||||
import { List } from '../ui/List.js';
|
||||
import { Item } from '../ui/Item.js';
|
||||
import { splitPath, splitCamelCase } from '../ui/utils.js';
|
||||
|
||||
import { RendererUtils, NoToneMapping, LinearSRGBColorSpace, QuadMesh, NodeMaterial, CanvasTarget } from 'three/webgpu';
|
||||
import { renderOutput, vec2, vec3, vec4, Fn, screenUV, step, OnMaterialUpdate, uniform } from 'three/tsl';
|
||||
|
||||
const aspectRatioUV = /*@__PURE__*/ Fn( ( [ uv, textureNode ] ) => {
|
||||
|
||||
const aspect = uniform( 0 );
|
||||
|
||||
OnMaterialUpdate( () => {
|
||||
|
||||
const { width, height } = textureNode.value;
|
||||
|
||||
aspect.value = width / height;
|
||||
|
||||
} );
|
||||
|
||||
const centered = uv.sub( 0.5 );
|
||||
const corrected = vec2( centered.x.div( aspect ), centered.y );
|
||||
const finalUV = corrected.add( 0.5 );
|
||||
|
||||
const inBounds = step( 0.0, finalUV.x ).mul( step( finalUV.x, 1.0 ) ).mul( step( 0.0, finalUV.y ) ).mul( step( finalUV.y, 1.0 ) );
|
||||
|
||||
return vec3( finalUV, inBounds );
|
||||
|
||||
} );
|
||||
|
||||
class Viewer extends Tab {
|
||||
|
||||
constructor( options = {} ) {
|
||||
|
||||
super( 'Viewer', options );
|
||||
|
||||
const nodeList = new List( 'Viewer', 'Name' );
|
||||
nodeList.setGridStyle( '150px minmax(200px, 2fr)' );
|
||||
nodeList.domElement.style.minWidth = '400px';
|
||||
|
||||
const scrollWrapper = document.createElement( 'div' );
|
||||
scrollWrapper.className = 'list-scroll-wrapper';
|
||||
scrollWrapper.appendChild( nodeList.domElement );
|
||||
this.content.appendChild( scrollWrapper );
|
||||
|
||||
const nodes = new Item( 'Nodes' );
|
||||
nodeList.add( nodes );
|
||||
|
||||
//
|
||||
|
||||
this.itemLibrary = new Map();
|
||||
this.folderLibrary = new Map();
|
||||
this.canvasNodes = new Map();
|
||||
this.currentDataList = [];
|
||||
this.nodeList = nodeList;
|
||||
this.nodes = nodes;
|
||||
|
||||
}
|
||||
|
||||
getFolder( name ) {
|
||||
|
||||
let folder = this.folderLibrary.get( name );
|
||||
|
||||
if ( folder === undefined ) {
|
||||
|
||||
folder = new Item( name );
|
||||
|
||||
this.folderLibrary.set( name, folder );
|
||||
this.nodeList.add( folder );
|
||||
|
||||
}
|
||||
|
||||
return folder;
|
||||
|
||||
}
|
||||
|
||||
addNodeItem( canvasData ) {
|
||||
|
||||
let item = this.itemLibrary.get( canvasData.id );
|
||||
|
||||
if ( item === undefined ) {
|
||||
|
||||
const name = canvasData.name;
|
||||
const domElement = canvasData.canvasTarget.domElement;
|
||||
|
||||
item = new Item( domElement, name );
|
||||
item.itemRow.children[ 1 ].style[ 'justify-content' ] = 'flex-start';
|
||||
this.itemLibrary.set( canvasData.id, item );
|
||||
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
getCanvasDataByNode( renderer, node ) {
|
||||
|
||||
let canvasData = this.canvasNodes.get( node );
|
||||
|
||||
if ( canvasData === undefined ) {
|
||||
|
||||
const canvas = document.createElement( 'canvas' );
|
||||
|
||||
const canvasTarget = new CanvasTarget( canvas );
|
||||
canvasTarget.setPixelRatio( window.devicePixelRatio );
|
||||
canvasTarget.setSize( 140, 140 );
|
||||
|
||||
const id = node.id;
|
||||
|
||||
const { path, name } = splitPath( splitCamelCase( node.getName() || '(unnamed)' ) );
|
||||
|
||||
const target = node.context( { getUV: ( textureNode ) => {
|
||||
|
||||
const uvData = aspectRatioUV( screenUV, textureNode );
|
||||
const correctedUV = uvData.xy;
|
||||
const mask = uvData.z;
|
||||
|
||||
return correctedUV.mul( mask );
|
||||
|
||||
} } );
|
||||
|
||||
let output = vec4( vec3( target ), 1 );
|
||||
output = renderOutput( output, NoToneMapping, renderer.outputColorSpace );
|
||||
output = output.context( { inspector: true } );
|
||||
|
||||
const material = new NodeMaterial();
|
||||
material.outputNode = output;
|
||||
|
||||
const quad = new QuadMesh( material );
|
||||
quad.name = 'Viewer - ' + name;
|
||||
|
||||
canvasData = {
|
||||
id,
|
||||
name,
|
||||
path,
|
||||
node,
|
||||
quad,
|
||||
canvasTarget,
|
||||
material
|
||||
};
|
||||
|
||||
this.canvasNodes.set( node, canvasData );
|
||||
|
||||
}
|
||||
|
||||
return canvasData;
|
||||
|
||||
}
|
||||
|
||||
update( inspector ) {
|
||||
|
||||
const renderer = inspector.getRenderer();
|
||||
const nodes = inspector.getNodes();
|
||||
|
||||
if ( nodes.length > 0 ) {
|
||||
|
||||
if ( ! renderer.backend.isWebGPUBackend ) {
|
||||
|
||||
inspector.resolveConsoleOnce( 'warn', 'Inspector: Viewer is only available with WebGPU.' );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( ! this.isVisible ) {
|
||||
|
||||
this.show();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ! this.isActive ) return;
|
||||
|
||||
const canvasDataList = nodes.map( node => this.getCanvasDataByNode( renderer, node ) );
|
||||
|
||||
//
|
||||
|
||||
const previousDataList = [ ...this.currentDataList ];
|
||||
|
||||
// remove old
|
||||
|
||||
for ( const canvasData of previousDataList ) {
|
||||
|
||||
if ( this.itemLibrary.has( canvasData.id ) && canvasDataList.indexOf( canvasData ) === - 1 ) {
|
||||
|
||||
const item = this.itemLibrary.get( canvasData.id );
|
||||
const parent = item.parent;
|
||||
|
||||
parent.remove( item );
|
||||
|
||||
if ( this.folderLibrary.has( parent.data[ 0 ] ) && parent.children.length === 0 ) {
|
||||
|
||||
parent.parent.remove( parent );
|
||||
|
||||
this.folderLibrary.delete( parent.data[ 0 ] );
|
||||
|
||||
}
|
||||
|
||||
this.itemLibrary.delete( canvasData.id );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const indexes = {};
|
||||
|
||||
for ( const canvasData of canvasDataList ) {
|
||||
|
||||
const item = this.addNodeItem( canvasData );
|
||||
const previousCanvasTarget = renderer.getCanvasTarget();
|
||||
|
||||
const path = canvasData.path;
|
||||
|
||||
if ( path ) {
|
||||
|
||||
const folder = this.getFolder( path );
|
||||
|
||||
if ( indexes[ path ] === undefined ) {
|
||||
|
||||
indexes[ path ] = 0;
|
||||
|
||||
}
|
||||
|
||||
if ( folder.parent === null || item.parent !== folder || folder.children.indexOf( item ) !== indexes[ path ] ) {
|
||||
|
||||
folder.add( item );
|
||||
|
||||
}
|
||||
|
||||
indexes[ path ] ++;
|
||||
|
||||
} else {
|
||||
|
||||
if ( ! item.parent ) {
|
||||
|
||||
this.nodes.add( item );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.currentDataList = canvasDataList;
|
||||
|
||||
//
|
||||
|
||||
const state = RendererUtils.resetRendererState( renderer );
|
||||
|
||||
renderer.toneMapping = NoToneMapping;
|
||||
renderer.outputColorSpace = LinearSRGBColorSpace;
|
||||
|
||||
renderer.setCanvasTarget( canvasData.canvasTarget );
|
||||
|
||||
canvasData.quad.render( renderer );
|
||||
|
||||
renderer.setCanvasTarget( previousCanvasTarget );
|
||||
|
||||
RendererUtils.restoreRendererState( renderer, state );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Viewer };
|
||||
95
node_modules/three/examples/jsm/inspector/ui/Graph.js
generated
vendored
Normal file
95
node_modules/three/examples/jsm/inspector/ui/Graph.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
export class Graph {
|
||||
|
||||
constructor( maxPoints = 512 ) {
|
||||
|
||||
this.maxPoints = maxPoints;
|
||||
this.lines = {};
|
||||
this.limit = 0;
|
||||
this.limitIndex = 0;
|
||||
|
||||
this.domElement = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
||||
this.domElement.setAttribute( 'class', 'graph-svg' );
|
||||
|
||||
}
|
||||
|
||||
addLine( id, color ) {
|
||||
|
||||
const path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
||||
path.setAttribute( 'class', 'graph-path' );
|
||||
path.style.stroke = color;
|
||||
path.style.fill = color;
|
||||
this.domElement.appendChild( path );
|
||||
|
||||
this.lines[ id ] = { path, color, points: [] };
|
||||
|
||||
}
|
||||
|
||||
addPoint( lineId, value ) {
|
||||
|
||||
const line = this.lines[ lineId ];
|
||||
if ( ! line ) return;
|
||||
|
||||
line.points.push( value );
|
||||
if ( line.points.length > this.maxPoints ) {
|
||||
|
||||
line.points.shift();
|
||||
|
||||
}
|
||||
|
||||
if ( value > this.limit ) {
|
||||
|
||||
this.limit = value;
|
||||
this.limitIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resetLimit() {
|
||||
|
||||
this.limit = 0;
|
||||
this.limitIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
const svgWidth = this.domElement.clientWidth;
|
||||
const svgHeight = this.domElement.clientHeight;
|
||||
if ( svgWidth === 0 ) return;
|
||||
|
||||
const pointStep = svgWidth / ( this.maxPoints - 1 );
|
||||
|
||||
for ( const id in this.lines ) {
|
||||
|
||||
const line = this.lines[ id ];
|
||||
|
||||
let pathString = `M 0,${ svgHeight }`;
|
||||
for ( let i = 0; i < line.points.length; i ++ ) {
|
||||
|
||||
const x = i * pointStep;
|
||||
const y = svgHeight - ( line.points[ i ] / this.limit ) * svgHeight;
|
||||
pathString += ` L ${ x },${ y }`;
|
||||
|
||||
}
|
||||
|
||||
pathString += ` L ${( line.points.length - 1 ) * pointStep},${ svgHeight } Z`;
|
||||
|
||||
const offset = svgWidth - ( ( line.points.length - 1 ) * pointStep );
|
||||
line.path.setAttribute( 'transform', `translate(${ offset }, 0)` );
|
||||
line.path.setAttribute( 'd', pathString );
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if ( this.limitIndex ++ > this.maxPoints ) {
|
||||
|
||||
this.resetLimit();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
170
node_modules/three/examples/jsm/inspector/ui/Item.js
generated
vendored
Normal file
170
node_modules/three/examples/jsm/inspector/ui/Item.js
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
export class Item {
|
||||
|
||||
constructor( ...data ) {
|
||||
|
||||
this.children = [];
|
||||
this.isOpen = true;
|
||||
this.childrenContainer = null;
|
||||
this.parent = null;
|
||||
this.domElement = document.createElement( 'div' );
|
||||
this.domElement.className = 'list-item-wrapper';
|
||||
this.itemRow = document.createElement( 'div' );
|
||||
this.itemRow.className = 'list-item-row';
|
||||
|
||||
this.userData = {};
|
||||
|
||||
this.data = data;
|
||||
this.data.forEach( ( cellData ) => {
|
||||
|
||||
const cell = document.createElement( 'div' );
|
||||
cell.className = 'list-item-cell';
|
||||
if ( cellData instanceof HTMLElement ) {
|
||||
|
||||
cell.appendChild( cellData );
|
||||
|
||||
} else {
|
||||
|
||||
cell.append( String( cellData ) );
|
||||
|
||||
}
|
||||
|
||||
this.itemRow.appendChild( cell );
|
||||
|
||||
} );
|
||||
|
||||
this.domElement.appendChild( this.itemRow );
|
||||
|
||||
// Bindings
|
||||
|
||||
this.onItemClick = this.onItemClick.bind( this );
|
||||
|
||||
}
|
||||
|
||||
onItemClick( e ) {
|
||||
|
||||
if ( e.target.closest( 'button, a, input, label' ) ) return;
|
||||
|
||||
this.toggle();
|
||||
|
||||
}
|
||||
|
||||
add( item, index = this.children.length ) {
|
||||
|
||||
if ( item.parent !== null ) {
|
||||
|
||||
item.parent.remove( item );
|
||||
|
||||
}
|
||||
|
||||
item.parent = this;
|
||||
|
||||
this.children.splice( index, 0, item );
|
||||
|
||||
this.itemRow.classList.add( 'collapsible' );
|
||||
|
||||
if ( ! this.childrenContainer ) {
|
||||
|
||||
this.childrenContainer = document.createElement( 'div' );
|
||||
this.childrenContainer.className = 'list-children-container';
|
||||
this.childrenContainer.classList.toggle( 'closed', ! this.isOpen );
|
||||
this.domElement.appendChild( this.childrenContainer );
|
||||
this.itemRow.addEventListener( 'click', this.onItemClick );
|
||||
|
||||
}
|
||||
|
||||
this.childrenContainer.insertBefore(
|
||||
item.domElement,
|
||||
this.childrenContainer.children[ index ] || null
|
||||
);
|
||||
|
||||
this.updateToggler();
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
remove( item ) {
|
||||
|
||||
const index = this.children.indexOf( item );
|
||||
|
||||
if ( index !== - 1 ) {
|
||||
|
||||
this.children.splice( index, 1 );
|
||||
this.childrenContainer.removeChild( item.domElement );
|
||||
|
||||
item.parent = null;
|
||||
|
||||
if ( this.children.length === 0 ) {
|
||||
|
||||
this.itemRow.classList.remove( 'collapsible' );
|
||||
this.itemRow.removeEventListener( 'click', this.onItemClick );
|
||||
|
||||
this.childrenContainer.remove();
|
||||
this.childrenContainer = null;
|
||||
|
||||
}
|
||||
|
||||
this.updateToggler();
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
updateToggler() {
|
||||
|
||||
const firstCell = this.itemRow.querySelector( '.list-item-cell:first-child' );
|
||||
let toggler = this.itemRow.querySelector( '.item-toggler' );
|
||||
|
||||
if ( this.children.length > 0 ) {
|
||||
|
||||
if ( ! toggler ) {
|
||||
|
||||
toggler = document.createElement( 'span' );
|
||||
toggler.className = 'item-toggler';
|
||||
firstCell.prepend( toggler );
|
||||
|
||||
}
|
||||
|
||||
if ( this.isOpen ) {
|
||||
|
||||
this.itemRow.classList.add( 'open' );
|
||||
|
||||
}
|
||||
|
||||
} else if ( toggler ) {
|
||||
|
||||
toggler.remove();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
toggle() {
|
||||
|
||||
this.isOpen = ! this.isOpen;
|
||||
this.itemRow.classList.toggle( 'open', this.isOpen );
|
||||
|
||||
if ( this.childrenContainer ) {
|
||||
|
||||
this.childrenContainer.classList.toggle( 'closed', ! this.isOpen );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
if ( this.isOpen ) {
|
||||
|
||||
this.toggle();
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
75
node_modules/three/examples/jsm/inspector/ui/List.js
generated
vendored
Normal file
75
node_modules/three/examples/jsm/inspector/ui/List.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
export class List {
|
||||
|
||||
constructor( ...headers ) {
|
||||
|
||||
this.headers = headers;
|
||||
this.children = [];
|
||||
this.domElement = document.createElement( 'div' );
|
||||
this.domElement.className = 'list-container';
|
||||
this.domElement.style.padding = '10px';
|
||||
this.id = `list-${Math.random().toString( 36 ).slice( 2, 11 )}`;
|
||||
this.domElement.dataset.listId = this.id;
|
||||
|
||||
this.gridStyleElement = document.createElement( 'style' );
|
||||
this.domElement.appendChild( this.gridStyleElement );
|
||||
|
||||
const headerRow = document.createElement( 'div' );
|
||||
headerRow.className = 'list-header';
|
||||
this.headers.forEach( headerText => {
|
||||
|
||||
const headerCell = document.createElement( 'div' );
|
||||
headerCell.className = 'list-header-cell';
|
||||
headerCell.textContent = headerText;
|
||||
headerRow.appendChild( headerCell );
|
||||
|
||||
} );
|
||||
this.domElement.appendChild( headerRow );
|
||||
|
||||
}
|
||||
|
||||
setGridStyle( gridTemplate ) {
|
||||
|
||||
this.gridStyleElement.textContent = `
|
||||
[data-list-id="${this.id}"] > .list-header,
|
||||
[data-list-id="${this.id}"] .list-item-row {
|
||||
grid-template-columns: ${gridTemplate};
|
||||
}
|
||||
`;
|
||||
|
||||
}
|
||||
|
||||
add( item ) {
|
||||
|
||||
if ( item.parent !== null ) {
|
||||
|
||||
item.parent.remove( item );
|
||||
|
||||
}
|
||||
|
||||
item.domElement.classList.add( 'header-wrapper', 'section-start' );
|
||||
item.parent = this;
|
||||
|
||||
this.children.push( item );
|
||||
this.domElement.appendChild( item.domElement );
|
||||
|
||||
}
|
||||
|
||||
remove( item ) {
|
||||
|
||||
const index = this.children.indexOf( item );
|
||||
|
||||
if ( index !== - 1 ) {
|
||||
|
||||
this.children.splice( index, 1 );
|
||||
this.domElement.removeChild( item.domElement );
|
||||
|
||||
item.parent = null;
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
2072
node_modules/three/examples/jsm/inspector/ui/Profiler.js
generated
vendored
Normal file
2072
node_modules/three/examples/jsm/inspector/ui/Profiler.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1667
node_modules/three/examples/jsm/inspector/ui/Style.js
generated
vendored
Normal file
1667
node_modules/three/examples/jsm/inspector/ui/Style.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
265
node_modules/three/examples/jsm/inspector/ui/Tab.js
generated
vendored
Normal file
265
node_modules/three/examples/jsm/inspector/ui/Tab.js
generated
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
import { EventDispatcher } from 'three';
|
||||
|
||||
/**
|
||||
* Tab class
|
||||
* @param {string} title - The title of the tab
|
||||
* @param {Object} options - Options for the tab
|
||||
* @param {boolean} [options.allowDetach=true] - Whether the tab can be detached into a separate window
|
||||
* @param {boolean} [options.builtin=false] - Whether the tab should appear in the profiler-toggle button
|
||||
* @param {string} [options.icon] - SVG icon HTML for the builtin button
|
||||
*
|
||||
* @example
|
||||
* // Create a tab that can be detached (default behavior)
|
||||
* const tab1 = new Tab('My Tab');
|
||||
*
|
||||
* // Create a tab that cannot be detached
|
||||
* const tab2 = new Tab('Fixed Tab', { allowDetach: false });
|
||||
*
|
||||
* // Create a builtin tab that appears in the profiler-toggle
|
||||
* const tab3 = new Tab('Builtin Tab', { builtin: true });
|
||||
*
|
||||
* // Create a builtin tab with custom icon
|
||||
* const tab4 = new Tab('Settings', { builtin: true, icon: '<svg>...</svg>' });
|
||||
*
|
||||
* // Control builtin tab visibility
|
||||
* tab3.showBuiltin(); // Show the builtin button and mini-content
|
||||
* tab3.hideBuiltin(); // Hide the builtin button and mini-content
|
||||
*/
|
||||
export class Tab extends EventDispatcher {
|
||||
|
||||
constructor( title, options = {} ) {
|
||||
|
||||
super();
|
||||
|
||||
this.id = title.toLowerCase();
|
||||
this.button = document.createElement( 'button' );
|
||||
this.button.className = 'tab-btn';
|
||||
this.button.textContent = title;
|
||||
|
||||
this.content = document.createElement( 'div' );
|
||||
this.content.id = `${this.id}-content`;
|
||||
this.content.className = 'profiler-content';
|
||||
|
||||
this._isActive = false;
|
||||
this.isVisible = true;
|
||||
this.isDetached = false;
|
||||
this.detachedWindow = null;
|
||||
this.allowDetach = options.allowDetach !== undefined ? options.allowDetach : true;
|
||||
this.builtin = options.builtin !== undefined ? options.builtin : false;
|
||||
this.icon = options.icon || null;
|
||||
this.builtinButton = null; // Reference to the builtin button in profiler-toggle
|
||||
this.miniContent = null; // Reference to the mini-panel content container
|
||||
this.profiler = null; // Reference to the profiler instance
|
||||
this.onVisibilityChange = null; // Callback for visibility changes
|
||||
|
||||
}
|
||||
|
||||
get inspector() {
|
||||
|
||||
return this.profiler.inspector;
|
||||
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
|
||||
const isProfilerVisible = this.profiler && this.profiler.panel.classList.contains( 'visible' );
|
||||
|
||||
if ( ! isProfilerVisible ) return false;
|
||||
|
||||
return this.isDetached || this._isActive;
|
||||
|
||||
}
|
||||
|
||||
set isActive( value ) {
|
||||
|
||||
this._isActive = value;
|
||||
|
||||
}
|
||||
|
||||
init( /*inspector*/ ) { }
|
||||
|
||||
update( /*inspector*/ ) { }
|
||||
|
||||
setActive( isActive ) {
|
||||
|
||||
this.button.classList.toggle( 'active', isActive );
|
||||
this.content.classList.toggle( 'active', isActive );
|
||||
|
||||
this.isActive = isActive;
|
||||
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
this.content.style.display = '';
|
||||
this.button.style.display = '';
|
||||
|
||||
this.isVisible = true;
|
||||
|
||||
// Show detached window if tab is detached
|
||||
if ( this.isDetached && this.detachedWindow ) {
|
||||
|
||||
this.detachedWindow.panel.style.display = '';
|
||||
|
||||
}
|
||||
|
||||
// Notify profiler of visibility change
|
||||
if ( this.onVisibilityChange ) {
|
||||
|
||||
this.onVisibilityChange();
|
||||
|
||||
}
|
||||
|
||||
this.showBuiltin();
|
||||
|
||||
}
|
||||
|
||||
hide() {
|
||||
|
||||
this.content.style.display = 'none';
|
||||
this.button.style.display = 'none';
|
||||
|
||||
this.isVisible = false;
|
||||
|
||||
// Hide detached window if tab is detached
|
||||
if ( this.isDetached && this.detachedWindow ) {
|
||||
|
||||
this.detachedWindow.panel.style.display = 'none';
|
||||
|
||||
}
|
||||
|
||||
// Notify profiler of visibility change
|
||||
if ( this.onVisibilityChange ) {
|
||||
|
||||
this.onVisibilityChange();
|
||||
|
||||
}
|
||||
|
||||
this.hideBuiltin();
|
||||
|
||||
}
|
||||
|
||||
showBuiltin() {
|
||||
|
||||
if ( ! this.builtin ) return;
|
||||
|
||||
// Show the builtin-tabs-container
|
||||
if ( this.profiler && this.profiler.builtinTabsContainer ) {
|
||||
|
||||
this.profiler.builtinTabsContainer.style.display = '';
|
||||
|
||||
}
|
||||
|
||||
// Show the button
|
||||
if ( this.builtinButton ) {
|
||||
|
||||
this.builtinButton.style.display = '';
|
||||
|
||||
}
|
||||
|
||||
// Show and activate the mini-panel with content
|
||||
if ( this.miniContent && this.profiler ) {
|
||||
|
||||
// Hide all other mini-panel contents
|
||||
this.profiler.miniPanel.querySelectorAll( '.mini-panel-content' ).forEach( content => {
|
||||
|
||||
content.style.display = 'none';
|
||||
|
||||
} );
|
||||
|
||||
// Remove active state from all builtin buttons
|
||||
this.profiler.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ).forEach( btn => {
|
||||
|
||||
btn.classList.remove( 'active' );
|
||||
|
||||
} );
|
||||
|
||||
// Activate this tab's button
|
||||
if ( this.builtinButton ) {
|
||||
|
||||
this.builtinButton.classList.add( 'active' );
|
||||
|
||||
}
|
||||
|
||||
// Move content to mini-panel if not already there
|
||||
if ( ! this.miniContent.firstChild ) {
|
||||
|
||||
while ( this.content.firstChild ) {
|
||||
|
||||
this.miniContent.appendChild( this.content.firstChild );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show the mini-panel and content
|
||||
this.miniContent.style.display = 'block';
|
||||
this.profiler.miniPanel.classList.add( 'visible' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hideBuiltin() {
|
||||
|
||||
if ( ! this.builtin ) return;
|
||||
|
||||
// Hide the button
|
||||
if ( this.builtinButton ) {
|
||||
|
||||
this.builtinButton.style.display = 'none';
|
||||
|
||||
}
|
||||
|
||||
// Hide the mini-panel content
|
||||
if ( this.miniContent ) {
|
||||
|
||||
this.miniContent.style.display = 'none';
|
||||
|
||||
// Move content back to main panel
|
||||
if ( this.miniContent.firstChild ) {
|
||||
|
||||
while ( this.miniContent.firstChild ) {
|
||||
|
||||
this.content.appendChild( this.miniContent.firstChild );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Deactivate button
|
||||
if ( this.builtinButton ) {
|
||||
|
||||
this.builtinButton.classList.remove( 'active' );
|
||||
|
||||
}
|
||||
|
||||
// Hide mini-panel if no content is visible
|
||||
if ( this.profiler ) {
|
||||
|
||||
const hasVisibleContent = Array.from( this.profiler.miniPanel.querySelectorAll( '.mini-panel-content' ) )
|
||||
.some( content => content.style.display !== 'none' );
|
||||
|
||||
if ( ! hasVisibleContent ) {
|
||||
|
||||
this.profiler.miniPanel.classList.remove( 'visible' );
|
||||
|
||||
}
|
||||
|
||||
// Hide the builtin-tabs-container if all builtin buttons are hidden
|
||||
const hasVisibleBuiltinButtons = Array.from( this.profiler.builtinTabsContainer.querySelectorAll( '.builtin-tab-btn' ) )
|
||||
.some( btn => btn.style.display !== 'none' );
|
||||
|
||||
if ( ! hasVisibleBuiltinButtons ) {
|
||||
|
||||
this.profiler.builtinTabsContainer.style.display = 'none';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
476
node_modules/three/examples/jsm/inspector/ui/Values.js
generated
vendored
Normal file
476
node_modules/three/examples/jsm/inspector/ui/Values.js
generated
vendored
Normal file
@@ -0,0 +1,476 @@
|
||||
import { EventDispatcher } from 'three';
|
||||
|
||||
class Value extends EventDispatcher {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.domElement = document.createElement( 'div' );
|
||||
this.domElement.className = 'param-control';
|
||||
|
||||
this._onChangeFunction = null;
|
||||
|
||||
this.addEventListener( 'change', ( e ) => {
|
||||
|
||||
// defer to avoid issues when changing multiple values in the same call stack
|
||||
|
||||
requestAnimationFrame( () => {
|
||||
|
||||
if ( this._onChangeFunction ) this._onChangeFunction( e.value );
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
setValue( /*val*/ ) {
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
dispatchChange() {
|
||||
|
||||
this.dispatchEvent( { type: 'change', value: this.getValue() } );
|
||||
|
||||
}
|
||||
|
||||
onChange( callback ) {
|
||||
|
||||
this._onChangeFunction = callback;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueNumber extends Value {
|
||||
|
||||
constructor( { value = 0, step = 0.1, min = - Infinity, max = Infinity } ) {
|
||||
|
||||
super();
|
||||
|
||||
this.input = document.createElement( 'input' );
|
||||
this.input.type = 'number';
|
||||
this.input.value = value;
|
||||
this.input.step = step;
|
||||
this.input.min = min;
|
||||
this.input.max = max;
|
||||
this.input.addEventListener( 'change', this._onChangeValue.bind( this ) );
|
||||
this.domElement.appendChild( this.input );
|
||||
this.addDragHandler();
|
||||
|
||||
}
|
||||
|
||||
_onChangeValue() {
|
||||
|
||||
const value = parseFloat( this.input.value );
|
||||
const min = parseFloat( this.input.min );
|
||||
const max = parseFloat( this.input.max );
|
||||
|
||||
if ( value > max ) {
|
||||
|
||||
this.input.value = max;
|
||||
|
||||
} else if ( value < min ) {
|
||||
|
||||
this.input.value = min;
|
||||
|
||||
} else if ( isNaN( value ) ) {
|
||||
|
||||
this.input.value = min;
|
||||
|
||||
}
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
}
|
||||
|
||||
addDragHandler() {
|
||||
|
||||
let isDragging = false;
|
||||
let startY, startValue;
|
||||
|
||||
this.input.addEventListener( 'mousedown', ( e ) => {
|
||||
|
||||
isDragging = true;
|
||||
startY = e.clientY;
|
||||
startValue = parseFloat( this.input.value );
|
||||
document.body.style.cursor = 'ns-resize';
|
||||
|
||||
} );
|
||||
|
||||
document.addEventListener( 'mousemove', ( e ) => {
|
||||
|
||||
if ( isDragging ) {
|
||||
|
||||
const deltaY = startY - e.clientY;
|
||||
const step = parseFloat( this.input.step ) || 1;
|
||||
const min = parseFloat( this.input.min );
|
||||
const max = parseFloat( this.input.max );
|
||||
|
||||
let stepSize = step;
|
||||
|
||||
if ( ! isNaN( max ) && isFinite( min ) ) {
|
||||
|
||||
stepSize = ( max - min ) / 100;
|
||||
|
||||
}
|
||||
|
||||
const change = deltaY * stepSize;
|
||||
|
||||
let newValue = startValue + change;
|
||||
newValue = Math.max( min, Math.min( newValue, max ) );
|
||||
|
||||
const precision = ( String( step ).split( '.' )[ 1 ] || [] ).length;
|
||||
this.input.value = newValue.toFixed( precision );
|
||||
|
||||
this.input.dispatchEvent( new Event( 'input' ) );
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
document.addEventListener( 'mouseup', () => {
|
||||
|
||||
if ( isDragging ) {
|
||||
|
||||
isDragging = false;
|
||||
document.body.style.cursor = 'default';
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
setValue( val ) {
|
||||
|
||||
this.input.value = val;
|
||||
|
||||
return super.setValue( val );
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
return parseFloat( this.input.value );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueCheckbox extends Value {
|
||||
|
||||
constructor( { value = false } ) {
|
||||
|
||||
super();
|
||||
|
||||
const label = document.createElement( 'label' );
|
||||
label.className = 'custom-checkbox';
|
||||
|
||||
const checkbox = document.createElement( 'input' );
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = value;
|
||||
this.checkbox = checkbox;
|
||||
|
||||
const checkmark = document.createElement( 'span' );
|
||||
checkmark.className = 'checkmark';
|
||||
|
||||
label.appendChild( checkbox );
|
||||
label.appendChild( checkmark );
|
||||
this.domElement.appendChild( label );
|
||||
|
||||
checkbox.addEventListener( 'change', () => {
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
setValue( val ) {
|
||||
|
||||
this.checkbox.checked = val;
|
||||
|
||||
return super.setValue( val );
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
return this.checkbox.checked;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueSlider extends Value {
|
||||
|
||||
constructor( { value = 0, min = 0, max = 1, step = 0.01 } ) {
|
||||
|
||||
super();
|
||||
|
||||
this.slider = document.createElement( 'input' );
|
||||
this.slider.type = 'range';
|
||||
this.slider.min = min;
|
||||
this.slider.max = max;
|
||||
this.slider.step = step;
|
||||
|
||||
const numberValue = new ValueNumber( { value, min, max, step } );
|
||||
this.numberInput = numberValue.input;
|
||||
this.numberInput.style.flexBasis = '80px';
|
||||
this.numberInput.style.flexShrink = '0';
|
||||
|
||||
this.slider.value = value;
|
||||
|
||||
this.domElement.append( this.slider, this.numberInput );
|
||||
|
||||
this.slider.addEventListener( 'input', () => {
|
||||
|
||||
this.numberInput.value = this.slider.value;
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
} );
|
||||
|
||||
numberValue.addEventListener( 'change', () => {
|
||||
|
||||
this.slider.value = parseFloat( this.numberInput.value );
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
setValue( val ) {
|
||||
|
||||
this.slider.value = val;
|
||||
this.numberInput.value = val;
|
||||
|
||||
return super.setValue( val );
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
return parseFloat( this.slider.value );
|
||||
|
||||
}
|
||||
|
||||
step( value ) {
|
||||
|
||||
this.slider.step = value;
|
||||
this.numberInput.step = value;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueSelect extends Value {
|
||||
|
||||
constructor( { options = [], value = '' } ) {
|
||||
|
||||
super();
|
||||
|
||||
const select = document.createElement( 'select' );
|
||||
|
||||
const createOption = ( name, optionValue ) => {
|
||||
|
||||
const optionEl = document.createElement( 'option' );
|
||||
optionEl.value = name;
|
||||
optionEl.textContent = name;
|
||||
|
||||
if ( optionValue == value ) optionEl.selected = true;
|
||||
|
||||
select.appendChild( optionEl );
|
||||
|
||||
return optionEl;
|
||||
|
||||
};
|
||||
|
||||
if ( Array.isArray( options ) ) {
|
||||
|
||||
options.forEach( opt => createOption( opt, opt ) );
|
||||
|
||||
} else {
|
||||
|
||||
Object.entries( options ).forEach( ( [ key, value ] ) => createOption( key, value ) );
|
||||
|
||||
}
|
||||
|
||||
this.domElement.appendChild( select );
|
||||
|
||||
//
|
||||
|
||||
select.addEventListener( 'change', () => {
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
} );
|
||||
|
||||
this.options = options;
|
||||
this.select = select;
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
const options = this.options;
|
||||
|
||||
if ( Array.isArray( options ) ) {
|
||||
|
||||
return options[ this.select.selectedIndex ];
|
||||
|
||||
} else {
|
||||
|
||||
return options[ this.select.value ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueColor extends Value {
|
||||
|
||||
constructor( { value = '#ffffff' } ) {
|
||||
|
||||
super();
|
||||
|
||||
const colorInput = document.createElement( 'input' );
|
||||
colorInput.type = 'color';
|
||||
colorInput.value = this._getColorHex( value );
|
||||
this.colorInput = colorInput;
|
||||
|
||||
this._value = value;
|
||||
|
||||
colorInput.addEventListener( 'input', () => {
|
||||
|
||||
const colorValue = colorInput.value;
|
||||
|
||||
if ( this._value.isColor ) {
|
||||
|
||||
this._value.setHex( parseInt( colorValue.slice( 1 ), 16 ) );
|
||||
|
||||
} else {
|
||||
|
||||
this._value = colorValue;
|
||||
|
||||
}
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
} );
|
||||
|
||||
this.domElement.appendChild( colorInput );
|
||||
|
||||
}
|
||||
|
||||
_getColorHex( color ) {
|
||||
|
||||
if ( color.isColor ) {
|
||||
|
||||
color = color.getHex();
|
||||
|
||||
}
|
||||
|
||||
if ( typeof color === 'number' ) {
|
||||
|
||||
color = `#${ color.toString( 16 ) }`;
|
||||
|
||||
} else if ( color[ 0 ] !== '#' ) {
|
||||
|
||||
color = '#' + color;
|
||||
|
||||
}
|
||||
|
||||
return color;
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
let value = this._value;
|
||||
|
||||
if ( typeof value === 'string' ) {
|
||||
|
||||
value = parseInt( value.slice( 1 ), 16 );
|
||||
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueButton extends Value {
|
||||
|
||||
constructor( { text = 'Button', value = () => {} } ) {
|
||||
|
||||
super();
|
||||
|
||||
const button = document.createElement( 'button' );
|
||||
button.textContent = text;
|
||||
button.onclick = value;
|
||||
this.domElement.appendChild( button );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ValueString extends Value {
|
||||
|
||||
constructor( { value = '' } ) {
|
||||
|
||||
super();
|
||||
|
||||
const input = document.createElement( 'input' );
|
||||
input.type = 'text';
|
||||
input.value = value;
|
||||
this.input = input;
|
||||
|
||||
input.addEventListener( 'input', () => {
|
||||
|
||||
this.dispatchChange();
|
||||
|
||||
} );
|
||||
|
||||
this.domElement.appendChild( input );
|
||||
|
||||
}
|
||||
|
||||
setValue( val ) {
|
||||
|
||||
this.input.value = val;
|
||||
|
||||
return super.setValue( val );
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
|
||||
return this.input.value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Value, ValueNumber, ValueString, ValueCheckbox, ValueSlider, ValueSelect, ValueColor, ValueButton };
|
||||
69
node_modules/three/examples/jsm/inspector/ui/utils.js
generated
vendored
Normal file
69
node_modules/three/examples/jsm/inspector/ui/utils.js
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
export function createValueSpan( id = null ) {
|
||||
|
||||
const span = document.createElement( 'span' );
|
||||
span.className = 'value';
|
||||
|
||||
if ( id !== null ) span.id = id;
|
||||
|
||||
return span;
|
||||
|
||||
}
|
||||
|
||||
export function setText( element, text ) {
|
||||
|
||||
const el = element instanceof HTMLElement ? element : document.getElementById( element );
|
||||
|
||||
if ( el && el.textContent !== text ) {
|
||||
|
||||
el.textContent = text;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function getText( element ) {
|
||||
|
||||
const el = element instanceof HTMLElement ? element : document.getElementById( element );
|
||||
|
||||
return el ? el.textContent : null;
|
||||
|
||||
}
|
||||
|
||||
export function splitPath( fullPath ) {
|
||||
|
||||
const lastSlash = fullPath.lastIndexOf( '/' );
|
||||
|
||||
if ( lastSlash === - 1 ) {
|
||||
|
||||
return {
|
||||
path: '',
|
||||
name: fullPath.trim()
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const path = fullPath.substring( 0, lastSlash ).trim();
|
||||
const name = fullPath.substring( lastSlash + 1 ).trim();
|
||||
|
||||
return { path, name };
|
||||
|
||||
}
|
||||
|
||||
export function splitCamelCase( str ) {
|
||||
|
||||
return str.replace( /([a-z0-9])([A-Z])/g, '$1 $2' ).trim();
|
||||
|
||||
}
|
||||
|
||||
export function formatBytes( bytes, decimals = 2 ) {
|
||||
|
||||
if ( bytes === 0 ) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
|
||||
const i = Math.floor( Math.log( bytes ) / Math.log( k ) );
|
||||
|
||||
return parseFloat( ( bytes / Math.pow( k, i ) ).toFixed( dm ) ) + ' ' + sizes[ i ];
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user