How to add custom behavior to a Mesh

This might be more of a JavaScript question.

I want to extend a Mesh to add my own custom behaviors. I’m building boxes, so I like to use the handy static createBox method, but this returns a Mesh instance, and can’t be extended into a custom class.

I can think of two possible solutions:

  1. Keep the Mesh returned from createBox as a property on my custom class, and pass methods along when I need them:
class MyBox {
constructor() {
this.box = BABYLON.MeshBuilder.createBox()
}

checkCollision() {
this.box.checkCollision()
}
myCustomMethod() {
//...
}
}
  1. Extend the Mesh, and implement a box in the constructor:
class MyBox extends Mesh {

constructor() {
//... copy the implementation from createBox method 
}
}

I’m not crazy about either method. Any opinions? Is there some obvious OOP technique I’m missing?

1 Like

If you just want to avoid writing all the delegated methods then behaviours can be implemented using composition (or make box a get() property if you aren’t strict about Demeter’s Law). Otherwise look at how mixins are done (BabylonJS Scene has dynamically assigned methods). Also, frameworks like ECSY from Mozilla can dynamically sign behaviours a well.

maybe some monkey patching like you find Here on Scene: Babylon.js/physicsEngineComponent.ts at master · BabylonJS/Babylon.js · GitHub

I probably prefer your first version - it fits well in ES6 style code and would add a public getter on the mesh. Depends a lot on what you are building.

I usually do MyThing extends TransformNode then in constructor do mesh.parent = this but I’ve been concerned this isn’t ideal as it creates another, potentially redundant level of transforms.

You could also rely on creating Behaviors, there are here for this exact purpose:Behaviors - Babylon.js Documentation

Nice suggestions all. Particularly using behaviors, I hadn’t seen that part of the docs yet, so cheers.

I also realized I was asking the wrong question a bit. createBox returns a specific instance of a Mesh. I want to attach custom methods to that instance. So I could do a static method that uses createBox, then Object.assign(s), my methods on top. Should work as long as none of my methods evaluate. Something like this:

class MyBox {
static function createMyBox() {
const box = BABYLON.BoxBuilder.createBox();
const myBox = new MyBox();
return Object.assign(box, myBox);
}
} 

^^ pseudo code, don’t copy and expect to work;-).
The tricky part I’ve seen with Object.assign is it will evaluate getters and setters of the source at assignment.

In the end, I can probably leverage behaviors to do what I need. So thanks again for the input folks.

1 Like

So behaviors are not exactly what I’m looking for. Say I have a box, and when it’s clicked, I want it to execute certain behavior, say console.log("is this box on: " + this.selected). I can create this behavior outside the box with the scene.pick method

window.addEventListener("click", function() {
if (scene.pick(scene.pointerX, scene.pointerY).hit) {
const selected = scene.pick(scene.pointerX, scene.pointerY).pickedMesh.selected;
console.log("is this box on: " + this.selected")
}
}

But say I want the behavior to be particular to the particular box. One box will console log, another will do another thing, etc. In this example I basically want to add a custom “click listener” to each box.

Above is a better statement of the problem I’m trying to solve.

What I thought I could do was use Object.assign to merge the instantiated box, with an instance of my custom Mesh extension:

class SectionBox extends BABYLON.Mesh {

selected:boolean

constructor() {
super()
this.selected = false
}
}

let box = BABYLON.MeshBuilder.CreateBox(name);
let sectionBox = new SectionBox();
box = Object.assign(sectionBox, box);
console.log(box.selected)
// false

The idea is all the properties in sectionBox are overridden by the properties in box, and the selected property remains. It appears to work, the box is rendered, but when I get the Mesh from the scene it does not have the selected property anymore.

One thing to note, the client code about, instantiating the two Meshes, is actually in a static method on the SectionBox. This is for convenience, and I don’t think it matters, I return the instance to the actual client code, where it does have access to the property.

I was able to get a basic toggle working using ActionManager. Link: Babylon.js Playground

However, I’m not sure how to get more complex things to happen. I might resort to a version of what @brianzinn suggested, maybe an outer object (one that isn’t passed into the scene), can hold onto a reference to the Mesh and fire it’s own methods when a value is toggle.:shrug:

Otherwise I’ll have to keep a map of the Mesh names, and cycle through them on click. Yuck.

I’m used to having callbacks fire at some point, but it appears that’s not really a thing in this library.

I would think Observer/Observable would be the pattern to follow here, but it appears like that only happens at the scene level.

Okay, I got something working using Observables: https://www.babylonjs-playground.com/#0XYMA9#31

I’m using Object.assign to put my method on the sphere Mesh. Then adding the pointer Observable to the scene. In the Observer callback I can then access my arbitrary method. Few.

1 Like

That’s an expensive way to add a function, as it copies over everything from mesh to a new object. You could also do:
sphere.myFunc = () => {
console.log(‘asdf2’)
}

Or to dynamically assign a group of properties/functions, something like this:
const objectToAssign = {
myFunc: () => {
console.log(‘asdf3’);
}
}
Object.keys(objectToAssign).forEach(key => {
sphere[key] = objectToAssign[key]
})

Anyway, glad you got it figured out :slight_smile:

2 Likes

I ended up not doing the copying, but I did re-implement the cube static method. I needed to not just add the function, but also preserve references to variables (this.myvar). I could have used closures, but I wanted to preserver typescripts checking, which meant dynamically adding methods and properties would have resulted in typescript yelling at me.

I was having a similar issue for a while but I found making custom behaviors that adds observers to mesh/scene observables to handle logic works quite well.

I made a playground example if you were interested in having a look.
I also made a TypeScript example (I’m a big fan of it).

2 Likes