As I mentioned before, the THREE.js Ray class is not very optimized regarding memory usage. Of course, you should avoid expensive operations like ray casting, but it seems to be the cheapest way to detect if a given point is inside of a mesh. So you will eventually do a lot of ray casting in your render loop.
This is the case for Ascent, as I’m implementing shooting at things there. The whole “shooting at things” implementation can be optimized a lot, but that’s something for another post; for now, we just want a less memory-eating ray caster class.
If you want to check out the source code first, you can find it in the “webgl_experiments” repo I’ve created: ReusableRay.js.
To test it, I used it in the shooting at things example, and memory usage dropped by a couple of MBs. While this is good news, the gc behavior still is horrible; but again, that’s another story (You can see it in action and shoot at things yourself over here; I’ve also fixed the buggy collision detection).
Here’s what’s different in the ReusableRay class compared to the “built-in” ray class:
1. Object re-use
The THREE.Ray class creates around ten new objects (Vector3 instances) for every newly created instance – which means, for every ray that’s being cast. As we want to keep and reuse the ray instance, the ReusableRay class creates all needed vector instances in the constructor and re-uses them.
2. Make the class itself re-usable
You will be casting more than one ray, from different positions and into different directions. The ray class provided by THREE.js requires you to instantiate a new instance for every origin/direction pair. The ReusableRay class adds a setSource method (see ReusableRay.js, line 264) that allows you to explicitly set the origin and direction of the ray, so you can use the same instance for different origin/direction pairs.
3. Move methods onto the prototype
It still can be useful to have multiple ray instances. In Ascent, the player entity maintains it’s own ray class for collision detection, so that the level author doesn’t have to care about that. Moving the ray caster’s methods onto the prototype means that they will be shared between instances, and don’t add to memory usage if multiple instances exist.
There has been some discussion on this, though; rumors have it that declaring methods on the prototype instead of the constructor might lead to a performance penalty in V8, as the constructed hidden class might be less optimized… but I couldn’t really find any proof. If you have any insights on this topic, please let me know!
4. Bonus: Detect Collada models
As I wrote before, THREE.js’ Ray class doesn’t detect imported Collada models, and the workaround I described is pretty ugly. The ReusableRay class also detects imported collada models. It checks if the given object is an instance of THREE.Object3D. If so, and if it has instances of THREE.Mesh as children, it will perform intersect checks for each of these and report back intersections it finds. So, no more need to fiddle around with meshes and object properties!
That’s it! If you have any ideas on how to further optimize the ray caster class, leave a comment! Also, if you decide to use it in your projects, and have findings to share regarding memory usage, performance or issues, please don’t hesitate to do so!