Repository: mrdoob/three.js (JavaScript)
Date: 2020-10-08 12:18:34+00:00
Task type: Feature

Issue:
Object3D: Add removeAll or clear.
I'd like to have removeAll or clear method in Object3D.

```
	remove: function ( object ) {

		if ( arguments.length > 1 ) {

			for ( let i = 0; i < arguments.length; i ++ ) {

				this.remove( arguments[ i ] );

			}

			return this;

		}

		const index = this.children.indexOf( object );

		if ( index !== - 1 ) {

			object.parent = null;
			this.children.splice( index, 1 );

			object.dispatchEvent( _removedEvent );

		}

		return this;

	}
```

`remove` find child within children with `indexOf` and remove it using `splice` which becomes bottleneck when there are a lot of children objects and I need to remove them all which I am experiencing now.

So i suggest to add `removeAll` method.

```
	removeAll: function ( ) {

		for ( let i = 0; i < this.children.length; i ++ ) {

			let object = this.children[i];

			object.parent = null;

			object.dispatchEvent( _removedEvent );

		}

		this.children = [];

		return this;

	}
```



Issue discussion:
Sounds good to me.

I thing I would prefer something like this:

```js
clear: function () {

	while ( this.children.length > 0 ) {

		const object = this.children.pop();
		object.parent = null;
		object.dispatchEvent( _removedEvent );

	}

	return this;

}
```
`this.children.pop` spends more time than `this.children = []`, doesn't it?
Unless `this.children` is referenced in remove event handler, i think latter one is better for performance. What is your thought?
If a user referenced `scene.children` somewhere in their code things would break because with `this.children = []` it would be a different array.
Then what about `this.children.length = 0`?
I think that works 👍
Would you like to do a PR with the new method?

Solution patch:
diff --git a/docs/api/en/core/Object3D.html b/docs/api/en/core/Object3D.html
--- a/docs/api/en/core/Object3D.html
+++ b/docs/api/en/core/Object3D.html
@@ -314,6 +314,11 @@ <h3>[method:this remove]( [param:Object3D object], ... )</h3>
 		Removes *object* as child of this object. An arbitrary number of objects may be removed.
 		</p>
 
+		<h3>[method:this removeAll]()</h3>
+		<p>
+		Removes all child objects.
+		</p>
+
 		<h3>[method:this rotateOnAxis]( [param:Vector3 axis], [param:Float angle] )</h3>
 		<p>
 		axis -- A normalized vector in object space. <br />
diff --git a/src/core/Object3D.d.ts b/src/core/Object3D.d.ts
--- a/src/core/Object3D.d.ts
+++ b/src/core/Object3D.d.ts
@@ -316,6 +316,11 @@ export class Object3D extends EventDispatcher {
 	 */
 	remove( ...object: Object3D[] ): this;
 
+	/**
+	 * Removes all child objects.
+	 */
+	removeAll(): this;
+
 	/**
 	 * Adds object as a child of this, while maintaining the object's world transform.
 	 */
diff --git a/src/core/Object3D.js b/src/core/Object3D.js
--- a/src/core/Object3D.js
+++ b/src/core/Object3D.js
@@ -370,6 +370,25 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 	},
 
+	removeAll: function () {
+
+		for ( let i = 0; i < this.children.length; i ++ ) {
+
+			const object = this.children[ i ];
+
+			object.parent = null;
+
+			object.dispatchEvent( _removedEvent );
+
+		}
+
+		this.children.length = 0;
+
+		return this;
+
+
+	},
+
 	attach: function ( object ) {
 
 		// adds object as a child of this, while maintaining the object's world transform


Test patch:
diff --git a/test/unit/src/core/Object3D.tests.js b/test/unit/src/core/Object3D.tests.js
--- a/test/unit/src/core/Object3D.tests.js
+++ b/test/unit/src/core/Object3D.tests.js
@@ -296,7 +296,7 @@ export default QUnit.module( 'Core', () => {
 
 		} );
 
-		QUnit.test( "add/remove", ( assert ) => {
+		QUnit.test( "add/remove/removeAll", ( assert ) => {
 
 			var a = new Object3D();
 			var child1 = new Object3D();
@@ -328,6 +328,13 @@ export default QUnit.module( 'Core', () => {
 			assert.strictEqual( a.children[ 0 ], child2, "The second one is now the parent's child again" );
 			assert.strictEqual( child1.children.length, 0, "The first one no longer has any children" );
 
+			a.add( child1 );
+			assert.strictEqual( a.children.length, 2, "The first child was added to the parent" );
+			a.removeAll();
+			assert.strictEqual( a.children.length, 0, "All childrens were removed" );
+			assert.strictEqual( child1.parent, null, "First child has no parent" );
+			assert.strictEqual( child2.parent, null, "Second child has no parent" );
+
 		} );
 
 		QUnit.test( "getObjectById/getObjectByName/getObjectByProperty", ( assert ) => {