Agent.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. import { GoogleGenAI } from '@google/genai';
  2. import * as Commands from './commands/Commands.js';
  3. import { Vector3, BoxGeometry, SphereGeometry, MeshStandardMaterial, Mesh, DirectionalLight, PointLight, AmbientLight, Color, CylinderGeometry } from 'three';
  4. class Agent {
  5. constructor( editor ) {
  6. this.editor = editor;
  7. this.container = new THREE.Group();
  8. this.dom = document.createElement( 'div' );
  9. this.dom.id = 'agent';
  10. this.lastModifiedObject = null; // Track last modified object
  11. // Create UI elements
  12. this.createUI();
  13. // Initialize signals
  14. this.signals = {
  15. agentResponse: new signals.Signal(),
  16. agentThinking: new signals.Signal()
  17. };
  18. // Bind methods
  19. this.processQuery = this.processQuery.bind( this );
  20. this.executeCommand = this.executeCommand.bind( this );
  21. this.generateRandomColor = this.generateRandomColor.bind( this );
  22. this.generateUniqueObjectName = this.generateUniqueObjectName.bind( this );
  23. this.editor.signals.storageLoaded.add( () => {
  24. this.init();
  25. } );
  26. }
  27. generateUniqueObjectName( baseName ) {
  28. const scene = this.editor.scene;
  29. let counter = 1;
  30. let name;
  31. // Keep incrementing counter until we find an unused name
  32. do {
  33. name = `${baseName}${counter}`;
  34. counter ++;
  35. } while ( scene.getObjectByName( name ) !== undefined );
  36. return name;
  37. }
  38. generateRandomColor() {
  39. const randomHex = Math.floor( Math.random() * 16777215 ).toString( 16 );
  40. return '#' + randomHex.padStart( 6, '0' );
  41. }
  42. createUI() {
  43. // Create input area
  44. const input = document.createElement( 'textarea' );
  45. input.placeholder = 'What do you want to do?';
  46. // Prevent keyboard shortcuts when focused
  47. input.addEventListener( 'keydown', ( e ) => {
  48. e.stopPropagation();
  49. if ( e.key === 'Enter' ) {
  50. if ( e.shiftKey ) {
  51. // Allow Shift+Enter for newlines
  52. return;
  53. }
  54. e.preventDefault();
  55. executeQuery();
  56. }
  57. } );
  58. // Create submit button
  59. const button = document.createElement( 'button' );
  60. button.textContent = 'SEND';
  61. const executeQuery = async () => {
  62. if ( button.disabled || ! input.value.trim() ) return;
  63. button.disabled = true;
  64. input.disabled = true;
  65. await this.processQuery( input.value );
  66. input.value = '';
  67. button.disabled = false;
  68. input.disabled = false;
  69. input.focus();
  70. };
  71. // Add event listeners
  72. button.addEventListener( 'click', executeQuery );
  73. // Append elements
  74. this.dom.appendChild( input );
  75. this.dom.appendChild( button );
  76. }
  77. async init() {
  78. // Initialize Google AI
  79. const ai = new GoogleGenAI( { apiKey: 'GEMINI_API_KEY' } );
  80. // Get scene information
  81. const sceneInfo = this.getSceneInfo();
  82. // Prepare prompt
  83. const systemPrompt = `You are a Three.js scene manipulation assistant. Current scene info:
  84. ${JSON.stringify( sceneInfo, null, 2 )}
  85. Available commands:
  86. - AddObject: Add a new object to the scene
  87. Types: box/cube, sphere, directionalLight, pointLight, ambientLight, cylinder
  88. Box parameters:
  89. - width, height, depth (default: 1)
  90. - widthSegments, heightSegments, depthSegments (default: 1) - controls geometry detail
  91. Sphere parameters:
  92. - radius (default: 0.5)
  93. - widthSegments (default: 32) - horizontal detail
  94. - heightSegments (default: 16) - vertical detail
  95. Cylinder parameters:
  96. - radiusTop (default: 0.5)
  97. - radiusBottom (default: 0.5)
  98. - height (default: 1)
  99. - radialSegments (default: 32) - horizontal detail
  100. - heightSegments (default: 1) - vertical detail
  101. - openEnded (default: false)
  102. DirectionalLight parameters:
  103. - color (default: white)
  104. - intensity (default: 1)
  105. PointLight parameters:
  106. - color (default: white)
  107. - intensity (default: 1)
  108. - distance (default: 0)
  109. - decay (default: 2)
  110. AmbientLight parameters:
  111. - color (default: white)
  112. - intensity (default: 1)
  113. Common parameters for all:
  114. - color (use simple color names like "red" or hex values like "#ff0000" - do not use functions or dynamic values)
  115. - position (e.g. {x: 0, y: 5, z: 0})
  116. - SetPosition: Set object position
  117. Parameters:
  118. - object: name of the object to move (optional - defaults to last modified object)
  119. - position: {x, y, z} (omitted coordinates keep current values)
  120. Example: Move right = {x: 2}, Move up = {y: 2}
  121. - SetMaterialColor: Change object material color
  122. Parameters:
  123. - object: name of the object (optional - defaults to last modified object)
  124. - color: color value (e.g. "red", "#ff0000", or "random" for a random color)
  125. Note: Use "random" keyword for random colors, do not use JavaScript expressions
  126. - SetScale: Change object size
  127. Parameters:
  128. - object: name of the object (optional - defaults to last modified object)
  129. - scale: {x, y, z} (values > 1 make bigger, < 1 make smaller)
  130. Example: Double size = {x: 2, y: 2, z: 2}
  131. Example: Half size = {x: 0.5, y: 0.5, z: 0.5}
  132. - SetMaterialValue: Set material property value
  133. Parameters:
  134. - object: name of the object (optional - defaults to last modified object)
  135. - property: material property to set (e.g. "wireframe")
  136. - value: value to set
  137. - SetRotation: Set object rotation
  138. Parameters:
  139. - object: name of the object (optional - defaults to last modified object)
  140. - rotation: {x, y, z} in radians
  141. - SetGeometry: Modify object geometry detail
  142. Parameters:
  143. - object: name of the object to modify (optional - defaults to last modified object)
  144. - widthSegments: number of segments along width (for box/sphere)
  145. - heightSegments: number of segments along height (for box/sphere)
  146. - depthSegments: number of segments along depth (for box only)
  147. Example: High detail sphere = { widthSegments: 64, heightSegments: 32 }
  148. Example: High detail box = { widthSegments: 4, heightSegments: 4, depthSegments: 4 }
  149. - RemoveObject: Remove an object from the scene
  150. Parameters:
  151. - object: name of the object to remove
  152. - MultiCmds: Execute multiple commands in sequence
  153. Parameters:
  154. - commands: array of command objects
  155. Example - Create multiple objects:
  156. {
  157. "type": "MultiCmds",
  158. "params": {
  159. "commands": [
  160. {
  161. "type": "AddObject",
  162. "params": {
  163. "type": "cube",
  164. "name": "Cube1",
  165. "position": {"x": -1.5}
  166. }
  167. },
  168. {
  169. "type": "AddObject",
  170. "params": {
  171. "type": "cube",
  172. "name": "Cube2",
  173. "position": {"x": -0.5}
  174. }
  175. },
  176. {
  177. "type": "AddObject",
  178. "params": {
  179. "type": "cube",
  180. "name": "Cube3",
  181. "position": {"x": 0.5}
  182. }
  183. },
  184. {
  185. "type": "AddObject",
  186. "params": {
  187. "type": "cube",
  188. "name": "Cube4",
  189. "position": {"x": 1.5}
  190. }
  191. }
  192. ]
  193. }
  194. }
  195. Example - Create and modify an object:
  196. {
  197. "type": "MultiCmds",
  198. "params": {
  199. "commands": [
  200. {
  201. "type": "AddObject",
  202. "params": { "type": "cube", "name": "MyCube" }
  203. },
  204. {
  205. "type": "SetMaterialColor",
  206. "params": { "object": "MyCube", "color": "red" }
  207. },
  208. {
  209. "type": "SetScale",
  210. "params": { "object": "MyCube", "scale": {"x": 2, "y": 2, "z": 2} }
  211. }
  212. ]
  213. }
  214. }
  215. Example - Modify all objects in the scene:
  216. {
  217. "type": "MultiCmds",
  218. "params": {
  219. "commands": [
  220. {
  221. "type": "SetMaterialColor",
  222. "params": { "object": "Box1", "color": "red" }
  223. },
  224. {
  225. "type": "SetMaterialColor",
  226. "params": { "object": "Box2", "color": "blue" }
  227. }
  228. ]
  229. }
  230. }
  231. Note: Use MultiCmds when you need to:
  232. 1. Create multiple objects at once
  233. 2. Apply multiple modifications to a single object
  234. 3. Apply modifications to multiple objects
  235. 4. Any combination of the above
  236. Important: When working with multiple similar objects (e.g. multiple spheres):
  237. - Objects are automatically numbered (e.g. "Sphere1", "Sphere2", etc.)
  238. - Use the exact object name including the number when targeting specific objects
  239. - To modify all objects of a type, create a MultiCmds command with one command per object
  240. - The scene info includes:
  241. - objectCounts: how many of each type exist
  242. - objectsByType: groups of objects by their base name
  243. - spheres: list of all sphere names
  244. - boxes: list of all box names
  245. - cylinders: list of all cylinder names
  246. - directionalLights: list of all directional light names
  247. - pointLights: list of all point light names
  248. - ambientLights: list of all ambient light names
  249. Example - Set random colors for all spheres:
  250. {
  251. "type": "MultiCmds",
  252. "params": {
  253. "commands": [
  254. {
  255. "type": "SetMaterialColor",
  256. "params": { "object": "Sphere1", "color": "random" }
  257. },
  258. {
  259. "type": "SetMaterialColor",
  260. "params": { "object": "Sphere2", "color": "random" }
  261. }
  262. ]
  263. }
  264. }
  265. Respond ONLY with a JSON object in this format:
  266. {
  267. "response": "Your text response to the user explaining what you're doing",
  268. "commands": {
  269. "type": "command_type",
  270. "params": {
  271. // command specific parameters
  272. }
  273. }
  274. }
  275. Important:
  276. 1. If no commands are needed, set "commands" to null
  277. 2. Do not include any JavaScript expressions or functions in the JSON
  278. 3. For random colors, use the "random" keyword instead of Math.random()
  279. 4. Do not include any other text outside the JSON
  280. Do not include any other text outside the JSON.`;
  281. this.chat = await ai.chats.create({
  282. model: "gemini-2.0-flash",
  283. history: [
  284. {
  285. role: "user",
  286. parts: [{ text: systemPrompt }],
  287. },
  288. {
  289. role: "model",
  290. parts: [
  291. {
  292. text: "I'm ready to help you create and modify your 3D scene.",
  293. },
  294. ],
  295. },
  296. ],
  297. config: {
  298. temperature: 0.2,
  299. maxOutputTokens: 2048
  300. },
  301. });
  302. console.log( 'CHAT:', this.chat );
  303. }
  304. async processQuery( query ) {
  305. if ( ! query.trim() ) return;
  306. try {
  307. this.signals.agentThinking.dispatch();
  308. const response = await this.chat.sendMessage( { message: query } );
  309. let responseData;
  310. try {
  311. // Strip markdown code block markers if present
  312. const cleanText = response.text.replace( /^```json\n|\n```$/g, '' )
  313. .replace( /^\s*```\s*|\s*```\s*$/g, '' ) // Remove any remaining code block markers
  314. .trim();
  315. try {
  316. // First try parsing as is
  317. responseData = JSON.parse( cleanText );
  318. } catch ( e ) {
  319. // If that fails, try to fix common JSON issues
  320. const fixedText = cleanText
  321. .replace( /,\s*([}\]])/g, '$1' ) // Remove trailing commas
  322. .replace( /([a-zA-Z0-9])\s*:\s*/g, '"$1": ' ) // Quote unquoted keys
  323. .replace( /\n/g, ' ' ) // Remove newlines
  324. .replace( /\s+/g, ' ' ); // Normalize whitespace
  325. responseData = JSON.parse( fixedText );
  326. }
  327. } catch ( e ) {
  328. console.error( 'AGENT: Failed to parse AI response as JSON:', e );
  329. console.error( 'AGENT: Raw response:', response.text );
  330. return;
  331. }
  332. // Execute commands if present
  333. if ( responseData.commands ) {
  334. try {
  335. await this.executeCommand( responseData.commands );
  336. } catch ( e ) {
  337. console.error( 'AGENT: Failed to execute commands:', e );
  338. }
  339. }
  340. // Log the response
  341. console.log( 'AGENT:', responseData.response );
  342. this.signals.agentResponse.dispatch( responseData.response );
  343. } catch ( error ) {
  344. console.error( 'AGENT: Agent error:', error );
  345. }
  346. }
  347. async executeCommand( commandData ) {
  348. if ( ! commandData.type || ! Commands[ commandData.type + 'Command' ] ) {
  349. console.error( 'AGENT: Invalid command type:', commandData.type );
  350. return;
  351. }
  352. let command;
  353. // Helper to get target object, falling back to last modified
  354. const getTargetObject = ( objectName ) => {
  355. if ( objectName ) {
  356. const object = this.editor.scene.getObjectByName( objectName );
  357. if ( object ) {
  358. this.lastModifiedObject = object;
  359. return object;
  360. }
  361. }
  362. return this.lastModifiedObject;
  363. };
  364. const createMaterial = ( params ) => {
  365. const material = new MeshStandardMaterial();
  366. if ( params.color ) {
  367. material.color.set( params.color );
  368. }
  369. return material;
  370. };
  371. const setPosition = ( object, position ) => {
  372. if ( position ) {
  373. object.position.set(
  374. position.x ?? 0,
  375. position.y ?? 0,
  376. position.z ?? 0
  377. );
  378. }
  379. };
  380. switch ( commandData.type ) {
  381. case 'AddObject':
  382. const type = commandData.params.type?.toLowerCase();
  383. if ( type === 'box' || type === 'cube' ) {
  384. const width = commandData.params.width ?? 1;
  385. const height = commandData.params.height ?? 1;
  386. const depth = commandData.params.depth ?? 1;
  387. const widthSegments = commandData.params.widthSegments ?? 1;
  388. const heightSegments = commandData.params.heightSegments ?? 1;
  389. const depthSegments = commandData.params.depthSegments ?? 1;
  390. const geometry = new BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
  391. const mesh = new Mesh( geometry, createMaterial( commandData.params ) );
  392. mesh.name = commandData.params.name || this.generateUniqueObjectName( 'Box' );
  393. setPosition( mesh, commandData.params.position );
  394. command = new Commands.AddObjectCommand( this.editor, mesh );
  395. this.lastModifiedObject = mesh;
  396. } else if ( type === 'sphere' ) {
  397. const radius = commandData.params.radius ?? 0.5;
  398. const widthSegments = commandData.params.widthSegments ?? 32;
  399. const heightSegments = commandData.params.heightSegments ?? 16;
  400. const geometry = new SphereGeometry( radius, widthSegments, heightSegments );
  401. const mesh = new Mesh( geometry, createMaterial( commandData.params ) );
  402. mesh.name = commandData.params.name || this.generateUniqueObjectName( 'Sphere' );
  403. setPosition( mesh, commandData.params.position );
  404. command = new Commands.AddObjectCommand( this.editor, mesh );
  405. this.lastModifiedObject = mesh;
  406. } else if ( type === 'cylinder' ) {
  407. const radiusTop = commandData.params.radiusTop ?? 0.5;
  408. const radiusBottom = commandData.params.radiusBottom ?? 0.5;
  409. const height = commandData.params.height ?? 1;
  410. const radialSegments = commandData.params.radialSegments ?? 32;
  411. const heightSegments = commandData.params.heightSegments ?? 1;
  412. const openEnded = commandData.params.openEnded ?? false;
  413. const geometry = new CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded );
  414. const mesh = new Mesh( geometry, createMaterial( commandData.params ) );
  415. mesh.name = commandData.params.name || this.generateUniqueObjectName( 'Cylinder' );
  416. setPosition( mesh, commandData.params.position );
  417. command = new Commands.AddObjectCommand( this.editor, mesh );
  418. this.lastModifiedObject = mesh;
  419. } else if ( type === 'directionallight' ) {
  420. const color = commandData.params.color || 0xffffff;
  421. const intensity = commandData.params.intensity ?? 1;
  422. const light = new DirectionalLight( color, intensity );
  423. light.name = commandData.params.name || this.generateUniqueObjectName( 'DirectionalLight' );
  424. setPosition( light, commandData.params.position );
  425. command = new Commands.AddObjectCommand( this.editor, light );
  426. this.lastModifiedObject = light;
  427. } else if ( type === 'pointlight' ) {
  428. const color = commandData.params.color || 0xffffff;
  429. const intensity = commandData.params.intensity ?? 1;
  430. const distance = commandData.params.distance ?? 0;
  431. const decay = commandData.params.decay ?? 2;
  432. const light = new PointLight( color, intensity, distance, decay );
  433. light.name = commandData.params.name || this.generateUniqueObjectName( 'PointLight' );
  434. setPosition( light, commandData.params.position );
  435. command = new Commands.AddObjectCommand( this.editor, light );
  436. this.lastModifiedObject = light;
  437. } else if ( type === 'ambientlight' ) {
  438. const color = commandData.params.color || 0xffffff;
  439. const intensity = commandData.params.intensity ?? 1;
  440. const light = new AmbientLight( color, intensity );
  441. light.name = commandData.params.name || this.generateUniqueObjectName( 'AmbientLight' );
  442. command = new Commands.AddObjectCommand( this.editor, light );
  443. this.lastModifiedObject = light;
  444. } else {
  445. console.warn( 'AGENT: Unsupported object type:', type );
  446. }
  447. break;
  448. case 'SetPosition':
  449. const positionObject = getTargetObject( commandData.params.object );
  450. if ( positionObject && commandData.params.position ) {
  451. const currentPos = positionObject.position;
  452. const newPosition = new Vector3(
  453. commandData.params.position.x ?? currentPos.x,
  454. commandData.params.position.y ?? currentPos.y,
  455. commandData.params.position.z ?? currentPos.z
  456. );
  457. command = new Commands.SetPositionCommand( this.editor, positionObject, newPosition );
  458. }
  459. break;
  460. case 'SetRotation':
  461. const rotationObject = getTargetObject( commandData.params.object );
  462. if ( rotationObject && commandData.params.rotation ) {
  463. const rot = commandData.params.rotation;
  464. const currentRot = rotationObject.rotation;
  465. const newRotation = new Vector3(
  466. rot.x ?? currentRot.x,
  467. rot.y ?? currentRot.y,
  468. rot.z ?? currentRot.z
  469. );
  470. command = new Commands.SetRotationCommand( this.editor, rotationObject, newRotation );
  471. }
  472. break;
  473. case 'SetScale':
  474. const scaleObject = getTargetObject( commandData.params.object );
  475. if ( scaleObject && commandData.params.scale ) {
  476. const scale = commandData.params.scale;
  477. const newScale = new Vector3( scale.x || 1, scale.y || 1, scale.z || 1 );
  478. command = new Commands.SetScaleCommand( this.editor, scaleObject, newScale );
  479. }
  480. break;
  481. case 'SetMaterialColor':
  482. const colorObject = getTargetObject( commandData.params.object );
  483. if ( colorObject && colorObject.material && commandData.params.color ) {
  484. let colorValue = commandData.params.color;
  485. // If color is "random", generate a random color
  486. if ( colorValue === 'random' ) {
  487. colorValue = this.generateRandomColor();
  488. }
  489. const color = new Color( colorValue );
  490. command = new Commands.SetMaterialColorCommand( this.editor, colorObject, 'color', color.getHex() );
  491. }
  492. break;
  493. case 'SetMaterialValue':
  494. const materialObject = getTargetObject( commandData.params.object );
  495. if ( materialObject && materialObject.material && commandData.params.property ) {
  496. const value = commandData.params.value ?? true;
  497. command = new Commands.SetMaterialValueCommand( this.editor, materialObject, commandData.params.property, value );
  498. }
  499. break;
  500. case 'SetGeometry':
  501. const detailObject = getTargetObject( commandData.params.object );
  502. if ( detailObject && detailObject.geometry ) {
  503. const params = commandData.params;
  504. let newGeometry;
  505. if ( detailObject.geometry instanceof BoxGeometry ) {
  506. const box = detailObject.geometry;
  507. newGeometry = new BoxGeometry(
  508. box.parameters.width ?? 1,
  509. box.parameters.height ?? 1,
  510. box.parameters.depth ?? 1,
  511. params.widthSegments ?? 1,
  512. params.heightSegments ?? 1,
  513. params.depthSegments ?? 1
  514. );
  515. } else if ( detailObject.geometry instanceof SphereGeometry ) {
  516. const sphere = detailObject.geometry;
  517. newGeometry = new SphereGeometry(
  518. sphere.parameters.radius ?? 0.5,
  519. params.widthSegments ?? 32,
  520. params.heightSegments ?? 16
  521. );
  522. } else if ( detailObject.geometry instanceof CylinderGeometry ) {
  523. const cylinder = detailObject.geometry;
  524. newGeometry = new CylinderGeometry(
  525. params.radiusTop ?? cylinder.parameters.radiusTop ?? 0.5,
  526. params.radiusBottom ?? cylinder.parameters.radiusBottom ?? 0.5,
  527. params.height ?? cylinder.parameters.height ?? 1,
  528. params.radialSegments ?? cylinder.parameters.radialSegments ?? 32,
  529. params.heightSegments ?? cylinder.parameters.heightSegments ?? 1,
  530. params.openEnded ?? cylinder.parameters.openEnded ?? false
  531. );
  532. }
  533. if ( newGeometry ) {
  534. command = new Commands.SetGeometryCommand( this.editor, detailObject, newGeometry );
  535. }
  536. }
  537. break;
  538. case 'RemoveObject':
  539. const removeObject = getTargetObject( commandData.params.object );
  540. if ( removeObject ) {
  541. command = new Commands.RemoveObjectCommand( this.editor, removeObject );
  542. this.lastModifiedObject = null;
  543. }
  544. break;
  545. case 'MultiCmds':
  546. if ( Array.isArray( commandData.params.commands ) ) {
  547. const commands = [];
  548. for ( const cmd of commandData.params.commands ) {
  549. const subCommand = await this.executeCommand( cmd );
  550. if ( subCommand ) commands.push( subCommand );
  551. }
  552. command = new Commands.MultiCmdsCommand( this.editor, commands );
  553. }
  554. break;
  555. default:
  556. console.warn( 'AGENT: Unsupported command type:', commandData.type, '- Available commands are: AddObject, SetPosition, SetRotation, SetScale, SetMaterialColor, SetMaterialValue, SetGeometry, RemoveObject, MultiCmds' );
  557. break;
  558. }
  559. console.log( 'AGENT: Command:', command );
  560. if ( command ) {
  561. this.editor.execute( command );
  562. }
  563. return command;
  564. }
  565. getSceneInfo() {
  566. const scene = this.editor.scene;
  567. // Helper to get all objects of a specific type
  568. const getObjectsByType = ( type ) => {
  569. return scene.children.filter( obj => {
  570. const baseName = obj.name.replace( /\d+$/, '' );
  571. return baseName.toLowerCase() === type.toLowerCase();
  572. } ).map( obj => obj.name );
  573. };
  574. // Get base names and their counts
  575. const nameCount = {};
  576. const objectsByType = {};
  577. scene.children.forEach( obj => {
  578. const baseName = obj.name.replace( /\d+$/, '' ); // Remove trailing numbers
  579. nameCount[ baseName ] = ( nameCount[ baseName ] || 0 ) + 1;
  580. // Group objects by their base name
  581. if ( ! objectsByType[ baseName ] ) {
  582. objectsByType[ baseName ] = [];
  583. }
  584. objectsByType[ baseName ].push( obj.name );
  585. } );
  586. const objects = scene.children.map( obj => ( {
  587. type: obj.type,
  588. name: obj.name,
  589. baseName: obj.name.replace( /\d+$/, '' ), // Add base name
  590. position: obj.position,
  591. rotation: obj.rotation,
  592. scale: obj.scale,
  593. isMesh: obj.isMesh,
  594. isLight: obj.isLight,
  595. material: obj.material ? {
  596. type: obj.material.type,
  597. color: obj.material.color ? '#' + obj.material.color.getHexString() : undefined
  598. } : undefined
  599. } ) );
  600. return {
  601. objects,
  602. meshes: objects.filter( obj => obj.isMesh ),
  603. lights: objects.filter( obj => obj.isLight ),
  604. materials: Object.keys( this.editor.materials ).length,
  605. cameras: Object.keys( this.editor.cameras ).length,
  606. objectCounts: nameCount, // Add counts of similar objects
  607. objectsByType, // Add grouped objects by type
  608. spheres: getObjectsByType( 'Sphere' ),
  609. boxes: getObjectsByType( 'Box' ),
  610. cylinders: getObjectsByType( 'Cylinder' ),
  611. directionalLights: getObjectsByType( 'DirectionalLight' ),
  612. pointLights: getObjectsByType( 'PointLight' ),
  613. ambientLights: getObjectsByType( 'AmbientLight' )
  614. };
  615. }
  616. clear() {
  617. while ( this.container.children.length > 0 ) {
  618. this.container.remove( this.container.children[ 0 ] );
  619. }
  620. }
  621. }
  622. export { Agent };
粤ICP备19079148号