-
Notifications
You must be signed in to change notification settings - Fork 10
Adding a new descriptor
There are easy descriptors and complicated descriptors.
Don't worry about complicated ones yet.
All descriptors must be placed within \src\core\hostDescriptors\descriptors
,
under an appropriate subdirectory:
- For example materials can go within
materials
, geometries withingeometries
. - If you're not sure, just place the file anywhere within
descriptors
.
The filename should match what you're describing:
- For a
<meshBasicMaterial/>
, the filename should bemeshBasicMaterial.ts
.
These property types will be used for providing hints for:
- providing hints for usage (see IntrinsicElements below)
- host instance creation (see createInstance)
- property updating (see Property Update Functions).
e.g.
interface IPointLightProps extends IObject3DProps {
color?: number | string;
intensity?: number;
distance?: number;
decay?: number;
}
These are used in order to allow TypeScript to see them as React Elements, and for validation.
For example, it prevents a <pointLight />
from having any property apart from what's defined in the interface above:
-
color
, -
intensity
, -
distance
, -
decay
.
e.g.
declare global {
namespace JSX {
interface IntrinsicElements {
// IThreeElementPropsBase declares the `key` and `ref` props
// used for all react elements
pointLight: IThreeElementPropsBase<PointLight>
& IPointLightProps;
}
}
}
All ReactThreeRenderer descriptors must extend ReactThreeRendererDescriptor
:
/**
* A base type for all ReactThreeRenderer element descriptors.
*/
abstract class ReactThreeRendererDescriptor< //
/**
* @typedef {*} ReactThreeRendererDescriptor.TProps
* @type ReactThreeRendererDescriptor.TProps
* The expected property types to be used for host instance creation
* and property updates.
*/
TProps = any,
/**
* @typedef {*} ReactThreeRendererDescriptor.TInstance
* @type ReactThreeRendererDescriptor.TInstance
* The instance type to be created and updated.
*/
TInstance = any,
/**
* @typedef {*} ReactThreeRendererDescriptor.TParent
* @type ReactThreeRendererDescriptor.TParent
* The parent types that the host instances can be mounted into.
*/
TParent = any,
/**
* @typedef {*} ReactThreeRendererDescriptor.TParent
* @type ReactThreeRendererDescriptor.TParent
* The types of objects the host instance will accept as children.
*/
TChild = any>{
/* ... */
}
For example:
// see "Define property types (for TypeScript)" heading
interface IObject3DProps extends IPropsWithChildren {
name?: string;
position?: Vector3;
rotation?: Euler;
quaternion?: Quaternion;
lookAt?: Vector3;
}
class Object3DDescriptorBase<
// expecting all object3D types to have the properties above
TProps extends IObject3DProps,
// expecting all object3D types to extend Object3D
T extends Object3D,
// expecting all children to be Object3D by default
TChild = Object3D,
// and their parents to be Object3D by default
TParent = Object3D>
extends ReactThreeRendererDescriptor<TProps,
T,
TParent,
TChild>{/* ... */}
Most descriptors would be extending Object3DDescriptorBase
, MaterialDescriptorBase
, and so on,
which in turn extend ReactThreeRendererDescriptor
.
The constructor is the place to declare property updates.
For each property defined in the interface above, you need to create update functions.
This can be done in various ways:
-
hasSimpleProp:
-
When you know the property will be updated via simple assignments.
this.hasSimpleProp(name);
Whenever the
name
property of an object changes, this will doobject.name = newName
.
-
-
hasProp:
-
For more complex updating, for example for a
color
property:this.hasProp("color", (instance: PointLight, newValue: any): void => { instance.color.set(newValue); });
-
-
hasPropGroup:
-
When you want to update multiple properties at once:
this.hasPropGroup(["width", "height"], (instance: WebGLRenderer, newSize: { width?: number, height?: number, }) => { const updatedSize: { width: number, height: number } = Object.assign({}, instance.getSize(), newSize); instance.setSize(updatedSize.width, updatedSize.height); });
-
/**
* Declares that this property can be updated with simple assignments.
* @param {string} propName
* The name of the property
* @param {boolean} updateInitial
* Does this property need to be updated right after host instance creation?
* @param {boolean} wantsRepaint
* Should the modification of this property trigger a re-render?
*/
function hasSimpleProp(
propName: string,
updateInitial: boolean = true,
wantsRepaint: boolean = true);
/**
* A property updater function.
*
* @param {TInstance} instance
* The host instance.
* @param {TPropType} newValue
* A value to set for the property.
* @param {PropertyUpdater.TProps}oldProps
* What the properties of the element were before the update.
* @param {TProps} newProps
* What the current properties of the element are.
*/
type PropertyUpdater< //
/**
* The property types for the host instance.
*/
TProps,
/**
* The instance type to be created and updated
*/
TInstance,
/**
* @typedef {any} TPropType
* @type TPropType
* The property type to update.
*/
TPropType> = (instance: TInstance,
newValue: TPropType,
oldProps: TProps,
newProps: TProps) => void;
/**
* Allows you to define an update function for a single property.
*
* @param {string} propName
* The name of the property
*
* @param {PropertyUpdater<TProps, TInstance, TProp>}updateFunction
* Handle updating of the property here.
*
* @param {boolean} updateInitial
* Does this property need to be updated right after host instance creation?
*
* @param {boolean} wantsRepaint
* Should the modification of this property trigger a re-render?
*/
function hasProp< //
/**
* TProp = The property type to be updated.
*/
TProp>(propName: string,
updateFunction: (instance: TInstance,
newValue: TPropType,
oldProps: TProps,
newProps: TProps) => void,
updateInitial: boolean = true,
wantsRepaint: boolean = true);
/**
* Helps to update multiple properties at once.
* @param {string[]} propNames
* The names of the properties
* @param {PropertyUpdater<TProps, TInstance, TProp>} updateFunction
* Similar to `hasProp`s updateFunction, but it will expect
* a key-value pair of `propertyName` to `newValue`
* @param {boolean} updateInitial
* Handle updating of the property here.
* @param {boolean} wantsRepaint
* Does this property need to be updated right after host instance creation?
*/
function hasPropGroup<TProp>(
propNames: string[],
updateFunction: PropertyUpdater<TProps, TInstance, TProp>,
updateInitial: boolean = true,
wantsRepaint: boolean = true);
This method is used to create the host instances.
abstract class ReactThreeRendererDescriptor< //
TProps = any,
TInstance = any,
TParent = any,
TChild = any>{
/**
* This function should create a host instance using the properties.
* @param {TProps} props
* The properties of the element
* @param {*} rootContainerInstance
* The object that `ReactTHREERenderer.render` was called upon.
* @return {TInstance} The new host instance
*/
public abstract createInstance(
props: TProps,
rootContainerInstance: any): TInstance;
}
You should export an instance of your descriptor class.
export default PointLightDescriptor;
import * as THREE from "three";
import {PointLight} from "three";
import {IObject3DProps, Object3DDescriptorBase} from "./object3D";
interface IPointLightProps extends IObject3DProps {
color?: number | string;
intensity?: number;
distance?: number;
decay?: number;
}
declare global {
namespace JSX {
interface IntrinsicElements {
pointLight: IThreeElementPropsBase<PointLight> & IPointLightProps;
}
}
}
class PointLightDescriptor extends Object3DDescriptorBase<IPointLightProps,
PointLight> {
constructor() {
super();
this.hasProp("color", (instance: PointLight,
newValue: any): void => {
instance.color.set(newValue);
}, false);
}
public createInstance(props: IPointLightProps) {
return new THREE.PointLight(
props.color,
props.intensity,
props.distance,
props.decay);
}
}
export default PointLightDescriptor;