| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944 |
- import { GoogleGenAI } from '@google/genai';
- import * as Commands from './commands/Commands.js';
- import { Vector3, BoxGeometry, SphereGeometry, MeshStandardMaterial, Mesh, DirectionalLight, PointLight, AmbientLight, Color, CylinderGeometry } from 'three';
- class Agent {
- constructor( editor ) {
- this.editor = editor;
- this.container = new THREE.Group();
- this.dom = document.createElement( 'div' );
- this.dom.id = 'agent';
- this.lastModifiedObject = null; // Track last modified object
- // Create UI elements
- this.createUI();
- // Initialize signals
- this.signals = {
- agentResponse: new signals.Signal(),
- agentThinking: new signals.Signal()
- };
- // Bind methods
- this.processQuery = this.processQuery.bind( this );
- this.executeCommand = this.executeCommand.bind( this );
- this.generateRandomColor = this.generateRandomColor.bind( this );
- this.generateUniqueObjectName = this.generateUniqueObjectName.bind( this );
- this.editor.signals.storageLoaded.add( () => {
- this.init();
- } );
- }
- generateUniqueObjectName( baseName ) {
- const scene = this.editor.scene;
- let counter = 1;
- let name;
- // Keep incrementing counter until we find an unused name
- do {
- name = `${baseName}${counter}`;
- counter ++;
- } while ( scene.getObjectByName( name ) !== undefined );
- return name;
- }
- generateRandomColor() {
- const randomHex = Math.floor( Math.random() * 16777215 ).toString( 16 );
- return '#' + randomHex.padStart( 6, '0' );
- }
- createUI() {
- // Create message bubble
- const messageBubble = document.createElement( 'div' );
- messageBubble.style.display = 'none';
- messageBubble.style.padding = '8px 12px';
- messageBubble.style.borderRadius = '4px';
- messageBubble.style.marginBottom = '8px';
- messageBubble.style.fontSize = '14px';
- messageBubble.style.position = 'relative';
- // Add message container first
- const messageContainer = document.createElement( 'div' );
- messageContainer.className = 'message-text';
- messageContainer.style.marginRight = '20px'; // Make space for the close button
- messageContainer.style.whiteSpace = 'pre-wrap';
- messageBubble.appendChild( messageContainer );
- // Add close button
- const closeButton = document.createElement( 'div' );
- closeButton.innerHTML = '×';
- closeButton.style.position = 'absolute';
- closeButton.style.top = '8px';
- closeButton.style.right = '8px';
- closeButton.style.cursor = 'pointer';
- closeButton.style.fontSize = '18px';
- closeButton.style.lineHeight = '14px';
- closeButton.style.zIndex = '1';
- closeButton.onclick = () => {
- messageBubble.style.display = 'none';
- };
- messageBubble.appendChild( closeButton );
- // Create input area
- const input = document.createElement( 'textarea' );
- input.placeholder = 'What do you want to do?';
- // Prevent keyboard shortcuts when focused
- input.addEventListener( 'keydown', ( e ) => {
- e.stopPropagation();
- if ( e.key === 'Enter' ) {
- if ( e.shiftKey ) {
- // Allow Shift+Enter for newlines
- return;
- }
- e.preventDefault();
- executeQuery();
- }
- } );
- // Create submit button
- const button = document.createElement( 'button' );
- button.textContent = 'SEND';
- const executeQuery = async () => {
- if ( button.disabled || ! input.value.trim() ) return;
- button.disabled = true;
- input.disabled = true;
- await this.processQuery( input.value );
- input.value = '';
- button.disabled = false;
- input.disabled = false;
- input.focus();
- };
- // Add event listeners
- button.addEventListener( 'click', executeQuery );
- // Append elements
- this.dom.appendChild( messageBubble );
- this.dom.appendChild( input );
- this.dom.appendChild( button );
- // Store references
- this.messageBubble = messageBubble;
- }
- showError( message ) {
- this.showMessage( message, 'error' );
- }
- showMessage( message, type = 'normal' ) {
- // Get the message container
- const messageContainer = this.messageBubble.querySelector( '.message-text' );
- const closeButton = this.messageBubble.querySelector( 'div:last-child' );
- // Set styles based on message type
- if ( type === 'error' ) {
- this.messageBubble.style.backgroundColor = '#ffebee';
- this.messageBubble.style.color = '#d32f2f';
- closeButton.style.color = '#d32f2f';
- } else {
- this.messageBubble.style.backgroundColor = '#e8f5e9';
- this.messageBubble.style.color = '#2e7d32';
- closeButton.style.color = '#2e7d32';
- }
- // Update message text
- messageContainer.textContent = message;
- this.messageBubble.style.display = 'block';
- }
- async init() {
- // Initialize Google AI
- const ai = new GoogleGenAI( { apiKey: 'AIzaSyCFpCy19tslP3VeSLAPUM-zQjz6Ka9g5no' } );
- // Get scene information
- const sceneInfo = this.getSceneInfo();
- // Prepare prompt
- const systemPrompt = `You are a Three.js scene manipulation assistant. Current scene info:
-
- ${JSON.stringify( sceneInfo, null, 2 )}
- Available commands:
- - AddObject: Add a new object to the scene
- Types: box/cube, sphere, directionalLight, pointLight, ambientLight, cylinder
- Box parameters:
- - width, height, depth (default: 1)
- - widthSegments, heightSegments, depthSegments (default: 1) - controls geometry detail
- Sphere parameters:
- - radius (default: 0.5)
- - widthSegments (default: 32) - horizontal detail
- - heightSegments (default: 16) - vertical detail
- Cylinder parameters:
- - radiusTop (default: 0.5)
- - radiusBottom (default: 0.5)
- - height (default: 1)
- - radialSegments (default: 32) - horizontal detail
- - heightSegments (default: 1) - vertical detail
- - openEnded (default: false)
- DirectionalLight parameters:
- - color (default: white)
- - intensity (default: 1)
- PointLight parameters:
- - color (default: white)
- - intensity (default: 1)
- - distance (default: 0)
- - decay (default: 2)
- AmbientLight parameters:
- - color (default: white)
- - intensity (default: 1)
- Common parameters for all:
- - color (use simple color names like "red" or hex values like "#ff0000" - do not use functions or dynamic values)
- - position (e.g. {x: 0, y: 5, z: 0})
- - SetPosition: Set object position
- Parameters:
- - object: name of the object to move (optional - defaults to last modified object)
- - position: {x, y, z} (omitted coordinates keep current values)
- Example: Move right = {x: 2}, Move up = {y: 2}
- - SetMaterialColor: Change object material color
- Parameters:
- - object: name of the object (optional - defaults to last modified object)
- - color: color value (e.g. "red", "#ff0000", or "random" for a random color)
- Note: Use "random" keyword for random colors, do not use JavaScript expressions
- - SetScale: Change object size
- Parameters:
- - object: name of the object (optional - defaults to last modified object)
- - scale: {x, y, z} (values > 1 make bigger, < 1 make smaller)
- Example: Double size = {x: 2, y: 2, z: 2}
- Example: Half size = {x: 0.5, y: 0.5, z: 0.5}
- - SetMaterialValue: Set material property value
- Parameters:
- - object: name of the object (optional - defaults to last modified object)
- - property: material property to set (e.g. "metalness", "roughness", "wireframe", "transparent", "opacity")
- - value: value to set (numbers between 0-1 for metalness/roughness/opacity, true/false for wireframe/transparent)
- Example: Make metallic = { property: "metalness", value: 1.0 }
- Example: Make rough = { property: "roughness", value: 1.0 }
- Example: Make reflective = Use MultiCmds to set both metalness=1.0 and roughness=0.0
- Example: Make transparent = { property: "transparent", value: true, opacity: 0.5 }
- Note: For reflective surfaces, combine metalness=1.0 with roughness=0.0 using MultiCmds
- - SetRotation: Set object rotation
- Parameters:
- - object: name of the object (optional - defaults to last modified object)
- - rotation: {x, y, z} in radians
- - SetGeometry: Modify object geometry detail
- Parameters:
- - object: name of the object to modify (optional - defaults to last modified object)
- - widthSegments: number of segments along width (for box/sphere)
- - heightSegments: number of segments along height (for box/sphere)
- - depthSegments: number of segments along depth (for box only)
- Example: High detail sphere = { widthSegments: 64, heightSegments: 32 }
- Example: High detail box = { widthSegments: 4, heightSegments: 4, depthSegments: 4 }
- - RemoveObject: Remove an object from the scene
- Parameters:
- - object: name of the object to remove
- - MultiCmds: Execute multiple commands in sequence
- Parameters:
- - commands: array of command objects
- Example - Create multiple objects:
- {
- "type": "MultiCmds",
- "params": {
- "commands": [
- {
- "type": "AddObject",
- "params": {
- "type": "cube",
- "name": "Cube1",
- "position": {"x": -1.5}
- }
- },
- {
- "type": "AddObject",
- "params": {
- "type": "cube",
- "name": "Cube2",
- "position": {"x": -0.5}
- }
- },
- {
- "type": "AddObject",
- "params": {
- "type": "cube",
- "name": "Cube3",
- "position": {"x": 0.5}
- }
- },
- {
- "type": "AddObject",
- "params": {
- "type": "cube",
- "name": "Cube4",
- "position": {"x": 1.5}
- }
- }
- ]
- }
- }
- Example - Create and modify an object:
- {
- "type": "MultiCmds",
- "params": {
- "commands": [
- {
- "type": "AddObject",
- "params": { "type": "cube", "name": "MyCube" }
- },
- {
- "type": "SetMaterialColor",
- "params": { "object": "MyCube", "color": "red" }
- },
- {
- "type": "SetScale",
- "params": { "object": "MyCube", "scale": {"x": 2, "y": 2, "z": 2} }
- }
- ]
- }
- }
- Example - Modify all objects in the scene:
- {
- "type": "MultiCmds",
- "params": {
- "commands": [
- {
- "type": "SetMaterialColor",
- "params": { "object": "Box1", "color": "red" }
- },
- {
- "type": "SetMaterialColor",
- "params": { "object": "Box2", "color": "blue" }
- }
- ]
- }
- }
- Note: Use MultiCmds when you need to:
- 1. Create multiple objects at once
- 2. Apply multiple modifications to a single object
- 3. Apply modifications to multiple objects
- 4. Any combination of the above
- Important: When working with multiple similar objects (e.g. multiple spheres):
- - Objects are automatically numbered (e.g. "Sphere1", "Sphere2", etc.)
- - Use the exact object name including the number when targeting specific objects
- - To modify all objects of a type, create a MultiCmds command with one command per object
- - The scene info includes:
- - objectCounts: how many of each type exist
- - objectsByType: groups of objects by their base name
- - spheres: list of all sphere names
- - boxes: list of all box names
- - cylinders: list of all cylinder names
- - directionalLights: list of all directional light names
- - pointLights: list of all point light names
- - ambientLights: list of all ambient light names
- Example - Set random colors for all spheres:
- {
- "type": "MultiCmds",
- "params": {
- "commands": [
- {
- "type": "SetMaterialColor",
- "params": { "object": "Sphere1", "color": "random" }
- },
- {
- "type": "SetMaterialColor",
- "params": { "object": "Sphere2", "color": "random" }
- }
- ]
- }
- }
- Respond ONLY with a JSON object in this format:
- {
- "response": "Your text response to the user explaining what you're doing",
- "commands": {
- "type": "command_type",
- "params": {
- // command specific parameters
- }
- }
- }
- Important:
- 1. If no commands are needed, set "commands" to null
- 2. Do not include any JavaScript expressions or functions in the JSON
- 3. For random colors, use the "random" keyword instead of Math.random()
- 4. Do not include any other text outside the JSON
- Do not include any other text outside the JSON.`;
- this.chat = await ai.chats.create({
- model: "gemini-2.0-flash",
- history: [
- {
- role: "user",
- parts: [{ text: systemPrompt }],
- },
- {
- role: "model",
- parts: [
- {
- text: "I'm ready to help you create and modify your 3D scene.",
- },
- ],
- },
- ],
- config: {
- temperature: 0.2,
- maxOutputTokens: 2048
- },
- });
- console.log( 'CHAT:', this.chat );
- }
- async processQuery( query ) {
- if ( ! query.trim() ) return;
- try {
- this.signals.agentThinking.dispatch();
- const response = await this.chat.sendMessage( { message: query } );
- console.log( 'RESPONSE:', response.text );
- let responseData;
- try {
- // Strip markdown code block markers if present
- const cleanText = response.text.replace( /^```json\n|\n```$/g, '' )
- .replace( /^\s*```\s*|\s*```\s*$/g, '' ) // Remove any remaining code block markers
- .trim();
- try {
- // First try parsing as is
- responseData = JSON.parse( cleanText );
- } catch ( e ) {
- // If that fails, try to fix common JSON issues
- const fixedText = cleanText
- .replace( /,\s*([}\]])/g, '$1' ) // Remove trailing commas
- .replace( /([a-zA-Z0-9])\s*:\s*/g, '"$1": ' ) // Quote unquoted keys
- .replace( /\n/g, ' ' ) // Remove newlines
- .replace( /\s+/g, ' ' ); // Normalize whitespace
- responseData = JSON.parse( fixedText );
- }
- } catch ( e ) {
- // console.error( 'AGENT: Failed to parse AI response as JSON:', e );
- // console.error( 'AGENT: Raw response:', response.text );
- this.showError( response.text );
- return;
- }
- // Execute commands if present
- if ( responseData.commands ) {
- try {
- await this.executeCommand( responseData.commands );
- } catch ( e ) {
- console.error( 'AGENT: Failed to execute commands:', e );
- this.showError( 'Failed to execute command: ' + e.message );
- return;
- }
- }
- // Log the response
- // console.log( 'AGENT:', responseData.response );
- this.signals.agentResponse.dispatch( responseData.response );
- this.showMessage( responseData.response );
- } catch ( error ) {
- console.error( 'AGENT: Agent error:', error );
- this.showError( 'Agent error: ' + error.message );
- }
- }
- async executeCommand( commandData ) {
- if ( ! commandData.type || ! Commands[ commandData.type + 'Command' ] ) {
- console.error( 'AGENT: Invalid command type:', commandData.type );
- return;
- }
- let command;
- // Helper to get target object, falling back to last modified
- const getTargetObject = ( objectName ) => {
- if ( objectName ) {
- const object = this.editor.scene.getObjectByName( objectName );
- if ( object ) {
- this.lastModifiedObject = object;
- return object;
- }
- }
- return this.lastModifiedObject;
- };
- const createMaterial = ( params ) => {
- const material = new MeshStandardMaterial();
- if ( params.color ) {
- material.color.set( params.color );
- }
- return material;
- };
- const setPosition = ( object, position ) => {
- if ( position ) {
- object.position.set(
- position.x ?? 0,
- position.y ?? 0,
- position.z ?? 0
- );
- }
- };
- switch ( commandData.type ) {
- case 'AddObject':
- const type = commandData.params.type?.toLowerCase();
- if ( type === 'box' || type === 'cube' ) {
- const width = commandData.params.width ?? 1;
- const height = commandData.params.height ?? 1;
- const depth = commandData.params.depth ?? 1;
- const widthSegments = commandData.params.widthSegments ?? 1;
- const heightSegments = commandData.params.heightSegments ?? 1;
- const depthSegments = commandData.params.depthSegments ?? 1;
- const geometry = new BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
- const mesh = new Mesh( geometry, createMaterial( commandData.params ) );
- mesh.name = commandData.params.name || this.generateUniqueObjectName( 'Box' );
- setPosition( mesh, commandData.params.position );
- command = new Commands.AddObjectCommand( this.editor, mesh );
- this.lastModifiedObject = mesh;
- } else if ( type === 'sphere' ) {
- const radius = commandData.params.radius ?? 0.5;
- const widthSegments = commandData.params.widthSegments ?? 32;
- const heightSegments = commandData.params.heightSegments ?? 16;
- const geometry = new SphereGeometry( radius, widthSegments, heightSegments );
- const mesh = new Mesh( geometry, createMaterial( commandData.params ) );
- mesh.name = commandData.params.name || this.generateUniqueObjectName( 'Sphere' );
- setPosition( mesh, commandData.params.position );
- command = new Commands.AddObjectCommand( this.editor, mesh );
- this.lastModifiedObject = mesh;
- } else if ( type === 'cylinder' ) {
- const radiusTop = commandData.params.radiusTop ?? 0.5;
- const radiusBottom = commandData.params.radiusBottom ?? 0.5;
- const height = commandData.params.height ?? 1;
- const radialSegments = commandData.params.radialSegments ?? 32;
- const heightSegments = commandData.params.heightSegments ?? 1;
- const openEnded = commandData.params.openEnded ?? false;
- const geometry = new CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded );
- const mesh = new Mesh( geometry, createMaterial( commandData.params ) );
- mesh.name = commandData.params.name || this.generateUniqueObjectName( 'Cylinder' );
- setPosition( mesh, commandData.params.position );
- command = new Commands.AddObjectCommand( this.editor, mesh );
- this.lastModifiedObject = mesh;
- } else if ( type === 'directionallight' ) {
- const color = commandData.params.color || 0xffffff;
- const intensity = commandData.params.intensity ?? 1;
- const light = new DirectionalLight( color, intensity );
- light.name = commandData.params.name || this.generateUniqueObjectName( 'DirectionalLight' );
- setPosition( light, commandData.params.position );
- command = new Commands.AddObjectCommand( this.editor, light );
- this.lastModifiedObject = light;
- } else if ( type === 'pointlight' ) {
- const color = commandData.params.color || 0xffffff;
- const intensity = commandData.params.intensity ?? 1;
- const distance = commandData.params.distance ?? 0;
- const decay = commandData.params.decay ?? 2;
- const light = new PointLight( color, intensity, distance, decay );
- light.name = commandData.params.name || this.generateUniqueObjectName( 'PointLight' );
- setPosition( light, commandData.params.position );
- command = new Commands.AddObjectCommand( this.editor, light );
- this.lastModifiedObject = light;
- } else if ( type === 'ambientlight' ) {
- const color = commandData.params.color || 0xffffff;
- const intensity = commandData.params.intensity ?? 1;
- const light = new AmbientLight( color, intensity );
- light.name = commandData.params.name || this.generateUniqueObjectName( 'AmbientLight' );
- command = new Commands.AddObjectCommand( this.editor, light );
- this.lastModifiedObject = light;
- } else {
- console.warn( 'AGENT: Unsupported object type:', type );
- }
- break;
- case 'SetPosition':
- const positionObject = getTargetObject( commandData.params.object );
- if ( positionObject && commandData.params.position ) {
- const currentPos = positionObject.position;
- const newPosition = new Vector3(
- commandData.params.position.x ?? currentPos.x,
- commandData.params.position.y ?? currentPos.y,
- commandData.params.position.z ?? currentPos.z
- );
- command = new Commands.SetPositionCommand( this.editor, positionObject, newPosition );
- }
- break;
- case 'SetRotation':
- const rotationObject = getTargetObject( commandData.params.object );
- if ( rotationObject && commandData.params.rotation ) {
- const rot = commandData.params.rotation;
- const currentRot = rotationObject.rotation;
- const newRotation = new Vector3(
- rot.x ?? currentRot.x,
- rot.y ?? currentRot.y,
- rot.z ?? currentRot.z
- );
- command = new Commands.SetRotationCommand( this.editor, rotationObject, newRotation );
- }
- break;
- case 'SetScale':
- const scaleObject = getTargetObject( commandData.params.object );
- if ( scaleObject && commandData.params.scale ) {
- const scale = commandData.params.scale;
- const newScale = new Vector3( scale.x || 1, scale.y || 1, scale.z || 1 );
- command = new Commands.SetScaleCommand( this.editor, scaleObject, newScale );
- }
- break;
- case 'SetMaterialColor':
- const colorObject = getTargetObject( commandData.params.object );
- if ( colorObject && colorObject.material && commandData.params.color ) {
- let colorValue = commandData.params.color;
- // If color is "random", generate a random color
- if ( colorValue === 'random' ) {
- colorValue = this.generateRandomColor();
- }
- const color = new Color( colorValue );
- command = new Commands.SetMaterialColorCommand( this.editor, colorObject, 'color', color.getHex() );
- }
- break;
- case 'SetMaterialValue':
- const materialObject = getTargetObject( commandData.params.object );
- if ( materialObject && materialObject.material && commandData.params.property ) {
- const property = commandData.params.property;
- let value = commandData.params.value;
- // Handle special cases for certain property types
- if ( property.includes( 'map' ) && value === null ) {
- // Handle removing textures
- value = null;
- } else if ( typeof value === 'string' && !isNaN( value ) ) {
- // Convert numeric strings to numbers
- value = parseFloat( value );
- }
- command = new Commands.SetMaterialValueCommand( this.editor, materialObject, property, value );
- }
- break;
- case 'SetGeometry':
- const detailObject = getTargetObject( commandData.params.object );
- if ( detailObject && detailObject.geometry ) {
- const params = commandData.params;
- let newGeometry;
- if ( detailObject.geometry instanceof BoxGeometry ) {
- const box = detailObject.geometry;
- newGeometry = new BoxGeometry(
- box.parameters.width ?? 1,
- box.parameters.height ?? 1,
- box.parameters.depth ?? 1,
- params.widthSegments ?? 1,
- params.heightSegments ?? 1,
- params.depthSegments ?? 1
- );
- } else if ( detailObject.geometry instanceof SphereGeometry ) {
- const sphere = detailObject.geometry;
- newGeometry = new SphereGeometry(
- sphere.parameters.radius ?? 0.5,
- params.widthSegments ?? 32,
- params.heightSegments ?? 16
- );
- } else if ( detailObject.geometry instanceof CylinderGeometry ) {
- const cylinder = detailObject.geometry;
- newGeometry = new CylinderGeometry(
- params.radiusTop ?? cylinder.parameters.radiusTop ?? 0.5,
- params.radiusBottom ?? cylinder.parameters.radiusBottom ?? 0.5,
- params.height ?? cylinder.parameters.height ?? 1,
- params.radialSegments ?? cylinder.parameters.radialSegments ?? 32,
- params.heightSegments ?? cylinder.parameters.heightSegments ?? 1,
- params.openEnded ?? cylinder.parameters.openEnded ?? false
- );
- }
- if ( newGeometry ) {
- command = new Commands.SetGeometryCommand( this.editor, detailObject, newGeometry );
- }
- }
- break;
- case 'RemoveObject':
- const removeObject = getTargetObject( commandData.params.object );
- if ( removeObject ) {
- command = new Commands.RemoveObjectCommand( this.editor, removeObject );
- this.lastModifiedObject = null;
- }
- break;
- case 'MultiCmds':
- if ( Array.isArray( commandData.params.commands ) ) {
- const commands = [];
- for ( const cmd of commandData.params.commands ) {
- const subCommand = await this.executeCommand( cmd );
- if ( subCommand ) commands.push( subCommand );
- }
- command = new Commands.MultiCmdsCommand( this.editor, commands );
- }
- break;
- default:
- console.warn( 'AGENT: Unsupported command type:', commandData.type, '- Available commands are: AddObject, SetPosition, SetRotation, SetScale, SetMaterialColor, SetMaterialValue, SetGeometry, RemoveObject, MultiCmds' );
- break;
- }
- console.log( 'AGENT: Command:', command );
- if ( command ) {
- this.editor.execute( command );
- }
- return command;
- }
- getSceneInfo() {
- const scene = this.editor.scene;
- // Helper to get all objects of a specific type
- const getObjectsByType = ( type ) => {
- return scene.children.filter( obj => {
- const baseName = obj.name.replace( /\d+$/, '' );
- return baseName.toLowerCase() === type.toLowerCase();
- } ).map( obj => obj.name );
- };
- // Get base names and their counts
- const nameCount = {};
- const objectsByType = {};
- scene.children.forEach( obj => {
- const baseName = obj.name.replace( /\d+$/, '' ); // Remove trailing numbers
- nameCount[ baseName ] = ( nameCount[ baseName ] || 0 ) + 1;
- // Group objects by their base name
- if ( ! objectsByType[ baseName ] ) {
- objectsByType[ baseName ] = [];
- }
- objectsByType[ baseName ].push( obj.name );
- } );
- const objects = scene.children.map( obj => ( {
- type: obj.type,
- name: obj.name,
- baseName: obj.name.replace( /\d+$/, '' ), // Add base name
- position: obj.position,
- rotation: obj.rotation,
- scale: obj.scale,
- isMesh: obj.isMesh,
- isLight: obj.isLight,
- material: obj.material ? {
- type: obj.material.type,
- color: obj.material.color ? '#' + obj.material.color.getHexString() : undefined
- } : undefined
- } ) );
- return {
- objects,
- meshes: objects.filter( obj => obj.isMesh ),
- lights: objects.filter( obj => obj.isLight ),
- materials: Object.keys( this.editor.materials ).length,
- cameras: Object.keys( this.editor.cameras ).length,
- objectCounts: nameCount, // Add counts of similar objects
- objectsByType, // Add grouped objects by type
- spheres: getObjectsByType( 'Sphere' ),
- boxes: getObjectsByType( 'Box' ),
- cylinders: getObjectsByType( 'Cylinder' ),
- directionalLights: getObjectsByType( 'DirectionalLight' ),
- pointLights: getObjectsByType( 'PointLight' ),
- ambientLights: getObjectsByType( 'AmbientLight' )
- };
- }
- clear() {
- while ( this.container.children.length > 0 ) {
- this.container.remove( this.container.children[ 0 ] );
- }
- }
- }
- export { Agent };
|