Mr.doob 3 месяцев назад
Родитель
Сommit
d6dcfbf977

+ 9 - 1
devtools/background.js

@@ -6,6 +6,8 @@ const MESSAGE_INIT = 'init';
 const MESSAGE_REQUEST_STATE = 'request-state';
 const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
 const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+const MESSAGE_HIGHLIGHT_OBJECT = 'highlight-object';
+const MESSAGE_UNHIGHLIGHT_OBJECT = 'unhighlight-object';
 const MESSAGE_REGISTER = 'register';
 const MESSAGE_COMMITTED = 'committed';
 
@@ -34,7 +36,13 @@ chrome.runtime.onConnect.addListener( port => {
 	let tabId;
 
 	// Messages that should be forwarded to content script
-	const forwardableMessages = new Set( [ MESSAGE_REQUEST_STATE, MESSAGE_REQUEST_OBJECT_DETAILS, MESSAGE_SCROLL_TO_CANVAS ] );
+	const forwardableMessages = new Set( [
+		MESSAGE_REQUEST_STATE,
+		MESSAGE_REQUEST_OBJECT_DETAILS,
+		MESSAGE_SCROLL_TO_CANVAS,
+		MESSAGE_HIGHLIGHT_OBJECT,
+		MESSAGE_UNHIGHLIGHT_OBJECT
+	] );
 
 	// Listen for messages from the devtools panel
 	port.onMessage.addListener( message => {

+ 41 - 2
devtools/bridge.js

@@ -16,6 +16,8 @@
 	const MESSAGE_REQUEST_STATE = 'request-state';
 	const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
 	const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+	const MESSAGE_HIGHLIGHT_OBJECT = 'highlight-object';
+	const MESSAGE_UNHIGHLIGHT_OBJECT = 'unhighlight-object';
 	const HIGHLIGHT_OVERLAY_DURATION = 1000;
 
 	// Only initialize if not already initialized
@@ -114,6 +116,9 @@
 
 				if ( ! object || ! object.uuid ) return;
 
+				// Skip DevTools highlight objects
+				if ( object.name === '__THREE_DEVTOOLS_HIGHLIGHT__' ) return;
+
 				// Skip if already processed (when duplicate prevention is enabled)
 				if ( processedUUIDs && processedUUIDs.has( object.uuid ) ) return;
 				if ( processedUUIDs ) processedUUIDs.add( object.uuid );
@@ -271,6 +276,21 @@
 					observedRenderers.push( obj );
 					devTools.objects.set( obj.uuid, data );
 
+					// Intercept render method to track last camera
+					if ( ! obj.__devtools_render_wrapped ) {
+
+						const originalRender = obj.render;
+						obj.render = function ( scene, camera ) {
+
+							devTools.lastCamera = camera;
+							return originalRender.call( this, scene, camera );
+
+						};
+
+						obj.__devtools_render_wrapped = true;
+
+					}
+
 					dispatchEvent( EVENT_RENDERER, data );
 
 				}
@@ -374,7 +394,7 @@
 
 			if ( window.THREE && window.THREE.REVISION ) {
 
-				dispatchEvent( EVENT_REGISTER, { revision: THREE.REVISION } );
+				dispatchEvent( EVENT_REGISTER, { revision: window.THREE.REVISION } );
 
 			}
 
@@ -409,6 +429,14 @@
 
 				scrollToCanvas( message.uuid );
 
+			} else if ( message.name === MESSAGE_HIGHLIGHT_OBJECT ) {
+
+				devTools.dispatchEvent( new CustomEvent( 'highlight-object', { detail: { uuid: message.uuid } } ) );
+
+			} else if ( message.name === MESSAGE_UNHIGHLIGHT_OBJECT ) {
+
+				devTools.dispatchEvent( new CustomEvent( 'unhighlight-object' ) );
+
 			}
 
 		} );
@@ -441,6 +469,9 @@
 
 			for ( const scene of observedScenes ) {
 
+				// Check if we're looking for the scene itself
+				if ( scene.uuid === uuid ) return scene;
+
 				const found = scene.getObjectByProperty( 'uuid', uuid );
 				if ( found ) return found;
 
@@ -450,6 +481,15 @@
 
 		}
 
+		// Expose utilities for highlight.js in a clean namespace
+		devTools.utils = {
+			findObjectInScenes,
+			generateUUID
+		};
+
+		// Expose renderers array for highlight.js
+		devTools.renderers = observedRenderers;
+
 		function createHighlightOverlay( targetElement ) {
 
 			const overlay = document.createElement( 'div' );
@@ -604,7 +644,6 @@
 
 			if ( currentObjectCount !== previousObjectCount ) {
 
-				console.log( `DevTools: Scene ${scene.uuid} count changed (${previousObjectCount} -> ${currentObjectCount}), dispatching update.` );
 				// Dispatch the batch update for the panel
 				dispatchEvent( EVENT_SCENE, { sceneUuid: scene.uuid, objects: batchObjects } );
 				// Update the cache

+ 25 - 6
devtools/content-script.js

@@ -7,20 +7,33 @@ const MESSAGE_ID = 'three-devtools';
 const MESSAGE_REQUEST_STATE = 'request-state';
 const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
 const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+const MESSAGE_HIGHLIGHT_OBJECT = 'highlight-object';
+const MESSAGE_UNHIGHLIGHT_OBJECT = 'unhighlight-object';
 
 // Inject the bridge script into the main document or a target (e.g., iframe)
 function injectBridge( target = document ) {
 
-	const script = document.createElement( 'script' );
-	script.src = chrome.runtime.getURL( 'bridge.js' );
-	script.onload = function () {
+	const bridgeScript = document.createElement( 'script' );
+	bridgeScript.src = chrome.runtime.getURL( 'bridge.js' );
+	bridgeScript.onload = function () {
 
 		this.remove();
 
+		// Inject highlight.js after bridge.js loads
+		const highlightScript = document.createElement( 'script' );
+		highlightScript.src = chrome.runtime.getURL( 'highlight.js' );
+		highlightScript.onload = function () {
+
+			this.remove();
+
+		};
+
+		( target.head || target.documentElement ).appendChild( highlightScript );
+
 	};
 
-	( target.head || target.documentElement ).appendChild( script );
-	return script;
+	( target.head || target.documentElement ).appendChild( bridgeScript );
+	return bridgeScript;
 
 }
 
@@ -118,7 +131,13 @@ function handleWindowMessage( event ) {
 // Listener for messages from the background script (originating from panel)
 function handleBackgroundMessage( message ) {
 
-	const forwardableMessages = new Set( [ MESSAGE_REQUEST_STATE, MESSAGE_REQUEST_OBJECT_DETAILS, MESSAGE_SCROLL_TO_CANVAS ] );
+	const forwardableMessages = new Set( [
+		MESSAGE_REQUEST_STATE,
+		MESSAGE_REQUEST_OBJECT_DETAILS,
+		MESSAGE_SCROLL_TO_CANVAS,
+		MESSAGE_HIGHLIGHT_OBJECT,
+		MESSAGE_UNHIGHLIGHT_OBJECT
+	] );
 
 	if ( forwardableMessages.has( message.name ) ) {
 

+ 1 - 6
devtools/devtools.js

@@ -3,12 +3,7 @@ try {
 	chrome.devtools.panels.create(
 		'Three.js',
 		null,
-		'panel/panel.html',
-		function () {
-
-			console.log( 'Three.js DevTools panel created' );
-
-		}
+		'panel/panel.html'
 	);
 
 } catch ( error ) {

+ 224 - 0
devtools/highlight.js

@@ -0,0 +1,224 @@
+/* global __THREE_DEVTOOLS__ */
+
+// This script handles highlighting of Three.js objects in the 3D scene
+
+( function () {
+
+	'use strict';
+
+	let highlightObject = null;
+
+	function cloneMaterial( material ) {
+
+		// Handle ShaderMaterial and RawShaderMaterial with custom yellow shaders
+		if ( material.isShaderMaterial || material.isRawShaderMaterial ) {
+
+			const vertexShader = material.isRawShaderMaterial ?
+				`
+				attribute vec3 position;
+				uniform mat4 modelViewMatrix;
+				uniform mat4 projectionMatrix;
+				void main() {
+					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+				}
+				` :
+				`
+				void main() {
+					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+				}
+				`;
+
+			const fragmentShader = material.isRawShaderMaterial ?
+				`
+				precision highp float;
+				void main() {
+					gl_FragColor = vec4( 1.0, 1.0, 0.0, 1.0 );
+				}
+				` :
+				`
+				void main() {
+					gl_FragColor = vec4( 1.0, 1.0, 0.0, 1.0 );
+				}
+				`;
+
+			// Create new shader material with solid yellow color
+			const cloned = new material.constructor( {
+				vertexShader: vertexShader,
+				fragmentShader: fragmentShader,
+				wireframe: true,
+				depthTest: false,
+				depthWrite: false,
+				transparent: true,
+				opacity: 1
+			} );
+
+			return cloned;
+
+		}
+
+		// Clone the material
+		const cloned = material.clone();
+
+		// Set yellow color
+		if ( cloned.color ) {
+
+			cloned.color.r = 1;
+			cloned.color.g = 1;
+			cloned.color.b = 0;
+
+		}
+
+		// If material has emissive, set it to yellow
+		if ( 'emissive' in cloned ) {
+
+			cloned.emissive.r = 1;
+			cloned.emissive.g = 1;
+			cloned.emissive.b = 0;
+
+		}
+
+		// Enable wireframe if the material supports it
+		if ( 'wireframe' in cloned ) {
+
+			cloned.wireframe = true;
+
+		}
+
+		// Disable vertex colors
+		cloned.vertexColors = false;
+
+		// Set to front side only (0 = FrontSide) if material supports it
+		if ( 'side' in cloned ) {
+
+			cloned.side = 0;
+
+		}
+
+		// Clear all texture maps
+		if ( 'map' in cloned ) cloned.map = null;
+		if ( 'lightMap' in cloned ) cloned.lightMap = null;
+		if ( 'aoMap' in cloned ) cloned.aoMap = null;
+		if ( 'emissiveMap' in cloned ) cloned.emissiveMap = null;
+		if ( 'bumpMap' in cloned ) cloned.bumpMap = null;
+		if ( 'normalMap' in cloned ) cloned.normalMap = null;
+		if ( 'displacementMap' in cloned ) cloned.displacementMap = null;
+		if ( 'roughnessMap' in cloned ) cloned.roughnessMap = null;
+		if ( 'metalnessMap' in cloned ) cloned.metalnessMap = null;
+		if ( 'alphaMap' in cloned ) cloned.alphaMap = null;
+		if ( 'envMap' in cloned ) cloned.envMap = null;
+
+		// Disable clipping
+		if ( cloned.clippingPlanes ) {
+
+			cloned.clippingPlanes = [];
+
+		}
+
+		// Render on top, ignoring depth
+		cloned.depthTest = false;
+		cloned.depthWrite = false;
+		cloned.transparent = true;
+		cloned.opacity = 1;
+
+		// Disable tone mapping and fog
+		cloned.toneMapped = false;
+		cloned.fog = false;
+
+		return cloned;
+
+	}
+
+	function highlight( uuid ) {
+
+		const object = __THREE_DEVTOOLS__.utils.findObjectInScenes( uuid );
+		if ( ! object ) {
+
+			// Object not in scene (e.g., renderer) - hide highlight
+			if ( highlightObject ) highlightObject.visible = false;
+			return;
+
+		}
+
+		// Skip helpers, existing highlights, and objects without geometry
+		if ( object.type.includes( 'Helper' ) || object.name === '__THREE_DEVTOOLS_HIGHLIGHT__' || ! object.geometry ) {
+
+			if ( highlightObject ) highlightObject.visible = false;
+			return;
+
+		}
+
+		// Remove old highlight if it exists
+		if ( highlightObject && highlightObject.parent ) {
+
+			highlightObject.parent.remove( highlightObject );
+
+		}
+
+		// Clone the object to preserve all properties (skeleton, bindMatrix, etc)
+		highlightObject = object.clone();
+		highlightObject.name = '__THREE_DEVTOOLS_HIGHLIGHT__';
+
+		// Apply yellow wireframe material
+		if ( highlightObject.material ) {
+
+			if ( Array.isArray( highlightObject.material ) ) {
+
+				highlightObject.material = highlightObject.material.map( cloneMaterial );
+
+			} else {
+
+				highlightObject.material = cloneMaterial( highlightObject.material );
+
+			}
+
+		}
+
+		// Disable shadows
+		highlightObject.castShadow = false;
+		highlightObject.receiveShadow = false;
+
+		// Render on top of everything
+		highlightObject.renderOrder = Infinity;
+
+		// Disable auto update before adding to scene
+		highlightObject.matrixAutoUpdate = false;
+		highlightObject.matrixWorldAutoUpdate = false;
+
+		// Find the scene and add at root
+		let scene = object;
+		while ( scene.parent ) scene = scene.parent;
+
+		scene.add( highlightObject );
+
+		// Reuse the matrixWorld from original object (after adding to scene)
+		highlightObject.matrixWorld = object.matrixWorld;
+
+		// Make sure it's visible
+		highlightObject.visible = true;
+
+	}
+
+	function unhighlight() {
+
+		if ( highlightObject ) {
+
+			highlightObject.visible = false;
+
+		}
+
+	}
+
+	// Listen for highlight events from bridge.js
+	__THREE_DEVTOOLS__.addEventListener( 'highlight-object', ( event ) => {
+
+		highlight( event.detail.uuid );
+
+	} );
+
+	__THREE_DEVTOOLS__.addEventListener( 'unhighlight-object', () => {
+
+		unhighlight();
+
+	} );
+
+} )();

+ 1 - 1
devtools/manifest.json

@@ -19,7 +19,7 @@
 		"run_at": "document_start"
 	}],
 	"web_accessible_resources": [{
-		"resources": ["bridge.js"],
+		"resources": ["bridge.js", "highlight.js"],
 		"matches": ["<all_urls>"]
 	}],
 	"permissions": [

+ 36 - 13
devtools/panel/panel.js

@@ -7,6 +7,8 @@ const MESSAGE_INIT = 'init';
 const MESSAGE_REQUEST_STATE = 'request-state';
 const MESSAGE_REQUEST_OBJECT_DETAILS = 'request-object-details';
 const MESSAGE_SCROLL_TO_CANVAS = 'scroll-to-canvas';
+const MESSAGE_HIGHLIGHT_OBJECT = 'highlight-object';
+const MESSAGE_UNHIGHLIGHT_OBJECT = 'unhighlight-object';
 const EVENT_REGISTER = 'register';
 const EVENT_RENDERER = 'renderer';
 const EVENT_OBJECT_DETAILS = 'object-details';
@@ -123,7 +125,6 @@ const intervalId = setInterval( () => {
 
 backgroundPageConnection.onDisconnect.addListener( () => {
 
-	console.log( 'Panel: Connection to background page lost' );
 	clearInterval( intervalId );
 	clearState();
 
@@ -138,6 +139,23 @@ function requestObjectDetails( uuid ) {
 	} );
 }
 
+// Function to highlight object in 3D scene
+function requestObjectHighlight( uuid ) {
+	backgroundPageConnection.postMessage( {
+		name: MESSAGE_HIGHLIGHT_OBJECT,
+		uuid: uuid,
+		tabId: chrome.devtools.inspectedWindow.tabId
+	} );
+}
+
+// Function to remove highlight from 3D scene
+function requestObjectUnhighlight() {
+	backgroundPageConnection.postMessage( {
+		name: MESSAGE_UNHIGHLIGHT_OBJECT,
+		tabId: chrome.devtools.inspectedWindow.tabId
+	} );
+}
+
 
 // Store renderer collapse states
 const rendererCollapsedState = new Map();
@@ -300,6 +318,7 @@ function handleThreeEvent( message ) {
 
 		case EVENT_REGISTER:
 			state.revision = message.detail.revision;
+			updateUI();
 			break;
 
 		// Handle individual renderer observation
@@ -309,32 +328,31 @@ function handleThreeEvent( message ) {
 			// Update or add the renderer in the state maps (always use latest data)
 			state.renderers.set( detail.uuid, detail );
 			state.objects.set( detail.uuid, detail );
-
+			updateUI();
 			break;
 
 		// Handle object details response
 		case EVENT_OBJECT_DETAILS:
 			state.selectedObject = message.detail;
-			console.log( 'Panel: Received object details:', message.detail );
 			showFloatingDetails( message.detail );
+			// Don't call updateUI() - this doesn't change the tree structure
 			break;
 
 		// Handle a batch of objects for a specific scene
 		case EVENT_SCENE:
 			const { sceneUuid, objects: batchObjects } = message.detail;
-			console.log( 'Panel: Received scene batch for', sceneUuid, 'with', batchObjects.length, 'objects' );
 			processSceneBatch( sceneUuid, batchObjects );
+			updateUI();
 			break;
 
 		case EVENT_COMMITTED:
 			// Page was reloaded, clear state
 			clearState();
+			updateUI();
 			break;
 
 	}
 
-	updateUI();
-
 }
 
 function renderRenderer( obj, container ) {
@@ -447,7 +465,7 @@ function renderObject( obj, container, level = 0 ) {
 		function countObjects( uuid ) {
 
 			const object = state.objects.get( uuid );
-			if ( object ) {
+			if ( object && object.name !== '__THREE_DEVTOOLS_HIGHLIGHT__' ) {
 
 				objectCount ++; // Increment count for the object itself
 				if ( object.children ) {
@@ -469,13 +487,18 @@ function renderObject( obj, container, level = 0 ) {
 	}
 
 	elem.innerHTML = labelContent;
-	
-	// Add mouseover handler to request object details
-	elem.addEventListener( 'mouseover', ( event ) => {
-		event.stopPropagation(); // Prevent bubbling to parent elements
+
+	// Add mouseenter handler to request object details and highlight in 3D
+	elem.addEventListener( 'mouseenter', () => {
 		requestObjectDetails( obj.uuid );
+		requestObjectHighlight( obj.uuid );
 	} );
-	
+
+	// Add mouseleave handler to remove 3D highlight
+	elem.addEventListener( 'mouseleave', () => {
+		requestObjectUnhighlight();
+	} );
+
 	container.appendChild( elem );
 
 	// Handle children (excluding children of renderers, as properties are shown in details)
@@ -489,7 +512,7 @@ function renderObject( obj, container, level = 0 ) {
 		// Get all children and sort them by type for better organization
 		const children = obj.children
 			.map( childId => state.objects.get( childId ) )
-			.filter( child => child !== undefined )
+			.filter( child => child !== undefined && child.name !== '__THREE_DEVTOOLS_HIGHLIGHT__' )
 			.sort( ( a, b ) => {
 
 				const getTypeOrder = ( obj ) => {

粤ICP备19079148号