Skip to content
This repository has been archived by the owner on Jul 20, 2020. It is now read-only.

Adding a new descriptor

Firtina Ozbalikci edited this page Oct 20, 2017 · 17 revisions

There are easy descriptors and complicated descriptors.

Don't worry about complicated ones yet.

File paths

All descriptors must be placed within \src\core\hostDescriptors\descriptors, under an appropriate subdirectory:

  • For example materials can go within materials, geometries within geometries.
  • 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 be meshBasicMaterial.ts.

Typescript Metadata

Define property types (for TypeScript)

These property types will be used for providing hints for:

e.g.

interface IPointLightProps extends IObject3DProps {
  color?: number | string;
  intensity?: number;
  distance?: number;
  decay?: number;
}

Place within IntrinsicElements

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;
    }
  }
}

Descriptor classes

Extend:

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.

Descriptor Methods

Constructor

The constructor is the place to declare property updates.

Property Update Functions

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 do object.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);
      });
hasSimpleProp
/**
 * 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;
hasProp
/**
 * 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);
 
hasPropGroup
/**
 * 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);

createInstance

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;
}

Export

You should export an instance of your descriptor class.

export default PointLightDescriptor;

Example descriptor

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;
Clone this wiki locally