Skip to content

Commit ba1dea1

Browse files
committed
support property transforms for constructor argument props (vector3, color3, color4).
1 parent 4e8ef5a commit ba1dea1

File tree

3 files changed

+105
-14
lines changed

3 files changed

+105
-14
lines changed

.npmignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
coverage
12
media
23
node_modules
34
src
@@ -10,6 +11,7 @@ tools
1011
.eslintrc
1112
.gitattributes
1213
.gitignore
14+
.nyc_output
1315
.storybook
1416
.travis.yml
1517
.vscode

src/ReactBabylonJSHostConfig.ts

+36-13
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as GENERATED from './generatedCode';
55
import * as CUSTOM_HOSTS from './customHosts';
66

77
import { CreatedInstance, CreatedInstanceMetadata, CustomProps } from './CreatedInstance';
8-
import { HasPropsHandlers, PropertyUpdate, UpdatePayload, PropsHandler } from './PropsHandler';
8+
import { HasPropsHandlers, PropertyUpdate, UpdatePayload, PropsHandler, CustomPropsHandler, PropChangeType } from './PropsHandler';
99
import { LifecycleListener } from "./LifecycleListener";
1010
import { GeneratedParameter, CreationType } from './codeGenerationDescriptors';
1111
import { applyUpdateToInstance, applyInitialPropsToCreatedInstance } from './UpdateInstance';
@@ -125,17 +125,40 @@ function addChild(parent: CreatedInstance<any> | undefined, child: CreatedInstan
125125
console.error('undefined child', parent);
126126
} else {
127127
// doubly linking child to parent
128-
parent.children.push(child) // TODO: need to remove from children as well when removing.
129-
child.parent = parent
128+
parent.children.push(child);
129+
child.parent = parent;
130130
}
131131
}
132132

133133
if (child && child.lifecycleListener && child.lifecycleListener.onParented) {
134-
child.lifecycleListener.onParented(parent!, child)
134+
child.lifecycleListener.onParented(parent!, child);
135135
}
136136

137137
if (parent && parent.lifecycleListener && parent.lifecycleListener.onChildAdded) {
138-
parent.lifecycleListener.onChildAdded(child, parent)
138+
parent.lifecycleListener.onChildAdded(child, parent);
139+
}
140+
}
141+
142+
/**
143+
* Allows constructor arguments to register for dynamically registered property transforms (should exclude undefined).
144+
* TODO: include other types or add "PropsChangeType" to GeneratedParameters (would increase bundle size)
145+
*
146+
* @param type generated name (not same as PropChangeType)
147+
* @param value parameter value
148+
*/
149+
const getConstructorValue = (type: string, value: any) : any => {
150+
switch(type) {
151+
case 'BabylonjsCoreVector3':
152+
const v3result = CustomPropsHandler.HandlePropsChange(PropChangeType.Vector3, undefined, value);
153+
return v3result.processed ? v3result.value : value;
154+
case 'BabylonjsCoreColor3':
155+
const c3result = CustomPropsHandler.HandlePropsChange(PropChangeType.Vector3, undefined, value);
156+
return c3result.processed ? c3result.value : value;
157+
case 'BabylonjsCoreColor4':
158+
const c4result = CustomPropsHandler.HandlePropsChange(PropChangeType.Color4, undefined, value);
159+
return c4result.processed ? c4result.value : value;
160+
default:
161+
return value;
139162
}
140163
}
141164

@@ -335,7 +358,7 @@ const ReactBabylonJSHostConfig: HostConfig<
335358
} else {
336359
const createInfoArgs = classDefinition.CreateInfo;
337360
metadata = classDefinition.Metadata;
338-
let generatedParameters: GeneratedParameter[] = createInfoArgs.parameters
361+
let generatedParameters: GeneratedParameter[] = createInfoArgs.parameters;
339362

340363
if (props.fromInstance !== undefined) {
341364
if (createInfoArgs.namespace.startsWith('@babylonjs/')) {
@@ -356,25 +379,25 @@ const ReactBabylonJSHostConfig: HostConfig<
356379
let args = generatedParameters.map(generatedParameter => {
357380
if (Array.isArray(generatedParameter.type)) {
358381
// TODO: if all props are missing, warn if main prop (ie: options) is required.
359-
let newParameter = {} as any
382+
let newParameter = {} as any;
360383
generatedParameter.type.forEach(subParameter => {
361-
let subPropValue = props[subParameter.name]
384+
let subPropValue = getConstructorValue(subParameter.type as string, props[subParameter.name]);
362385
if (subPropValue === undefined && subParameter.optional === false && generatedParameter.optional === false) {
363-
console.warn("Missing a required secondary property:", subParameter.name)
386+
console.warn("Missing a required secondary property:", subParameter.name);
364387
} else {
365-
newParameter[subParameter.name] = subPropValue
388+
newParameter[subParameter.name] = subPropValue;
366389
}
367390
})
368-
return newParameter
391+
return newParameter;
369392
} else {
370-
let value = props[generatedParameter.name]
393+
let value = getConstructorValue(generatedParameter.type, props[generatedParameter.name]);
371394
if (value === undefined) {
372395
// NOTE: we removed the hosted Scene component, which needs (generatedParameter.type == "BabylonjsCoreEngine")
373396
// SceneOrEngine type is Scene
374397
if (generatedParameter.type.includes("BabylonjsCoreScene") || (generatedParameter.type === "any" && generatedParameter.name === "scene")) {
375398
// MeshBuild.createSphere(name: string, options: {...}, scene: any)
376399
// console.log('Assigning scene to:', type, generatedParameter)
377-
value = scene
400+
value = scene;
378401
}
379402
}
380403

test/render.spec.tsx

+67-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
2-
import { AbstractMesh, Color4, Engine, NullEngine, Scene, Vector3 } from "@babylonjs/core";
2+
import { AbstractMesh, ArcRotateCamera, Camera, Color4, Engine, NullEngine, Scene, Vector3 } from "@babylonjs/core";
33
import sinon from "sinon";
44
import assert from 'assert';
55

66
import { Container } from "../src/ReactBabylonJSHostConfig";
77
import { createReconciler, ReconcilerInstance } from '../src/render';
88
import { SceneContext } from '../src/hooks/scene';
9+
import { CustomPropsHandler, PropChangeType } from '../src/PropsHandler';
910

1011
describe(' > Reconciler/Render tests', function testSuite() {
1112

@@ -71,7 +72,72 @@ describe(' > Reconciler/Render tests', function testSuite() {
7172
const box: AbstractMesh | undefined = container.scene.meshes.find(m => m.name === 'box');
7273
assert.ok(box !== undefined)
7374
assert.strictEqual('boxMat', box.material.name);
75+
})
7476

77+
it('Should transform registered Vector3 conversion from array.', async function test() {
78+
class CustomVector3ArrayHandler {
79+
get name() {
80+
return `test:Vector3Array`
81+
}
82+
83+
get propChangeType() {
84+
return PropChangeType.Vector3;
85+
}
86+
87+
accept(newProp) {
88+
// console.log('Vector3: newProp:', newProp, Array.isArray(newProp));
89+
return Array.isArray(newProp);
90+
}
91+
92+
process(oldProp, newProp) {
93+
if (oldProp === undefined || oldProp.length !== newProp.length) {
94+
// console.log(`found diff length (${oldProp?.length}/${newProp?.length}) Color3Array new? ${oldProp === undefined}`)
95+
return {
96+
processed: true,
97+
value: Vector3.FromArray(newProp)
98+
};
99+
}
100+
101+
for (let i = 0; i < oldProp.length; i++) {
102+
if (oldProp[i] !== newProp[i]) {
103+
// console.log('found difference...', oldProp, newProp);
104+
return {
105+
processed: true,
106+
value: Vector3.FromArray(newProp)
107+
};
108+
}
109+
}
75110

111+
// console.log('not processed...');
112+
return {processed: false, value: null};
113+
}
114+
}
115+
CustomPropsHandler.RegisterPropsHandler(new CustomVector3ArrayHandler());
116+
117+
const container: Container = getRootContainerInstance();
118+
const reconciler: ReconcilerInstance = createReconciler({});
119+
120+
const sceneGraph = (
121+
<SceneContext.Provider value={{
122+
scene: container.scene,
123+
sceneReady: true
124+
}}>
125+
<arcRotateCamera name='camera1' alpha={0} beta={Math.PI / 3} radius={10} target={[0, 1, 0] as any as Vector3} />
126+
<hemisphericLight name='light1' direction={Vector3.Up()} />
127+
<box
128+
name="box"
129+
size={2}
130+
position={new Vector3(0, 1.2, 0)}
131+
>
132+
<standardMaterial name='boxMat' />
133+
</box>
134+
</SceneContext.Provider>
135+
)
136+
reconciler.render(sceneGraph, container, () => { /* empty for now */ }, null)
137+
138+
const camera: Camera | undefined = container.scene.cameras.find(c => c.name === 'camera1');
139+
assert.ok(camera !== undefined);
140+
assert.ok(camera instanceof ArcRotateCamera, 'Should be ArcRotateCamera');
141+
assert.ok(Vector3.Up().equals(camera.target), 'should be the same as Vector3.Up');
76142
})
77143
})

0 commit comments

Comments
 (0)