diff --git a/README.md b/README.md index c399465b..c85d1a39 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,12 @@ If you are using 3D models, include `@babylonjs/loaders` and ensure it is regist # Usage Styles `react-babylonjs` tries to remain unopinionated about how you integrate BabylonJS with React. This module provides a 100% declarative option and/or you can customise by adding code. There are lots of escape hatches where you can switch to imperative coding and direct access to objects. - -![Connecting the pieces](https://raw.githubusercontent.com/brianzinn/react-babylonjs/master/media/react-babylonjs-boxes.gif) ## Connecting the pieces If you are new to React or babylon.js (or both) there is some learning ahead. The babylon.js documentation site is really useful for understanding the basics of lighting, cameras, etc. This project aims to make easy to integrate those into React using JSX. -Here we re-use a `MovingBox` component that can be clicked or hovered. These reusable components can be used to compose a declarative scene just like regular React development. +Here we re-use a `SpinningBox` component that can be clicked or hovered. These reusable components can be used to compose a declarative scene. We are using hooks for the clicking, hovering and spinning. +![Connecting the pieces](https://raw.githubusercontent.com/brianzinn/react-babylonjs/master/media/react-babylonjs-boxes.gif) ```jsx import React, { useRef, useState } from 'react' import { Engine, Scene, useBeforeRender, useClick, useHover } from 'react-babylonjs' @@ -38,7 +37,7 @@ import { Vector3, Color3 } from '@babylonjs/core' const DefaultScale = new Vector3(1, 1, 1); const BiggerScale = new Vector3(1.25, 1.25, 1.25); -const MovingBox = (props) => { +const SpinningBox = (props) => { // access Babylon scene objects with same React hook as regular DOM elements const boxRef = useRef(null); @@ -70,14 +69,18 @@ const MovingBox = (props) => { ); } -export const SceneWithMovingBoxes = () => ( +export const SceneWithSpinningBoxes = () => (
- - + +
diff --git a/src/customComponents/Model.tsx b/src/customComponents/Model.tsx index 22f81d75..30ea61ff 100644 --- a/src/customComponents/Model.tsx +++ b/src/customComponents/Model.tsx @@ -1,49 +1,38 @@ import { AbstractMesh, ISceneLoaderProgressEvent, Vector3 } from "@babylonjs/core"; import React, { useEffect } from "react" +import { FiberAbstractMeshProps, FiberAbstractMeshPropsCtor } from "../generatedProps"; import { ILoadedModel, LoadedModel } from "../hooks/loaders/loadedModel"; import { SceneLoaderOptions, useSceneLoader } from "../hooks/loaders/useSceneLoader"; export type ModelProps = { + /** + * Only used on init. Will not update dynamically (scaling will update dynamically and override this) + */ meshNames?: any receiveShadows?: boolean rootUrl: string sceneFilename: string pluginExtension?: string - position?: Vector3 - scaling?: Vector3 - rotation?: Vector3 - showBoundingBox?: boolean alwaysSelectAsActiveMesh?: boolean reportProgress?: boolean - - /** - * Only used on init. Will not update dynamically (scaling will update dynamically and override this) - */ scaleToDimension?: number onModelLoaded?: (model: ILoadedModel) => void onModelError?: (model: LoadedModel) => void onLoadProgress?: (event: ISceneLoaderProgressEvent) => void onCreated?: (rootMesh: AbstractMesh) => void -} +} & FiberAbstractMeshProps & FiberAbstractMeshPropsCtor; -const Model = (props: ModelProps) => { +const Model: React.FC = (props: ModelProps) => { + const { alwaysSelectAsActiveMesh, onModelLoaded, pluginExtension, rootUrl, receiveShadows, reportProgress, scaleToDimension, sceneFilename, ...rest } = props; const options: SceneLoaderOptions = { - receiveShadows: props.receiveShadows, - scaleToDimension: props.scaleToDimension, - alwaysSelectAsActiveMesh: props.alwaysSelectAsActiveMesh, - reportProgress: props.reportProgress, - onModelLoaded: props.onModelLoaded - } - const sceneLoaderResults = useSceneLoader(props.rootUrl, props.sceneFilename, props.pluginExtension, options); - - if (props.position) { - sceneLoaderResults.rootMesh!.position = props.position; - } - - if (props.rotation) { - sceneLoaderResults.rootMesh!.rotation = props.rotation; + receiveShadows, + scaleToDimension, + alwaysSelectAsActiveMesh, + reportProgress, + onModelLoaded } + const sceneLoaderResults = useSceneLoader(rootUrl, sceneFilename, pluginExtension, options); useEffect(() => { return () => { @@ -52,8 +41,7 @@ const Model = (props: ModelProps) => { } }, []); - // TODO: return and push ...rest of spread props with fromInstance = {rootMesh} - return null; + return ; } -export default Model +export default Model; diff --git a/src/customComponents/Skybox.tsx b/src/customComponents/Skybox.tsx index c8afb613..d5f1f4a5 100644 --- a/src/customComponents/Skybox.tsx +++ b/src/customComponents/Skybox.tsx @@ -8,8 +8,8 @@ interface SkyboxProps { } const Skybox: React.FC = (props: SkyboxProps) => - - + + diff --git a/stories/babylonjs/Models/model-loader.stories.js b/stories/babylonjs/Models/model-loader.stories.js index c61b848d..d3a72d3c 100644 --- a/stories/babylonjs/Models/model-loader.stories.js +++ b/stories/babylonjs/Models/model-loader.stories.js @@ -1,123 +1,116 @@ -import React, { Component} from 'react'; +import React, { Suspense, useRef, useContext, useMemo } from 'react'; import '@babylonjs/inspector'; import { Vector3, Color3 } from '@babylonjs/core'; import { ActionManager, SetValueAction } from '@babylonjs/core/Actions'; -import { Engine, Scene } from '../../../dist/react-babylonjs'; +import { Engine, Scene, useAssetManager, TaskType, useBeforeRender, AssetManagerContext, AssetManagerContextProvider } from '../../../dist/react-babylonjs'; -import ScaledModelWithProgress from '../ScaledModelWithProgress'; +import { Vector3 } from '@babylonjs/core'; -// import '../../style.css'; +import '../../style.css'; export default { title: 'Models' }; -class WithModel extends Component { - constructor () { - super() +const baseUrl = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/' - this.state = { - avocadoYPos: -1.5, - avocadoScaling: 3.0 - } +const modelAssetTasks = [ + { taskType: TaskType.Mesh, rootUrl: `${baseUrl}BoomBox/glTF/`, sceneFilename: 'BoomBox.gltf', name: 'boombox' }, + { taskType: TaskType.Mesh, rootUrl: `${baseUrl}Avocado/glTF/`, sceneFilename: 'Avocado.gltf', name: 'avocado' } +]; - this.moveAvocadoUp = this.moveAvocadoUp.bind(this) - this.moveAvocadoDown = this.moveAvocadoDown.bind(this) - this.increaseAvocadoSize = this.increaseAvocadoSize.bind(this) - this.decreaseAvocadoSize = this.decreaseAvocadoSize.bind(this) - this.onModelLoaded = this.onModelLoaded.bind(this) - } +const MyFallback = () => { + const boxRef = useRef(); + const context = useContext(AssetManagerContext); + console.log('context in fallback:', context); - moveAvocadoDown () { - this.setState((state) => ({ - ...state, - avocadoYPos: state.avocadoYPos - 0.5 - })) - } + useBeforeRender((scene) => { + if (boxRef.current) { + var deltaTimeInMillis = scene.getEngine().getDeltaTime(); - moveAvocadoUp () { - this.setState((state) => ({ - ...state, - avocadoYPos: state.avocadoYPos + 0.5 - })) - } + const rpm = 10; + boxRef.current.hostInstance.rotation.x = Math.PI / 4; + boxRef.current.hostInstance.rotation.y += ((rpm / 60) * Math.PI * 2 * (deltaTimeInMillis / 1000)); + } + }) + + const eventData = context?.lastProgress?.eventData; + + return <> + + + + {eventData !== undefined && + + } + {eventData === undefined && + + } + + + + + +} - increaseAvocadoSize () { - this.setState((state) => ({ - ...state, - avocadoScaling: state.avocadoScaling + 0.1 - })) - } +const MyModels = () => { + const [result] = useAssetManager(modelAssetTasks); - decreaseAvocadoSize () { - this.setState((state) => ({ - ...state, - avocadoScaling: state.avocadoScaling - 0.1 - })) - } + useMemo(() => { + console.log('Loaded Tasks', result); + const boomboxTask = result.map['boombox']; + boomboxTask.loadedMeshes[0].position = new Vector3(2.5, 0, 0); + boomboxTask.loadedMeshes[1].scaling = new Vector3(20, 20, 20); - onModelLoaded = (model, sceneContext) => { - let mesh = model.meshes[1] - mesh.actionManager = new ActionManager(sceneContext.scene) - mesh.actionManager.registerAction( - new SetValueAction( - ActionManager.OnPointerOverTrigger, - mesh.material, - 'wireframe', - true - ) - ) - mesh.actionManager.registerAction( - new SetValueAction( - ActionManager.OnPointerOutTrigger, - mesh.material, - 'wireframe', - false - ) - ) - } + const avocadoTask = result.map['avocado']; + avocadoTask.loadedMeshes[0].position = new Vector3(-2.5, 0, 0); + avocadoTask.loadedMeshes[1].scaling = new Vector3(20, 20, 20); + }); + + return null; +} + +const MyScene = () => { - render () { - let baseUrl = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/' - return ( - - - - + return ( + + + + + - {/*}> + }> - + */} {/* }> - - */} + + {/* */} - - - ) + + + ) } } -export const ModelStory = () => ( +export const ModelLoaderStory = () => (
- +
) -ModelStory.story = { - name: '3D-Model' -} +ModelLoaderStory.story = { + name: 'Asset Manager' +} \ No newline at end of file diff --git a/stories/babylonjs/Models/model.stories.js b/stories/babylonjs/Models/model.stories.js index c1230a8c..8054969f 100644 --- a/stories/babylonjs/Models/model.stories.js +++ b/stories/babylonjs/Models/model.stories.js @@ -53,7 +53,7 @@ class WithModel extends Component { onModelLoaded = (model, sceneContext) => { let mesh = model.meshes[1] - mesh.actionManager = new ActionManager(sceneContext.scene) + mesh.actionManager = new ActionManager(mesh._scene) mesh.actionManager.registerAction( new SetValueAction( ActionManager.OnPointerOverTrigger, diff --git a/stories/babylonjs/ScaledModelWithProgress.js b/stories/babylonjs/ScaledModelWithProgress.js index 56177d78..36f1235b 100644 --- a/stories/babylonjs/ScaledModelWithProgress.js +++ b/stories/babylonjs/ScaledModelWithProgress.js @@ -1,41 +1,7 @@ -import React, { Suspense, useContext, useEffect, useRef } from 'react' +import React, { Suspense, useContext } from 'react' import { Vector3, Matrix, Color3 } from '@babylonjs/core/Maths/math' -import { useSceneLoader, useScene, SceneLoaderContextProvider, SceneLoaderContext, SceneContext } from '../../dist/react-babylonjs'; - -// try with later versions of RHL to get hooks working here: -// const [loadProgress, updateProgress] = useState(0) -const MyModel = (props) => { - - const sceneLoaderResults = useSceneLoader(props.rootUrl, props.sceneFilename, undefined, { - scaleToDimension: props.scaleTo, - reportProgress: true - }) - - const scene = useScene(); - const modelLoaded = useRef(false); - if (props.onModelLoaded && !modelLoaded.current) { - modelLoaded.current = true; - props.onModelLoaded(sceneLoaderResults, { scene }); - } - - if (props.position) { - sceneLoaderResults.rootMesh.position = props.position; - } - - if (props.rotation) { - sceneLoaderResults.rootMesh.rotation = props.rotation; - } - - useEffect(() => { - return () => { - console.log('disposing the sceneloader results.') - sceneLoaderResults.dispose(); - } - }, []); - - return null; -} +import { Model, SceneLoaderContextProvider, SceneLoaderContext } from '../../dist/react-babylonjs'; const ProgressFallback = (props) => { const sceneLoaderContext = useContext(SceneLoaderContext); @@ -67,7 +33,7 @@ const ScaledModelWithProgress = (props) => { return ( }> - + )