diff --git a/.storybook/main.js b/.storybook/main.js index 3b771db3..bcb1d057 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -6,6 +6,7 @@ module.exports = { '@storybook/addon-actions/register', '@storybook/addon-links/register', '@storybook/addon-storysource/register', + '@storybook/addon-notes/register', ], webpackFinal: async (config, { configType }) => { config.module.rules.push({ diff --git a/README.md b/README.md index ec3fd887..2cd8d090 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,6 @@ Huge shout out to [Konsumer](https://github.com/konsumer) that brought this proj Thanks to [seacloud9](https://github.com/seacloud9) for adding storybook (and [GSAP demo](https://brianzinn.github.io/react-babylonjs/?path=/story/integrations--gsap-timeline)). Also for adding [dynamic terrain](https://brianzinn.github.io/react-babylonjs/?path=/story/babylon-basic--dynamic-terrain). Ported a branch of his into a [PIXI demo](https://brianzinn.github.io/react-babylonjs/?path=/story/integrations--pixi-story). -Lots of contributions from [hookex](https://github.com/hookex) :) Proper texture handling [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/textures--image-texture), Node parenting [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/babylon-basic--transform-node) Full Screen GUI [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/gui--gui-full-screen), Effect Layers [glow demo](https://brianzinn.github.io/react-babylonjs/?path=/story/special-fx--glow-layer) and behaviors [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/behaviors--pointer-drag-behavior). +Lots of contributions from [hookex](https://github.com/hookex) :) Proper texture handling [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/textures--image-texture), Node parenting [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/babylon-basic--transform-node) Full Screen GUI [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/gui--gui-full-screen), Effect Layers [glow demo](https://brianzinn.github.io/react-babylonjs/?path=/story/special-fx--glow-layer), behaviors [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/behaviors--pointer-drag-behavior), useHover & useClick [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/hooks--use-hover-event) and react-spring integration [demo](https://brianzinn.github.io/react-babylonjs/?path=/story/integrations--react-spring). Made with ♥ by Brian Zinn diff --git a/package.json b/package.json index 582c949f..0667444c 100644 --- a/package.json +++ b/package.json @@ -85,11 +85,16 @@ "@babylonjs/loaders": "^4.1.0", "@babylonjs/procedural-textures": "^4.1.0", "@inlet/react-pixi": "^1.2.8", + "@react-spring/addons": "^9.0.0-beta.33", + "@react-spring/animated": "^9.0.0-beta.33", + "@react-spring/core": "9.0.0-beta.30", + "@react-spring/shared": "^9.0.0-beta.33", "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-typescript": "^4.0.0", "@storybook/addon-actions": "^5.3.17", "@storybook/addon-info": "^5.3.17", "@storybook/addon-links": "^5.3.17", + "@storybook/addon-notes": "5.3.17", "@storybook/addon-storysource": "^5.3.17", "@storybook/addons": "^5.3.17", "@storybook/react": "^5.3.17", @@ -141,23 +146,19 @@ "tslint-config-standard": "^8.0.1", "typedoc": "^0.14.1", "typescript": "3.8.2", - "validate-commit-msg": "^2.14.0", - "@react-spring/addons": "^9.0.0-beta.33", - "@react-spring/animated": "^9.0.0-beta.33", - "@react-spring/core": "9.0.0-beta.30", - "@react-spring/shared": "^9.0.0-beta.33" + "validate-commit-msg": "^2.14.0" }, "peerDependencies": { "@babylonjs/core": "4.x", "@babylonjs/gui": "4.x", "@babylonjs/inspector": "4.x", "@babylonjs/loaders": "4.x", + "@react-spring/addons": "9.x", + "@react-spring/animated": "9.x", + "@react-spring/core": "9.x", + "@react-spring/shared": "9.x", "react": "16.x", "react-dom": "16.x", - "react-reconciler": "0.x", - "@react-spring/addons": "^9.0.0-beta.33", - "@react-spring/animated": "^9.0.0-beta.33", - "@react-spring/core": "9.0.0-beta.30", - "@react-spring/shared": "^9.0.0-beta.33" + "react-reconciler": "0.x" } } diff --git a/src/PropsHandler.ts b/src/PropsHandler.ts index 5bcef61a..18d9f572 100644 --- a/src/PropsHandler.ts +++ b/src/PropsHandler.ts @@ -1,7 +1,6 @@ import { Vector3, Color3, Color4 } from '@babylonjs/core/Maths/math' import { Control } from '@babylonjs/gui/2D/controls/control' import { Observable, FresnelParameters, BaseTexture, Nullable } from '@babylonjs/core' -import {isEqual} from "./helper/is"; // TODO: type/value need to be joined, as the method will have multiple. export interface PropertyUpdate { @@ -26,6 +25,11 @@ export interface HasPropsHandlers { addPropsHandler(propHandler: PropsHandler): void } +// internal +type HandlerUpdateProcessResult = { + accepted: boolean +} & PropertyUpdateProcessResult; + export type PropertyUpdateProcessResult = { processed: boolean, value: Nullable @@ -122,22 +126,44 @@ export class CustomPropsHandler { return true; } - public static HandlePropsChange(propsChangeType: PropChangeType, oldProp: any, newProp: any): PropertyUpdateProcessResult { + private static NOT_ACCEPTED: Readonly> = Object.freeze({ + accepted: false, + processed: false, + value: null + }); + + private static ACCEPTED_NOT_PROCSSED: Readonly> = Object.freeze({ + accepted: true, + processed: false, + value: null + }); + + public static HandlePropsChange(propsChangeType: PropChangeType, oldProp: any, newProp: any): Readonly> { const registeredHandlers: ICustomPropsHandler[] = CustomPropsHandler._registeredPropsHandlers[propsChangeType]; - const notProcessed: PropertyUpdateProcessResult = { processed: false, value: null}; if (registeredHandlers === undefined) { - return notProcessed; + return CustomPropsHandler.NOT_ACCEPTED; } + let accepted = false; for (const handler of registeredHandlers) { if (handler.accept(newProp)) { + accepted = true; const propertyUpdatedProcessResult: PropertyUpdateProcessResult = handler.process(oldProp, newProp); - // console.log(`handler '${handler.name}'custom prop processing result:`, propertyUpdatedProcessResult); - return propertyUpdatedProcessResult; + // console.log(`handler '${handler.name}'custom prop processing result:`, oldProp, newProp, propertyUpdatedProcessResult); + // give other custom handlers (if any) the opportunity to handle + if (propertyUpdatedProcessResult.processed) { + return { + accepted: true, + processed: true, + value: propertyUpdatedProcessResult.value + }; + } } } - return notProcessed; + return accepted + ? CustomPropsHandler.ACCEPTED_NOT_PROCSSED + : CustomPropsHandler.NOT_ACCEPTED; } } @@ -155,156 +181,188 @@ export enum PropChangeType { Texture = "Texture" } -const handledCustomProp = (changeType: PropChangeType, oldProp: any, newProp: any, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): boolean => { - const processedResult = CustomPropsHandler.HandlePropsChange(changeType, oldProp, newProp); - if (processedResult.processed) { - // console.log(`handled ${PropChangeType.Color3} on ${propertyName} - bypassing built-in handler - new Value: ${JSON.stringify(processedResult.value ?? {})}`); - changedProps.push({ - propertyName, - type: propertyType, - changeType, - value: processedResult.value! - }) +/** + * Encapsulates common error handling and handling of registered custom prop handlers. + */ +function propertyCheck ( + oldProp: T | undefined, + newProp: T | undefined, + propertyName: string, + propertyType: string, + propChangeType: PropChangeType, + changedProps: PropertyUpdate[], + templateMethod: (oldProp: T | undefined, newProp: T | undefined, changedProps: PropertyUpdate[]) => void ): void +{ + try { + const processedResult = CustomPropsHandler.HandlePropsChange(propChangeType, oldProp, newProp); + if (processedResult.accepted) { + if (processedResult.processed) { + // console.log(`handled ${propChangeType} on ${propertyName} by custom handler - new Value: ${JSON.stringify(processedResult.value ?? {})}`); + changedProps.push({ + propertyName, + type: propertyType, + changeType: propChangeType, + value: processedResult.value! + }) + } + + // we assume any custom handler that 'accepts' a property does not want to pass + // down to built-in handler. ie: [] -> Vector3 would fail. + return; + } + + templateMethod(oldProp, newProp, changedProps); + } catch (e) { + console.error(`Unable to update '${propertyName}' with ${propChangeType}:`, newProp); + console.error(e); } - return processedResult.processed; } export const checkVector3Diff = (oldProp: Vector3 | undefined, newProp: Vector3 | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - if (handledCustomProp(PropChangeType.Vector3, oldProp, newProp, propertyName, propertyType, changedProps)) { - return; - } - - if (newProp && (!oldProp || !isEqual(newProp, oldProp))) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Vector3, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.Vector3, changedProps, (oldProp, newProp, changedProps) => { + if (newProp && (!oldProp || !newProp.equals(oldProp))) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Vector3, + value: newProp + }) + } + }); } export const checkColor3Diff = (oldProp: Color3 | undefined, newProp: Color3 | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - if (handledCustomProp(PropChangeType.Color3, oldProp, newProp, propertyName, propertyType, changedProps)) { - return; - } - - if (newProp && (!oldProp || !isEqual(newProp, oldProp))) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Color3, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.Color3, changedProps, (oldProp, newProp, changedProps) => { + if (newProp && (!oldProp || !newProp.equals(oldProp))) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Color3, + value: newProp + }) + } + }) } export const checkColor4Diff = (oldProp: Color4 | undefined, newProp: Color4 | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - if (handledCustomProp(PropChangeType.Color4, oldProp, newProp, propertyName, propertyType, changedProps)) { - return; - } - - // Color4.equals() not added until PR #5517 - if (newProp && (!oldProp || oldProp.r !== newProp.r || oldProp.g !== newProp.g || oldProp.b !== newProp.b || oldProp.a !== newProp.a)) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Color4, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.Color4, changedProps, (oldProp, newProp, changedProps) => { + // Color4.equals() not added until PR #5517 + if (newProp && (!oldProp || oldProp.r !== newProp.r || oldProp.g !== newProp.g || oldProp.b !== newProp.b || oldProp.a !== newProp.a)) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Color4, + value: newProp + }) + } + }) } export const checkFresnelParametersDiff = (oldProp: FresnelParameters | undefined, newProp: FresnelParameters | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - // FresnelParameters.equals() not added until PR #7818 (https://github.com/BabylonJS/Babylon.js/pull/7818) - if (newProp /* won't clear the property value */ && ( - !oldProp || - oldProp.bias !== newProp.bias || - oldProp.power !== newProp.power || - (!oldProp.leftColor || !oldProp.leftColor.equals(newProp.leftColor)) || - (!oldProp.rightColor || !oldProp.rightColor.equals(newProp.rightColor)) || - oldProp.isEnabled !== newProp.isEnabled - ) - ) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.FresnelParameters, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.FresnelParameters, changedProps, (oldProp, newProp, changedProps) => { + // FresnelParameters.equals() not added until PR #7818 (https://github.com/BabylonJS/Babylon.js/pull/7818) + if (newProp /* won't clear the property value */ && ( + !oldProp || + oldProp.bias !== newProp.bias || + oldProp.power !== newProp.power || + (!oldProp.leftColor || !oldProp.leftColor.equals(newProp.leftColor)) || + (!oldProp.rightColor || !oldProp.rightColor.equals(newProp.rightColor)) || + oldProp.isEnabled !== newProp.isEnabled + ) + ) + { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.FresnelParameters, + value: newProp + }) + } + }) } export const checkLambdaDiff = (oldProp: any | undefined, newProp: any | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - if (newProp !== oldProp) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.LambdaExpression, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.LambdaExpression, changedProps, (oldProp, newProp, changedProps) => { + if (newProp !== oldProp) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.LambdaExpression, + value: newProp + }) + } + }) } export const checkControlDiff = (oldProp: Control | undefined, newProp: Control | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - // only sets once - if (newProp && !oldProp) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Control, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.Control, changedProps, (oldProp, newProp, changedProps) => { + // only sets once + if (newProp && !oldProp) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Control, + value: newProp + }) + } + }) } export type PrimitiveType = string | number | undefined | null | boolean; -export const checkPrimitiveDiff = (oldProp: PrimitiveType, newProp: PrimitiveType, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - if (newProp !== oldProp) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Primitive, - value: newProp - }) - } +export const checkPrimitiveDiff = (oldProp: PrimitiveType | undefined, newProp: PrimitiveType | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.Primitive, changedProps, (oldProp, newProp, changedProps) => { + if (newProp !== oldProp) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Primitive, + value: newProp + }) + } + }) } export const checkTextureDiff = (oldProp: BaseTexture | undefined, newProp: BaseTexture | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - if (newProp !== oldProp) { - console.log('pushing texture:', propertyName, propertyType) - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Texture, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.Texture, changedProps, (oldProp, newProp, changedProps) => { + if (newProp !== oldProp) { + console.log('pushing texture:', propertyName, propertyType) + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Texture, + value: newProp + }) + } + }) } export const checkNumericArrayDiff = (oldProp: number[] | undefined, newProp: number[] | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - // just length - missing loop + indexOf comparison (or deepEquals()) - if (newProp && (!oldProp || oldProp.length !== newProp.length)) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.NumericArray, - value: newProp - }) - } + propertyCheck(oldProp, newProp, propertyName, propertyType, PropChangeType.NumericArray, changedProps, (oldProp, newProp, changedProps) => { + // just length - missing loop + indexOf comparison (or deepEquals()) + if (newProp && (!oldProp || oldProp.length !== newProp.length)) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.NumericArray, + value: newProp + }) + } + }) } -export const checkObservableDiff = (oldProp: Observable, newProp: Observable, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { - // if it starts with 'on' then we have different handling. - if (oldProp === undefined && oldProp !== newProp) { - changedProps.push({ - propertyName, - type: propertyType, - changeType: PropChangeType.Observable, - value: newProp - }) - } +export const checkObservableDiff = (oldProp: Observable | undefined, newProp: Observable | undefined, propertyName: string, propertyType: string, changedProps: PropertyUpdate[]): void => { + propertyCheck>(oldProp, newProp, propertyName, propertyType, PropChangeType.Observable, changedProps, (oldProp, newProp, changedProps) => { + // if it starts with 'on' then we have different handling. + if (oldProp === undefined && oldProp !== newProp) { + changedProps.push({ + propertyName, + type: propertyType, + changeType: PropChangeType.Observable, + value: newProp + }) + } + }) } /** diff --git a/src/UpdateInstance.ts b/src/UpdateInstance.ts index 20ab98e5..906595ed 100644 --- a/src/UpdateInstance.ts +++ b/src/UpdateInstance.ts @@ -1,4 +1,4 @@ -import { Vector3, Color3, Color4, Mesh, Scene } from '@babylonjs/core' +import { Vector3, Color3, Color4 } from '@babylonjs/core' import { PropertyUpdate, PropsHandler, PropChangeType } from "./PropsHandler" import { CreatedInstance } from "./CreatedInstance" @@ -83,6 +83,7 @@ export const applyInitialPropsToInstance = (instance: CreatedInstance, prop return; } + // console.log('applying initial props:', props); let initPayload: PropertyUpdate[] = [] instance.propsHandlers.getPropsHandlers().forEach((propHandler: PropsHandler) => { // NOTE: this can actually be WRONG, because here we want to compare the props with the object. diff --git a/src/helper/is.ts b/src/helper/is.ts deleted file mode 100644 index 9040c058..00000000 --- a/src/helper/is.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {Color3, Vector3} from "@babylonjs/core"; - -export const is = { - arr: Array.isArray, - obj: (a: unknown): a is object => - Object.prototype.toString.call(a) === '[object Object]', - fun: (a: unknown): a is Function => typeof a === 'function', - str: (a: unknown): a is string => typeof a === 'string', - num: (a: unknown): a is number => typeof a === 'number', - und: (a: unknown): a is undefined => a === void 0, - nul: (a: unknown): a is null => a === null, - set: (a: unknown): a is Set => a instanceof Set, - map: (a: unknown): a is Map => a instanceof Map, - equ(a: any, b: any) { - if (typeof a !== typeof b) return false - if (is.str(a) || is.num(a)) return a === b - if ( - is.obj(a) && - is.obj(b) && - Object.keys(a).length + Object.keys(b).length === 0 - ) { - return true - } - let i - for (i in a) if (!(i in b)) return false - for (i in b) if (a[i] !== b[i]) return false - return is.und(i) ? a === b : true - }, - color3: (a: unknown) => a instanceof Color3, - vector3: (a: unknown) => a instanceof Vector3, -}; - -function isArrEqual(a: number[], b: number[]): boolean { - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - - return true; -} - -/** - * todo: support more types comparision - * @param a - * @param b - */ -export function isEqual(a: unknown, b: unknown): boolean { - if (a === b) { - return true; - } - - if (is.color3(a) && is.color3(b)) { - return (a as Color3).equals(b as Color3); - } - - if (is.vector3(a) && is.vector3(b)) { - return (a as Color3).equals(b as Color3); - } - - if (is.arr(a) && is.arr(b)) { - return isArrEqual(a, b); - } - - return false; -} diff --git a/src/hooks.ts b/src/hooks.ts index ba98ddde..a53c2a48 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -8,8 +8,11 @@ import { ActionEvent, ExecuteCodeAction, Mesh, + IAction, } from '@babylonjs/core'; +import {Control} from '@babylonjs/gui/2D/controls/control'; + import { SceneContext } from './Scene' import { ICustomPropsHandler, CustomPropsHandler } from './PropsHandler'; import {CreatedInstance} from "./CreatedInstance"; @@ -54,58 +57,109 @@ export function useAfterRender(callback: OnFrameRenderFn, mask?: number, insertF }) } -export function useCustomPropsHandler(propsHandler: ICustomPropsHandler/*, deps?: React.DependencyList | undefined*/): void { - // running inside useEffect is too late for initial props - CustomPropsHandler.RegisterPropsHandler(propsHandler); +export const useCustomPropsHandler = (propsHandler: ICustomPropsHandler/*, deps?: React.DependencyList | undefined*/): void => { + const firstRun = useRef(true); + + if (firstRun.current === true) { + CustomPropsHandler.RegisterPropsHandler(propsHandler); + firstRun.current = false; + } + useEffect(() => { - return () => { - // console.warn('de-registering on unmount', propsHandler.name); - CustomPropsHandler.UnregisterPropsHandler(propsHandler); - } + return () => { + // console.warn('de-registering on unmount', propsHandler.name); + CustomPropsHandler.UnregisterPropsHandler(propsHandler); + } }, []) } +export type MeshEventType = { + (ev: ActionEvent): void +} -export interface EventFunc { - (ev: ActionEvent): void; +export type Gui2dEventType = { + (eventData: Control, eventState: EventState): void } +export type HoverType = MeshEventType | Gui2dEventType; + /** - * hover hook - * TODO: support UI - * @param over - * @param out + * useHover hook + * + * TODO: support GUI 3D contols + * + * @param over expression when hover over starts + * @param out expression when hover stops */ -export function useHover(over?: EventFunc, out?: EventFunc): [MutableRefObject>, boolean] { +export const useHover = (over?: HoverType, out?: HoverType): [MutableRefObject>, boolean] => { const [value, setValue] = useState(false); - const ref = useRef>(null) as MutableRefObject>; + const ref = useRef>(null) as MutableRefObject>; useEffect(() => { if (ref.current) { - const mesh = ref.current.hostInstance as Mesh; + const registeredMeshActions: Nullable[] = []; + let observer2dGuiEnter: Nullable> = null; + let observer2dGuiOut: Nullable> = null; + + if (ref.current.metadata.isMesh === true) { + const mesh = ref.current.hostInstance as Mesh; + + if (!mesh.actionManager) { + mesh.actionManager = new ActionManager(mesh.getScene()); + } - if (!mesh.actionManager) { - mesh.actionManager = new ActionManager(mesh.getScene()); + const onPointerOverAction: Nullable = mesh.actionManager.registerAction( + new ExecuteCodeAction( + ActionManager.OnPointerOverTrigger, (ev) => { + over && (over as MeshEventType)(ev); + setValue(true); + } + ) + ); + + const onPointerOutAction: Nullable = mesh.actionManager.registerAction( + new ExecuteCodeAction( + ActionManager.OnPointerOutTrigger, (ev) => { + out && (out as MeshEventType)(ev); + setValue(false); + } + ) + ); + + registeredMeshActions.push(onPointerOverAction); + registeredMeshActions.push(onPointerOutAction); + } else if (ref.current.metadata.isGUI2DControl === true) { + const control = ref.current.hostInstance as Control; + observer2dGuiEnter = control.onPointerEnterObservable.add(over as Gui2dEventType); + observer2dGuiOut = control.onPointerOutObservable.add(out as Gui2dEventType); + } else { + console.warn("Can only apply useHover to non-mesh/2D control currently.", ref.current.metadata); } - mesh.actionManager.registerAction( - new ExecuteCodeAction( - ActionManager.OnPointerOverTrigger, function (ev) { - over && over(ev); - setValue(true); - } - ) - ); - - mesh.actionManager.registerAction( - new ExecuteCodeAction( - ActionManager.OnPointerOutTrigger, function (ev) { - out && out(ev); - setValue(false); + if (registeredMeshActions.length > 0 || observer2dGuiEnter !== null) { + return () => { + if (ref.current) { + if (registeredMeshActions.length > 0) { + registeredMeshActions.forEach((action: Nullable) => { + if (action !== null) { + const mesh = ref.current.hostInstance as Mesh; + mesh.actionManager?.unregisterAction(action); + } + }) + registeredMeshActions.splice(0, registeredMeshActions.length) + } + + if (observer2dGuiEnter !== null) { + const control = ref.current.hostInstance as Control; + control.onPointerEnterObservable.remove(observer2dGuiEnter); + control.onPointerOutObservable.remove(observer2dGuiEnter); + observer2dGuiEnter = null; + observer2dGuiOut = null; + } } - ) - ); + } + } } }, [ref.current]); // todo: if use ref.current as dep, duplicate register action. @@ -114,27 +168,33 @@ export function useHover(over?: EventFunc, out?: EventFunc): [MutableRefObject>] { +export function useClick(onClick: MeshEventType): [MutableRefObject>] { const ref = useRef>(null) as MutableRefObject>; useEffect(() => { if (ref.current) { - const mesh = ref.current.hostInstance as Mesh; + if (ref.current.metadata.isMesh === true) { + const mesh = ref.current.hostInstance as Mesh; - if (!mesh.actionManager) { - mesh.actionManager = new ActionManager(mesh.getScene()); - } + if (!mesh.actionManager) { + mesh.actionManager = new ActionManager(mesh.getScene()); + } - mesh.actionManager.registerAction( - new ExecuteCodeAction( - ActionManager.OnPickTrigger, function (ev) { - onClick(ev); - } - ) - ); + mesh.actionManager.registerAction( + new ExecuteCodeAction( + ActionManager.OnPickTrigger, function (ev) { + onClick(ev); + } + ) + ); + } else { + console.warn('onClick hook only supports referencing Meshes'); + } } }, [ref]); // todo: if use ref.current as dep, duplicate register action. diff --git a/src/plugins/react-babylon-spring/custormProps.ts b/src/plugins/react-babylon-spring/customProps.ts similarity index 79% rename from src/plugins/react-babylon-spring/custormProps.ts rename to src/plugins/react-babylon-spring/customProps.ts index 53cacee2..6dd2c917 100644 --- a/src/plugins/react-babylon-spring/custormProps.ts +++ b/src/plugins/react-babylon-spring/customProps.ts @@ -1,5 +1,4 @@ import { - CustomPropsHandler, ICustomPropsHandler, PropChangeType, PropertyUpdateProcessResult @@ -13,7 +12,7 @@ function parseRgbaString(rgba: string): number[] { const Key = 'react-babylon-spring'; -class CustomColor3StringHandler implements ICustomPropsHandler { +export class CustomColor3StringHandler implements ICustomPropsHandler { get name() { return `${Key}:Color3String` } @@ -36,7 +35,7 @@ class CustomColor3StringHandler implements ICustomPropsHandler { } } -class CustomColor3ArrayHandler implements ICustomPropsHandler { +export class CustomColor3ArrayHandler implements ICustomPropsHandler { get name() { return `${Key}:Color3Array` } @@ -70,7 +69,7 @@ class CustomColor3ArrayHandler implements ICustomPropsHandler } } -class CustomColor4StringHandler implements ICustomPropsHandler { +export class CustomColor4StringHandler implements ICustomPropsHandler { get name() { return `${Key}:Color4String` @@ -96,7 +95,7 @@ class CustomColor4StringHandler implements ICustomPropsHandler { } -class CustomVector3ArrayHandler implements ICustomPropsHandler { +export class CustomVector3ArrayHandler implements ICustomPropsHandler { get name() { return `${Key}:Vector3Array` } @@ -129,8 +128,10 @@ class CustomVector3ArrayHandler implements ICustomPropsHandler (
-) \ No newline at end of file +) + +ExtrudeShapesPlusCSG.story = { + name: 'Extrude (+CSG)', + parameters: + { + notes: 'Extrude Shape and *Constructive Solid Geometry* (CSG). There are no plans to integrate CSG declaratively yet.' + } +} \ No newline at end of file diff --git a/stories/babylonjs/Behaviors/dragNdrop.stories.js b/stories/babylonjs/Behaviors/dragNdrop.stories.js index 02e7f083..445825eb 100644 --- a/stories/babylonjs/Behaviors/dragNdrop.stories.js +++ b/stories/babylonjs/Behaviors/dragNdrop.stories.js @@ -1,4 +1,4 @@ -import React /* , { useState } */ from 'react' +import React from 'react' import { Engine, Scene } from '../../../dist/react-babylonjs' import { Vector3, Color3 } from '@babylonjs/core/Maths/math' import '../../style.css' @@ -48,5 +48,12 @@ export const DragNDrop = () => ( ) DragNDrop.story = { - name: "Drag 'n' Drop" + name: "Drag 'n' Drop", + parameters: { + notes: { + markdown: `Often the declarative version can be much simpler! + + Here is the reference playground: [link](https://www.babylonjs.com/demos/dragndrop/dragdrop.js)` + } + } } \ No newline at end of file diff --git a/stories/babylonjs/PhysicsAndHooks/hooks.stories.js b/stories/babylonjs/Hooks/hooks.stories.js similarity index 95% rename from stories/babylonjs/PhysicsAndHooks/hooks.stories.js rename to stories/babylonjs/Hooks/hooks.stories.js index 208487b3..c46f8096 100644 --- a/stories/babylonjs/PhysicsAndHooks/hooks.stories.js +++ b/stories/babylonjs/Hooks/hooks.stories.js @@ -1,10 +1,9 @@ import React, { useContext, useRef } from 'react' -import { storiesOf } from '@storybook/react' import { Engine, Scene as ReactScene, withScene, BabylonJSContext, SceneContext, useBeforeRender } from '../../../dist/react-babylonjs' import { Vector3, Color3 } from '@babylonjs/core/Maths/math' import '../../style.css' -export default { title: 'Physics and Hooks' }; +export default { title: 'Hooks' }; const Scene = withScene(ReactScene) @@ -58,3 +57,7 @@ export const RenderHooksStory = () => ( ) + +RenderHooksStory.story = { + name: 'useBeforeRender' +} \ No newline at end of file diff --git a/stories/babylonjs/PhysicsAndHooks/more-hooks.stories.js b/stories/babylonjs/Hooks/more-hooks.stories.js similarity index 93% rename from stories/babylonjs/PhysicsAndHooks/more-hooks.stories.js rename to stories/babylonjs/Hooks/more-hooks.stories.js index ad96838f..ccbe204c 100644 --- a/stories/babylonjs/PhysicsAndHooks/more-hooks.stories.js +++ b/stories/babylonjs/Hooks/more-hooks.stories.js @@ -3,7 +3,7 @@ import { storiesOf } from '@storybook/react' import { Engine, Scene, useBabylonEngine, useBabylonCanvas, useBabylonScene } from '../../../dist/react-babylonjs' import { Vector3 } from '@babylonjs/core' -export default { title: 'Physics and Hooks' }; +export default { title: 'Hooks' }; const MyScene = () => { const engine = useBabylonEngine(); @@ -47,4 +47,8 @@ export const ConvenienceHooks = () => (
Look at console.
-) \ No newline at end of file +) + +ConvenienceHooks.story = { + name: 'engine/canvas/scene' +} \ No newline at end of file diff --git a/stories/babylonjs/Events/useClick.stories.js b/stories/babylonjs/Hooks/useClick.stories.js similarity index 89% rename from stories/babylonjs/Events/useClick.stories.js rename to stories/babylonjs/Hooks/useClick.stories.js index 7f1dcc37..c56e594d 100644 --- a/stories/babylonjs/Events/useClick.stories.js +++ b/stories/babylonjs/Hooks/useClick.stories.js @@ -1,10 +1,10 @@ -import React, {useEffect, useRef, useState} from 'react' +import React, {useState} from 'react' import {Engine, Scene, useClick} from '../../../dist/react-babylonjs' import {Vector3} from '@babylonjs/core/Maths/math' import '../../style.css' import {Color3} from "@babylonjs/core/Maths/math.color"; -export default {title: 'Events'}; +export default {title: 'Hooks'}; const getRandomColor = (function () { // const Colors = ['#4F86EC', '#D9503F', '#F2BD42', '#58A55C']; @@ -27,7 +27,7 @@ function WithUseClick() { }); return @@ -45,3 +45,7 @@ export const UseClickEvent = () => ( ); + +UseClickEvent.story = { + name: 'useClick' +} \ No newline at end of file diff --git a/stories/babylonjs/Events/useHover.stories.js b/stories/babylonjs/Hooks/useHover.stories.js similarity index 50% rename from stories/babylonjs/Events/useHover.stories.js rename to stories/babylonjs/Hooks/useHover.stories.js index ab92f557..2d5c70d9 100644 --- a/stories/babylonjs/Events/useHover.stories.js +++ b/stories/babylonjs/Hooks/useHover.stories.js @@ -1,9 +1,10 @@ import React, {useState} from 'react' import {Engine, Scene, useHover} from '../../../dist/react-babylonjs' import {Vector3} from '@babylonjs/core/Maths/math' +import {Control} from '@babylonjs/gui/2D/controls/control'; import '../../style.css' -export default {title: 'Events'}; +export default {title: 'Hooks'}; function WithUseHover() { const [scaling, setScaling] = useState(new Vector3(1, 1, 1)); @@ -13,12 +14,40 @@ function WithUseHover() { ); return } +const WithUseHoverGui = () => { + + const [color, setColor] = useState('white'); + const [isHovered, setIsHovered] = useState(false); + const [rectRef] = useHover( + _ => { + setColor('yellow'); + setIsHovered(true); + }, + _ => { + setColor('white'); + setIsHovered(false) + } + ); + + return + + + + + + + + +} + export const UseHoverEvent = () => (
@@ -26,8 +55,13 @@ export const UseHoverEvent = () => ( +
); + +UseHoverEvent.story = { + name: 'useHover' +} \ No newline at end of file diff --git a/stories/babylonjs/Integrations/chromaJS.stories.js b/stories/babylonjs/Integrations/chromaJS.stories.js index c9747b75..cb2976ba 100644 --- a/stories/babylonjs/Integrations/chromaJS.stories.js +++ b/stories/babylonjs/Integrations/chromaJS.stories.js @@ -1,7 +1,7 @@ -import React, { useEffect } from 'react' +import React from 'react' import { Vector3, Color3 } from '@babylonjs/core'; import { Control } from '@babylonjs/gui'; -import { Engine, Scene, PropChangeType, CustomPropsHandler } from '../../../dist/react-babylonjs' +import { Engine, Scene, PropChangeType, useCustomPropsHandler } from '../../../dist/react-babylonjs' import '../../style.css'; import chroma, { Color } from 'chroma-js' @@ -59,15 +59,7 @@ const distance = (alpha, beta) => { * But this story works well,Animation is smooth。 */ function WithCustomColors(props) { - // useCustomPropsHandler(new ChromajsColor3PropsHandler()); - const handlerRef = CustomPropsHandler.RegisterPropsHandler(new ChromajsColor3PropsHandler()); - useEffect(() => { - return () => { - console.error('de-registering on unmount??', handlerRef.name); - CustomPropsHandler.UnregisterPropsHandler(handlerRef); - } - }, []) - + useCustomPropsHandler(new ChromajsColor3PropsHandler()); const degreeIncrements = (360 / SQUARES_PER_CIRCLE); return ( @@ -130,7 +122,7 @@ function WithCustomColors(props) { ) } -export const chromaJSProps = () => +export const ChromaJSProps = () =>
@@ -142,3 +134,7 @@ export const chromaJSProps = () =>
+ChromaJSProps.story = { + name: 'chroma-js' +} + diff --git a/stories/babylonjs/Integrations/pixi-render.stories.js b/stories/babylonjs/Integrations/pixi-render.stories.js index 81b089a5..96f24ed3 100644 --- a/stories/babylonjs/Integrations/pixi-render.stories.js +++ b/stories/babylonjs/Integrations/pixi-render.stories.js @@ -158,5 +158,5 @@ export const PIXIStory = () => { } PIXIStory.story = { - name: 'PIXI' + name: 'PixiJS v5' } \ No newline at end of file diff --git a/stories/babylonjs/Integrations/reactSpring.stories.js b/stories/babylonjs/Integrations/reactSpring.stories.js index fc5e1d73..24ab8de7 100644 --- a/stories/babylonjs/Integrations/reactSpring.stories.js +++ b/stories/babylonjs/Integrations/reactSpring.stories.js @@ -1,5 +1,16 @@ import React from 'react' -import {Engine, Scene, useHover, useSprings, useSpring, a} from '../../../dist/react-babylonjs' +import { + Engine, + Scene, + useHover, + useSprings, + useSpring, + a, // "animated" versions of all react-babylonjs objects + useCustomPropsHandler, + CustomColor3ArrayHandler, + CustomColor3StringHandler, + CustomVector3ArrayHandler +} from '../../../dist/react-babylonjs' import { Vector3, Color3 } from '@babylonjs/core/Maths/math'; import '../../style.css' @@ -25,7 +36,11 @@ function getCyclePosition(i, blankRadius) { return [x, z]; } -function WithSpring() { +const WithSpring = () => { + useCustomPropsHandler(new CustomColor3ArrayHandler()); + useCustomPropsHandler(new CustomColor3StringHandler()); + useCustomPropsHandler(new CustomVector3ArrayHandler()); + const [props, set] = useSprings(100, i => { const [x, z] = getCyclePosition(i, 30); @@ -87,7 +102,7 @@ function WithSpring() { - + @@ -105,3 +120,6 @@ export const ReactSpring = () => ( ); +ReactSpring.story = { + name: 'react-spring' +} \ No newline at end of file diff --git a/stories/babylonjs/PhysicsAndHooks/physics.stories.js b/stories/babylonjs/Physics/physics.stories.js similarity index 98% rename from stories/babylonjs/PhysicsAndHooks/physics.stories.js rename to stories/babylonjs/Physics/physics.stories.js index 7bb12bb6..2473f92d 100644 --- a/stories/babylonjs/PhysicsAndHooks/physics.stories.js +++ b/stories/babylonjs/Physics/physics.stories.js @@ -9,7 +9,7 @@ import '../../style.css' import * as CANNON from 'cannon'; window.CANNON = CANNON; -export default { title: 'Physics and Hooks' }; +export default { title: 'Physics' }; // The TypeScript version of this story has it's own repo const gravityVector = new Vector3(0, -9.81, 0); diff --git a/stories/babylonjs/VR/withVr.stories.js b/stories/babylonjs/VR/withVr.stories.js index b3381cd1..a527287b 100644 --- a/stories/babylonjs/VR/withVr.stories.js +++ b/stories/babylonjs/VR/withVr.stories.js @@ -1,5 +1,4 @@ import React, { Component } from 'react' -import { storiesOf } from '@storybook/react' import { Engine, Scene } from '../../../dist/react-babylonjs' import ScaledModelWithProgress from '../ScaledModelWithProgress' @@ -7,7 +6,7 @@ import SingleAxisRotateMeshBehavior from '../SingleAxisRotateMeshBehavior' import { Vector3, Color3, Axis } from '@babylonjs/core/Maths/math' import '../../style.css' -export default { title: 'With VR' }; +export default { title: 'VR' }; class WithVR extends Component { constructor () { @@ -93,3 +92,15 @@ export const SimpleVR = () => ( ) + +SimpleVR.story = { + name: 'VR', + parameters: { + notes: { + markdown: `Click on ICO spheres to rotate ghettoblaster model (clockwise/counter-clockwise). + + ##In VR mode + Use your hand-held controls to click ICO spheres and teleport by clicking on the ground.` + } + } +} diff --git a/tools/gh-pages-publish.ts b/tools/gh-pages-publish.ts index afb69ad8..4d8c93f2 100644 --- a/tools/gh-pages-publish.ts +++ b/tools/gh-pages-publish.ts @@ -4,6 +4,7 @@ const url = require("url") let repoUrl let pkg = JSON.parse(readFileSync("package.json") as any) + if (typeof pkg.repository === "object") { if (!pkg.repository.hasOwnProperty("url")) { throw new Error("URL does not exist in repository section") @@ -17,19 +18,36 @@ let parsedUrl = url.parse(repoUrl) let repository = (parsedUrl.host || "") + (parsedUrl.path || "") let ghToken = process.env.GH_TOKEN +if (ghToken === undefined || ghToken === '') { + throw new Error("set/export GH_TOKEN before deploying... :)"); +} + /** - * first run 'npm run build-storybook'. was getting OOM exception with node ver < 12!!! + * OOM exception occurs with node < v12!!! */ -echo("Deploying docs!!!") -cd("storybook-static") -touch(".nojekyll") -exec("git init") -exec("git add .") -exec('git config user.name "Brian Zinn"') -exec('git config user.email "github@wakeskate.com"') -exec('git commit -m "docs(docs): update gh-pages"') -exec( - `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages` -) -echo("Docs deployed!!") -cd("..") +let cwdStorybookStatic = false; +try { + const cdShellString = cd("storybook-static"); + + cwdStorybookStatic = (cdShellString.code === 0); + if (!cwdStorybookStatic) { + throw new Error("run 'build-storybook' before publishing."); + } + + echo("Deploying docs!!!") + touch(".nojekyll") + exec("git init") + exec("git add .") + exec('git config user.name "Brian Zinn"') + exec('git config user.email "github@wakeskate.com"') + exec('git commit -m "docs(docs): update gh-pages"') + exec( + `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages` + ) + echo("Docs deployed!!") + +} finally { + if (cwdStorybookStatic) { + cd(".."); // restore cwd + } +}