Skip to content

Commit

Permalink
new feature: behavior (#49)
Browse files Browse the repository at this point in the history
* feat[behavior]: add feature and story

Co-authored-by: huxiaohui <huxiaohui@megvii.com>
  • Loading branch information
hookex and huxiaohui committed Mar 17, 2020
1 parent 12db283 commit d418e6d
Show file tree
Hide file tree
Showing 10 changed files with 1,484 additions and 102 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/CreatedInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface InstanceMetadataParameter {
customType?: boolean // not used by code-gen
isCamera?: boolean
isEffectLayer?: boolean;
isBehavior?: boolean;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/ReactBabylonJSHostConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ const ReactBabylonJSHostConfig: HostConfig<
lifecycleListener = new CUSTOM_COMPONENTS.CameraLifecycleListener(scene, props, canvas as HTMLCanvasElement)
} else if (metadata.isNode) {
lifecycleListener = new CUSTOM_COMPONENTS.NodeLifecycleListener();
} else if (metadata.isEffectLayer) {
lifecycleListener = new CUSTOM_COMPONENTS.EffectLayerLifecycleListener();
} else if (metadata.isBehavior) {
lifecycleListener = new CUSTOM_COMPONENTS.BehaviorLifecycleListener();
}

// here we dynamically assign listeners for specific types.
Expand Down
21 changes: 21 additions & 0 deletions src/customComponents/BehaviorsLifecycleListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CreatedInstance } from "../CreatedInstance"
import { LifecycleListener } from "../LifecycleListener"
import { Node } from '@babylonjs/core/node'
import {Behavior, PointerDragBehavior} from "@babylonjs/core";

type behaviors = Behavior<PointerDragBehavior>;

export default class BehaviorLifecycleListener implements LifecycleListener<behaviors> {
onParented(parent: CreatedInstance<any>, child: CreatedInstance<any>) {
if (parent.metadata.isNode && child.metadata.isBehavior) {
// TODO: consider add option for setParent(), which parents and maintains mesh pos/rot in world space
// child.hostInstance.setParent(parent.hostInstance)
parent.hostInstance.addBehavior(child.hostInstance)
}
}
onChildAdded(parent: CreatedInstance<any>, child: CreatedInstance<any>) {/* empty */}
onMount(instance: CreatedInstance<behaviors>) {/* empty */}
onUnmount(): void {/* empty */}
}


11 changes: 11 additions & 0 deletions src/customComponents/EffectLayerLifecycleListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CreatedInstance } from "../CreatedInstance"
import { LifecycleListener } from "../LifecycleListener"
import { Mesh } from '@babylonjs/core/Meshes'
import {EffectLayer} from "@babylonjs/core";

export default class EffectLayerLifecycleListener implements LifecycleListener<EffectLayer> {
onParented(parent: CreatedInstance<any>, child: CreatedInstance<any>) {/* empty */}
onChildAdded(parent: CreatedInstance<any>, child: CreatedInstance<any>) {/* empty */}
onMount(instance: CreatedInstance<EffectLayer>) {/* empty */}
onUnmount(): void {/* empty */}
}
2 changes: 2 additions & 0 deletions src/customComponents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export {default as TargetPropsHandler} from "./TargetPropsHandler"
export {default as TexturesLifecycleListener} from "./TexturesLifecycleListener"
export {default as VRExperienceHelperLifecycleListener} from "./VRExperienceHelperLifecycleListener"
export {default as NodeLifecycleListener} from "./NodeLifecycleListener";
export {default as EffectLayerLifecycleListener} from "./EffectLayerLifecycleListener";
export {default as BehaviorLifecycleListener} from "./BehaviorsLifecycleListener";
1,291 changes: 1,198 additions & 93 deletions src/generatedCode.ts

Large diffs are not rendered by default.

147 changes: 145 additions & 2 deletions src/generatedProps.ts

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions stories/babylonjs/8-behaviors/pointerDragBehavior.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import {storiesOf} from '@storybook/react'
import {Engine, Scene} from '../../../dist/react-babylonjs.es5'
import {Vector3} from '@babylonjs/core/Maths/math'
import '../../style.css'

/**
* official demo:
* https://www.babylonjs-playground.com/#YEZPVT
*
* TODO: camera autoAttaching?
*/

function WithPointerDragBehavior() {
return (
<>
<sphere name='sphere1' diameter={2} segments={16} position={new Vector3(0, 1, 0)}>
<pointerDragBehavior dragAxis={new Vector3(1, 0, 0)}
useObjectOrientationForDragging={true}
onDragStartObservable={_ => console.log('dragStart')}
onDragObservable={_ => console.log('drag')}
onDragEndObservable={_ => console.log('dragEnd')}/>
</sphere>
<ground name='ground1' width={6} height={6} subdivisions={2}/>
</>
)
}

export default storiesOf('Behaviors', module)
.add('Pointer drag behavior', () => (
<div style={{flex: 1, display: 'flex'}}>
<Engine antialias adaptToDeviceRatio canvasId='babylonJS'>
<Scene>
<freeCamera name='camera1'
position={new Vector3(0, 5, -10)}
setTarget={[Vector3.Zero()]}/>
<hemisphericLight name='light1' intensity={0.7} direction={Vector3.Up()}/>
<WithPointerDragBehavior/>
</Scene>
</Engine>
</div>
)
)
64 changes: 58 additions & 6 deletions tools/generate-code.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* To debug code generation use the launch config in VS Code - "Generate Code (debug)"
*/
*/
import {
Project,
ts,
Expand Down Expand Up @@ -138,6 +138,14 @@ classesOfInterest.set("PhysicsImpostor", undefined);
classesOfInterest.set("VRExperienceHelper", undefined);
classesOfInterest.set("DynamicTerrain", undefined);
classesOfInterest.set("EffectLayer", undefined);
classesOfInterest.set("Behavior", undefined);

/**
* In babylon.js, Behavior is a interface, not a class.
* Each specific behavior is a separated class.
* Add it to classesOfInterest if className includes keyword.
*/
const classesOfKeywordInterest = ['Behavior'];

type ModuleDeclaration = {
moduleSpecifier: string,
Expand Down Expand Up @@ -300,9 +308,21 @@ const createTypeFromText = (typeText: string, targetFiles: SourceFile[], customP
return typeText;
}

/**
* If has interest file, add it's class info to classesOfInterest.
* @param classDeclaration
* @param sourceFiles
*/
const addSourceClass = (classDeclaration: ClassDeclaration, sourceFiles: SourceFile[]) => {
const className = classDeclaration.getName()
if (className !== undefined && classesOfInterest.has(className)) {

if (className === undefined) {
return;
}

const findByKeyword = classesOfKeywordInterest.some(keyword => className.includes(keyword));

if (classesOfInterest.has(className) || findByKeyword) {
const moduleDeclaration = getModuleDeclarationFromClassDeclaration(classDeclaration);

addNamedImportToFile(moduleDeclaration, sourceFiles, true);
Expand Down Expand Up @@ -592,18 +612,27 @@ const addCreateInfoFromFactoryMethod = (method: MethodDeclaration, factoryClass:
writeTypeAlias(generatedPropsSourceFile, `${targetClass.getName()}PropsCtor`, typeProperties);
}

/**
* get some methods from babylonjs class
* @param classDeclaration
*/
const getInstanceSetMethods = (classDeclaration: ClassDeclaration): MethodDeclaration[] => {
let instanceSetMethods: MethodDeclaration[] = []
classDeclaration.getInstanceMethods().forEach((methodDeclaration: MethodDeclaration) => {
const methodName = methodDeclaration.getName();
if (methodName.startsWith("set")) {
// TODO: add ?
if (methodName.startsWith("set") || methodName.startsWith('add')) {
instanceSetMethods.push(methodDeclaration)
}
})

return instanceSetMethods;
}

/**
* get props from babylonjs class
* @param classDeclaration
*/
const getInstanceProperties = (classDeclaration: ClassDeclaration): (PropertyDeclaration | SetAccessorDeclaration)[] => {
let result: (PropertyDeclaration | SetAccessorDeclaration)[] = [];

Expand Down Expand Up @@ -763,7 +792,7 @@ const writePropertyAsUpdateFunction = (propsProperties: OptionalKind<PropertySig
}

/**
*
* create Fiber***hPropsHandler class
* @param classDeclaration
* @param rootBaseClass Base class (or same class if there is no base class). For factory methods is the class being created.
* @param moduleDeclaration
Expand Down Expand Up @@ -911,6 +940,7 @@ const addPropsAndHandlerClasses = (generatedCodeSourceFile: SourceFile, generate
writer.writeLine(`let updates: ${PropertyUpdateInterface}[] = [];`)

let addedMeshProperties = new Set<string>();
// write Fiber***Props
propertiesToAdd.sort((a, b) => a.getName().localeCompare(b.getName())).forEach((property: (PropertyDeclaration | SetAccessorDeclaration)) => {
writePropertyAsUpdateFunction(typeProperties, writer, property, addedMeshProperties, babylonClassDeclaration.importAlias, [generatedCodeSourceFile, generatedPropsSourceFile]);
})
Expand Down Expand Up @@ -978,6 +1008,14 @@ const writeTypeAlias = (file: SourceFile, name: string, typeProperties: Optional
})
}

/**
* create Fiber***CtorProps interface from babylonjs class constructor's parameters
* @param sourceClass
* @param targetClass
* @param moduleDeclaration
* @param generatedCodeSourceFile
* @param generatedPropsSourceFile
*/
const addCreateInfoFromConstructor = (sourceClass: ClassDeclaration, targetClass: ClassDeclaration, moduleDeclaration: ModuleDeclaration, generatedCodeSourceFile: SourceFile, generatedPropsSourceFile: SourceFile): void => {
// this will allow us to do reflection to create the BabylonJS object from application props.
const ctorArgsProperty = targetClass.addProperty({
Expand Down Expand Up @@ -1053,6 +1091,14 @@ const addCreateInfoFromConstructor = (sourceClass: ClassDeclaration, targetClass
writeTypeAlias(generatedPropsSourceFile, `${ClassNamesPrefix}${className}PropsCtor`, typeProperties);
}

/**
* create Metadata|ReactHostElement|ConstructorProps from babylonjs class.
* @param generatedCodeSourceFile
* @param generatedPropsSourceFile
* @param classNamespaceTuple
* @param metadata
* @param extra
*/
const createClassesDerivedFrom = (generatedCodeSourceFile: SourceFile, generatedPropsSourceFile: SourceFile, classNamespaceTuple: ClassNameSpaceTuple, metadata?: InstanceMetadataParameter, extra?: (newClassDeclaration: ClassDeclaration, originalClassDeclaration: ClassDeclaration) => void): void => {
let classDeclaration: ClassDeclaration | undefined = classNamespaceTuple.classDeclaration;

Expand Down Expand Up @@ -1086,7 +1132,7 @@ const createClassesDerivedFrom = (generatedCodeSourceFile: SourceFile, generated
const newClassDeclaration = createClassDeclaration(classDeclaration, baseClassDeclarationForCreate, generatedCodeSourceFile, generatedPropsSourceFile, extra);
addCreateInfoFromConstructor(classDeclaration, newClassDeclaration, classNamespaceTuple.moduleDeclaration, generatedCodeSourceFile, generatedPropsSourceFile);

const metadataClone = metadata === undefined ? {} : { ...metadata };
const metadataClone = metadata === undefined ? {} : { ...metadata };
if (inheritsFromNode) {
metadataClone.isNode = true;
}
Expand Down Expand Up @@ -1370,6 +1416,13 @@ const generateCode = async () => {
createSingleClass("VRExperienceHelper", generatedCodeSourceFile, generatedPropsSourceFile)
createSingleClass("DynamicTerrain", generatedCodeSourceFile, generatedPropsSourceFile, undefined, { acceptsMaterials: true })

classesOfInterest.forEach((_,className) => {
if (className.includes('Behavior')) {
createSingleClass(className as string, generatedCodeSourceFile,
generatedPropsSourceFile, undefined, {isBehavior: true})
}
})

if (classesOfInterest.get("Scene")) {
// Scene we only want to generate the handlers. Constructor is very simple - just an Engine
const sceneTuple: ClassNameSpaceTuple = classesOfInterest.get("Scene")!
Expand Down Expand Up @@ -1478,4 +1531,3 @@ result.then(() => {
}).catch(reason => {
console.error('failed:', reason);
})

0 comments on commit d418e6d

Please sign in to comment.