I am now developing a react-based Babylon app, the process is going smoothly and I am able to dispatch my action payload from Babylon world to my store in redux, and use some param from Babylon passing to react component as some props, (like I have an Id Info component in react, and I will dispatch the Id of my mesh in my Babylon world to react, it works perfectly).
So my problem now is when I try useSelector as the way that redux can pass some updated stuff inside Babylon, it always gives me only the initalState from redux, no matter the param from selector I pass is in onSceneReady, or onRender hook (You mightâve telled I am using Scene Component),.
Idk if there is a way I can put my case in the playground, so if my question is lacking some important information, I will add on.
Thanks,
Johnson
@RaananW or @brianzinn could probably help.
I think you might have a stale reference. Can you share more of the code?
export const BabylonMain = () => {
const dispatch = useAppDispatch();
const { boxInfo } = useAppSelector((state) => (state) => state.babylon3d);
const onSceneReady = (scene: any) => {
// This creates and positions a free camera (non-mesh)
var camera = new FreeCamera("camera1", new Vector3(0, 5, -5), scene);
// This targets the camera to scene origin
camera.setTarget(Vector3.Zero());
//Prevent too fast zooming
// camera.wheelPrecision = 50;
const canvas = scene.getEngine().getRenderingCanvas();
// This attaches the camera to the canvas
// camera.upperBetaLimit = Math.PI / 2.2 // limit the z axis no less zero
camera.attachControl(canvas, true);
scene.clearColor = new Color4(0.109803922, 0.109803922, 0.117647059, 1);
// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
var light = new HemisphericLight("light", new Vector3(0, 1, -1), scene);
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7;
async function CreateModel() {
const model = await SceneLoader.ImportMeshAsync(
"",
"./models/",
"model.glb"
);
// console.log(model)
model.meshes[0].position = new Vector3(710, 25, 510);
// mesh.material = materialOne
// hightLightColor.addMesh(mesh, Color3.White()) // pass hightLightColor to all meshes
// const gl = new GlowLayer('glow', scene)
model.meshes.map((mesh) => mesh.addBehavior(pointerDragBehavior2)); // add drag behavior
scene.createDefaultCameraOrLight(true, true, true);
const helper = scene.createDefaultEnvironment();
helper.setMainColor(Color3.Gray());
return model;
}
CreateModel();
scene.onPointerMove = (e: any) => {
// e.preventDefault()
// const pickingInfo = scene.pick(scene.pointerX, scene.pointerY)
const pickingInfo = scene.pick(
scene.pointerX,
scene.pointerY,
scene.pointerZ
);
if (pickingInfo.hit && pickingInfo.pickedMesh.name.includes("Brep")) {
// console.log(globalCoords)
const payload = {
id: pickingInfo.pickedMesh.name,
mousePos: { globalCoords },
};
dispatch(babylon3dSlice.actions.clickBox(payload));
}
};
};
/**
* Will run on every frame render. We are spinning the box on y-axis.
*/
const onRender = (sence) => {
console.log(boxInfo);
// if (box !== undefined) {
// var deltaTimeInMillis = scene.getEngine().getDeltaTime();
// const rpm = 10;
// box.rotation.y += ((rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000));
};
return (
<>
<SceneComponent
antialias
onSceneReady={onSceneReady}
onRender={onRender}
id="my-canvas"
/>
</>
);
};
so my code here load a custom model.glb and within it each mesh have an unique id, and I set a onClick event when I click on the mesh, Babylon will dispatch the mesh id to redux.
I also set a boxInfo from useAppSelector, which design for handle some click event from react that I want to bring back to Babylon, as mentioned, it also give me initial state in onRender hook no matter the id keep changing.
The code was written in kind of dirty way for quick demonstrate my thought, I hope it will not affect a lot
Thanks,
Johnson
export const BabylonMain = () => {
const dispatch = useAppDispatch();
const { boxInfo } = useAppSelector((state) => (state) => state.babylon3d);
const onSceneReady = (scene: any) => {
// This creates and positions a free camera (non-mesh)
var camera = new FreeCamera("camera1", new Vector3(0, 5, -5), scene);
// This targets the camera to scene origin
camera.setTarget(Vector3.Zero());
//Prevent too fast zooming
// camera.wheelPrecision = 50;
const canvas = scene.getEngine().getRenderingCanvas();
// This attaches the camera to the canvas
// camera.upperBetaLimit = Math.PI / 2.2 // limit the z axis no less zero
camera.attachControl(canvas, true);
scene.clearColor = new Color4(0.109803922, 0.109803922, 0.117647059, 1);
// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
var light = new HemisphericLight("light", new Vector3(0, 1, -1), scene);
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7;
async function CreateModel() {
const model = await SceneLoader.ImportMeshAsync(
"",
"./models/",
"model.glb"
);
// console.log(model)
model.meshes[0].position = new Vector3(710, 25, 510);
// mesh.material = materialOne
// hightLightColor.addMesh(mesh, Color3.White()) // pass hightLightColor to all meshes
// const gl = new GlowLayer('glow', scene)
model.meshes.map((mesh) => mesh.addBehavior(pointerDragBehavior2)); // add drag behavior
scene.createDefaultCameraOrLight(true, true, true);
const helper = scene.createDefaultEnvironment();
helper.setMainColor(Color3.Gray());
return model;
}
CreateModel();
scene.onPointerMove = (e: any) => {
// e.preventDefault()
// const pickingInfo = scene.pick(scene.pointerX, scene.pointerY)
const pickingInfo = scene.pick(
scene.pointerX,
scene.pointerY,
scene.pointerZ
);
if (pickingInfo.hit && pickingInfo.pickedMesh.name.includes("Brep")) {
// console.log(globalCoords)
const payload = {
id: pickingInfo.pickedMesh.name,
mousePos: { globalCoords },
};
dispatch(babylon3dSlice.actions.clickBox(payload));
}
};
};
/**
* Will run on every frame render. We are spinning the box on y-axis.
*/
const onRender = (sence) => {
console.log(boxInfo);
// if (box !== undefined) {
// var deltaTimeInMillis = scene.getEngine().getDeltaTime();
// const rpm = 10;
// box.rotation.y += ((rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000));
};
return (
<>
<SceneComponent
antialias
onSceneReady={onSceneReady}
onRender={onRender}
id="my-canvas"
/>
</>
);
};
edit: I think you want onPointermove.add(âŚ)
are you sure your dispatch is being called? Does it not work with the second pickInfo
?
Scene | Babylon.js Documentation (babylonjs.com)
hello @brianzinn , I appreciated your reply.
I can call my dispatch function inside Babylon hook, and my redux logger showed my store did updated for what was changed in Babylon world.
Also inside my BabylonMain component, I am able to console.log the updated id, but it wont work once eI trynna to pass the boxinfo inside either onRender or onSceneReady hook, it always consold.log initalState no matter the boxinfo is updated in redux.
Thanks,
Johnson
Exactly - thatâs what I had originally guessed. You have a stale ref. If you switch to a âuseRefâ and access âcurrentâ property will solve it.
If that donât make sense - look up lexical function scope and closure.
Edit : thatâs said without looking at your SceneComponent. Iâm assuming it doesnât update onRender method.
2 Likes
hello @brianzinn, thanks a lot, my problem had been solved by implementing your useRef solution, also I dived into some articles about stale references and gained a lot since I never heard of them before.
I might seek one more piece of advice from you if possible, basically after my redux can transmit between react and Babylon, the next step I will do is build draggable components in react, and I will drop the âmeshâ component into my âBabylonâ then should generate a corresponding mesh in the dropped location inside my Babylon world.
I am wondering if there is a solution to convert the screen window X, and Y coordinates into vector locations in Babylon world, since the other similar articles I have seen in the forum, havenât used these react Babylon combo for dragging meshes.
I have learned a lot in the forum and was able to build the first step of my project, really appreciated it!
Thanks,
Johnson
1 Like
hi @johnsonafool
I made a game once in babylon that had itâs own level editor - but I dragged only on the canvas. You can use the x/y co-ordinates to drop, but probably that will be in x/z co-ordinates. Not sure what you will do for Y (see below what a planeNormal is) - I was doing a camera pick on the camera forward ray and highlighting where it would drop. You can tap into the drag events - iâve used react-dnd
project for DOM (only DOM) drag and drop with success - hope the pointer events will pick up on the client and that is a workable solution.
Here is a drag-n-drop live demo using react-babylonjs
:
Behaviors - Drag ânâ Drop â
Storybook (brianzinn.github.io)
You can probably get some good code from the pointerDragBehavior - here is the declarative code for that, but the original code is a babylon demo (inspiration link in the code):
react-babylonjs/dragNdrop.stories.js at master ¡ brianzinn/react-babylonjs (github.com)
1 Like