three.js: Memory leak with TransformControls

Description of the problem

When using TransformControls with angular references are left after dispose of component. TransformControls.dispose() only releases listener but not any geometry or material associated with it so gc can’t release memory (https://stackoverflow.com/questions/33152132/three-js-collada-whats-the-proper-way-to-dispose-and-release-memory-garbag/33199591#33199591)

It was even hard to find all references as it misused Object3D and not Group for …XY/XYZ/XYZE…

What I did so far:

public static disposeTransformControl(node: THREE.Object3D) {
		if (node === undefined || node.children === undefined) return;
		for (let i = node.children.length - 1; i >= 0; i--) {
			let child = node.children[i];
			//child.userData = null;
			this.disposeTransformControl(child);
			const c = child as any;
			if (c.E) {
				this.disposeTransformControl(c.E);
				this.disposeNode(c.E);
				c.E = null;
			}
			if (c.XYZ) {
				this.disposeTransformControl(c.XYZ);
				this.disposeNode(c.XYZ);
				c.XYZ = null;
			}
			if (c.XY) {
				this.disposeTransformControl(c.XY);
				this.disposeNode(c.XY);
				c.XY = null;
			}
			if (c.XYZE) {
				this.disposeTransformControl(c.XYZE);
				this.disposeNode(c.XYZE);
				c.XYZE = null;
			}
			if (c.XZ) {
				this.disposeTransformControl(c.XZ);
				this.disposeNode(c.XZ);
				c.XZ = null;
			}
			if (c.YZ) {
				this.disposeTransformControl(c.YZ);
				this.disposeNode(c.YZ);
				c.YZ = null;
			}
			if (c.planes) {
				this.disposeTransformControl(c.planes);
				this.disposeNode(c.planes);
				c.planes = null;
			}
			if (c.activePlane) {
				this.disposeTransformControl(c.activePlane);
				this.disposeNode(c.activePlane);
				c.activePlane = null;
			}
			if (c.pickers) {
				this.disposeTransformControl(c.pickers);
				this.disposeNode(c.pickers);
				c.pickers = null;
			}
			if (c.handles) {
				this.disposeTransformControl(c.handles);
				this.disposeNode(c.handles);
				c.handles = null;
			}

			if (c.handleGizmos) {
				if (c.handleGizmos.E) {
					for (const ar of c.handleGizmos.E) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.X) {
					for (const ar of c.handleGizmos.X) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.Y) {
					for (const ar of c.handleGizmos.Y) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.Z) {
					for (const ar of c.handleGizmos.Z) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.XY) {
					for (const ar of c.handleGizmos.XY) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.YZ) {
					for (const ar of c.handleGizmos.YZ) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.XZ) {
					for (const ar of c.handleGizmos.XYZ) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.handleGizmos.XYZ) {
					for (const ar of c.handleGizmos.XYZ) {
						this.destructHandlerPicker(ar);
					}
				}

				if (c.handleGizmos.XYZE) {
					for (const ar of c.handleGizmos.XYZE) {
						this.destructHandlerPicker(ar);
					}
				}

				this.disposeTransformControl(c.handleGizmos);
				this.disposeNode(c.handleGizmos);
				c.handleGizmos = null;
			}

			if (c.pickerGizmos) {
				if (c.pickerGizmos.E) {
					for (const ar of c.pickerGizmos.E) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.X) {
					for (const ar of c.pickerGizmos.X) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.Y) {
					for (const ar of c.pickerGizmos.Y) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.Z) {
					for (const ar of c.pickerGizmos.Z) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.XY) {
					for (const ar of c.pickerGizmos.XY) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.YZ) {
					for (const ar of c.pickerGizmos.YZ) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.XZ) {
					for (const ar of c.pickerGizmos.XZ) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.XYZ) {
					for (const ar of c.pickerGizmos.XYZ) {
						this.destructHandlerPicker(ar);
					}
				}
				if (c.pickerGizmos.XYZE) {
					for (const ar of c.pickerGizmos.XYZE) {
						this.destructHandlerPicker(ar);
					}
				}
				this.disposeTransformControl(c.pickerGizmos);
				this.disposeNode(c.pickerGizmos);
				c.pickerGizmos = null;
			}

			this.disposeNode(child);
			node.remove(child);
			child = null;
		}
	}

Code is quick and dirty solution not meant as perfect solution!

What would be the right way to solve this?

Thanks -Michael

Browser
  • All of them
  • Chrome
  • Firefox
  • Internet Explorer
OS
  • All of them
  • Windows
  • macOS
  • Linux
  • Android
  • iOS
Hardware Requirements (graphics card, VR Device, …)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Ah OK. The use of Group vs. Object3D is not relevant for memory disposal. Its a syntax clarity issue.

When using TransformControls with angular references…

Is this issue particular to Angular in some way?

Also, can you turn your code example into a PR? It will make it easier to understand the intention. Moreover maybe we can come up with a more generalized solution that can be applied to other articulated rigs in threejs such as characters, camera controls, game objects etc…

You are also welcome to change Object3D to Group if you prefer it like that. If you do, please send it as a separate PR.