Components

List of available components for the composable player.

<Viewer>

The Viewer component contains the Three.JS Canvas component within it, so it gathers everything related to the player under its wings. This is also where we specify the authentication for any of its children to allow access to the assets inside the ThreeKit Platform.

🚧

Warning!

In order to use the Viewer within a Next.JS implementation, we need to implement it within a separate component, which then has to be imported on the main page using the dynamic library. This is necessary in order to disable SSR completely, as the underlying architecture makes use of the window object.

See example in the Getting Started section.

Prop List

NameTypeStatus
authThreekitAuthPropsRequired
resolver{UnifiedResolver()}Optional
diagnosticsBooleanOptional
fgColorStringOptional
canvasPropsRenderPropsOptional
childrenReact.ReactNodeOptional

auth: ThreekitAuthProps - Required

This is a required prop, as it needs to set the proper authentication for the Viewer components to communicate with the ThreeKit Platform.

resolver: {UnifiedResolver()} - Optional

The resolver is used by the ThreeKit gltf exporter to generate each variant and send it to the player.

The recommended resolver is the following:

UnifiedResolver({
  cacheScope: "v1",
  optimize: false,
})

The cacheScope allows us to specify which cached version of the assets should be loaded. When the assets have been updated on the platform, the string value for the cacheScope should be updated manually at this time to a new value. The value itself can be anything, and it's up to the user.

diagnostics: Boolean - Optional

Choose whether to generate diagnostics or not.

fgColor: String - Optional

CSS colors for tinting the icons.

canvasProps:RenderProps - Optional

Props that can be passed to the Three.JS Canvas created by the Viewer.

Visit the official documentation.

children: React.ReactNode - Optional

Children components to be passed to the Viewer.

Example

"use client";
import {
  Scene,
  Viewer,
  UnifiedResolver,
} from "@threekit/react-three-fiber";

export default function Home() {
  const auth = {
    orgId: "YOUR_ORG_ID",
    host: "preview.threekit.com",
    publicToken: "YOUR_PUBLIC_TOKEN",
  };

  return (
    <div style={{ width: "100%", height: "100%" }}>
      <Viewer
        auth={auth}
        ui={true}
        resolver={UnifiedResolver({
          cacheScope: "v1",
          optimize: false,
        })}
      >
        <MyScene assetId={"b1571ea4-e7f3-400f-ba58-b875b382a4a5"} />
      </Viewer>
    </div>
  );
}
"use client";
import dynamic from "next/dynamic";
const Player = dynamic(() => import("../components/Player"), { ssr: false });

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center p-24">
      <div style={{ width: "673px", height: "550px", background: "#eee" }}>
        <Player />
      </div>
    </main>
  );
}

<MyScene>

The composable package does not currently contain a usable Scene component. However, here is an example of a Scene component that can be used to import scene assets, as well as changing cameras. You will find the Source Code below.

Prop List

NameTypeStatus
assetIdStringRequired
cameraTHREE.PerspectiveCameraOptional
configurationConfigurationOptional
settingsExporterSettingsOptional

assetId: String - Required

This represents the assetId of either a Scene or Catalog Item from the ThreeKit Platform.

camera: THREE.PerspectiveCamera - Optional

This prop can be used to set the specified camera object as the new active camera in the player. The Scene will copy its settings and apply them to the default camera of the player, retaining the original camera properties intact.

If connected to a state variable, this allows the user to change cameras automatically when the state changes.

configuration: Configuration - Optional

This prop can be used to request the gltf for a particular variant of the asset. If not specified, it will request the default configuration of the asset as specified in the Platform.

Connect this to a state variable that stores the configuration data, in order to update the Scene on every state change.

It is recommended to use the Composable Configurator to drive this configuration.

settings: ExporterSettings - Optional

These settings allow you to choose how the asset's contents should be exported and interpreted by the gltf exporter.

Source Code

Simply save this as a component file. This code makes use of the useScene hook in order to build our own Scene component.

"use client";
import { AssetProps, useScene } from "@threekit/react-three-fiber";
import * as THREE from "three";
import { useThree, useFrame, Object3DProps } from "@react-three/fiber";
import { forwardRef, useEffect, useState } from "react";

type MySceneProps = AssetProps &
  Object3DProps & {
    camera?: THREE.PerspectiveCamera | null;
  };

function cloneCamera(camera: THREE.PerspectiveCamera | null) {
  if (!camera) return null;
  const worldPosition = new THREE.Vector3();
  const worldRotation = new THREE.Quaternion();
  camera.updateMatrixWorld(true);
  camera.matrixWorld.decompose(
    worldPosition,
    worldRotation,
    new THREE.Vector3()
  );
  const cameraClone = camera.clone();
  cameraClone.position.copy(worldPosition);
  cameraClone.rotation.setFromQuaternion(worldRotation);
  cameraClone.updateMatrixWorld(true);
  return cameraClone;
}

export default forwardRef(function MyScene(
  props: MySceneProps,
  ref: React.Ref<THREE.Object3D>
) {
  const { camera, assetId, configuration, ...r3fProps } = props;
  const threekitAsset = useScene({ assetId, configuration });
  const r3f = useThree((r3f) => r3f);
  const { domeLight } = threekitAsset.scene.userData;
  const [defaultCamera, setDefaultCamera] =
    useState<THREE.PerspectiveCamera | null>(
      cloneCamera(threekitAsset.scene.userData.camera)
    );

  useEffect(() => {
    if (!camera) return;
    setDefaultCamera(cloneCamera(camera));
  }, [camera]);

  useEffect(() => {
    if (!r3f.scene || !domeLight?.image) return;
    const oldEnv = r3f.scene.environment;
    r3f.scene.environment = domeLight.image;
    return () => {
      // undo our environment change, assuming it hasn't been overridden
      if (r3f.scene.environment === domeLight.image) {
        r3f.scene.environment = oldEnv;
      }
    };
  }, [r3f, domeLight]);

  useEffect(() => {
    if (!defaultCamera) return;
    defaultCamera.aspect = r3f.viewport.aspect;
    defaultCamera.updateProjectionMatrix(); // only required initially. R3F only updates the aspect on canvas resize

    const oldCam = r3f.camera;
    if (r3f.camera === defaultCamera) return;
    r3f.set({ camera: defaultCamera });
    return () => {
      if (r3f.camera === oldCam) {
        // undo our camera change, assuming it hasn't been overridden
        r3f.set({ camera: oldCam });
      }
    };
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [defaultCamera]);

  useFrame(() => {
    if (!domeLight?.image) return;
    const intensity = domeLight.intensity ?? 1;

    // unfortunately there's no global envMapFactor in three
    r3f.scene?.traverse((obj) => {
      const mesh = obj as THREE.Mesh;
      if (mesh.isMesh) {
        const materials = Array.isArray(mesh.material)
          ? mesh.material
          : [mesh.material];
        for (const mat of materials) {
          if ("envMap" in mat && "envMapFactor" in mat) {
            if (!mat.envMap) {
              // only assign the intensity if the material doesn't have an envMap override
              mat.envMapFactor = intensity;
            }
          }
        }
      }
    });
  });

  return (
    <>
      {threekitAsset ? (
        <primitive
          object={threekitAsset.scene}
          ref={ref}
          castShadow
          receiveShadow
          {...r3fProps}
        />
      ) : null}
    </>
  );
});

Example

This component can then be imported into your Player component, and used as a child of the <Viewer> component:

<Viewer
  auth={auth}
  ui={true}
  resolver={UnifiedResolver({
    cacheScope: "v1",
    optimize: false,
  })}
>
  <MyScene assetId={"b1571ea4-e7f3-400f-ba58-b875b382a4a5"} />
</Viewer>

<Asset>

The <Asset> component allows us to load the asset associated with a catalog item or model asset. It can be paired together with a Scene and other Assets within the Viewer.

The composable package does not currently contain a fully usable Asset component. However, here is an example of a Asset component that can be used to import model assets, as well as setting a ref. You will find the Source Code below.

Props

NameTypeStatus
assetIdStringRequired
configurationConfigurationOptional
settingsExporterSettingsOptional

assetId: String - Required

This represents the assetId of either a Catalog Item or a Model Asset from the ThreeKit Platform.

configuration: Configuration - Optional

This prop can be used to request the gltf for a particular variant of the asset. If not specified, it will request the default configuration of the asset as specified in the Platform.

Connect this to a state variable that stores the configuration data, in order to update the Model on every state change.

It is recommended to use the Composable Configurator to drive this configuration.

settings: ExporterSettings - Optional

These settings allow you to choose how the asset's contents should be exported and interpreted by the gltf exporter.

Source Code

Simply save this as a component file. This code makes use of the useAsset hook in order to build our own Asset component. The <Asset> component included in "@threekit/react-three-fiber" does not currently support ref properly.

import { forwardRef } from "react";
import { type AssetProps, useAsset } from "@threekit/react-three-fiber";
import * as THREE from "three";

export default forwardRef(function Asset(
  props: AssetProps,
  ref: React.Ref<THREE.Object3D>
) {
  const { assetId, configuration, ...r3fProps } = props;
  const threekitAsset = useAsset({ assetId, configuration });

  return (
    <>
      {threekitAsset ? (
        <primitive
          object={threekitAsset.scene}
          caseShadows
          receiveShadows
          {...r3fProps}
          ref={ref}
        />
      ) : null}
    </>
  );
});

Example

Please note how in this example we are loading the Asset in a separate <group> container. The <group> for the Asset component can be positioned to a specific location as shown here, or you can position it to the location of a specific node that is loaded from the Scene using the traverse method.

<Viewer
  auth={auth}
  ui={true}
  resolver={UnifiedResolver({
    cacheScope: "v1",
    optimize: false,
  })}
>
  <OrbitControls />
  <MyScene assetId={"b1571ea4-e7f3-400f-ba58-b875b382a4a5"} />
  <group position={new THREE.Vector3(0, 0, 0)}>
    <Asset assetId="3f8684ed-785f-4862-93bf-af7eeaaab961" />
  </group>
</Viewer>;

<TurntableControls>

This component allows us to replicate the Node/Turntable Camera Control from the monolithic player. With this control mode we need to specify a target child that will receive the rotation around Y axis when the user drags to orbit. This will correspond to a horizontal drag. The vertical drag will continue to apply to the camera's X axis, resulting in a camera orbit vertically around the target child.

The focal target of the <TurntableControls> is currently locked at the world (0, 0, 0) location. You will need to compensate for that within your Platform setup, or through <group> components on the front-end.

🚧

Warning!

This control mode is not currently compatible with the Camera Switching method that uses the camera prop on the <MyScene> component.

It is only compatible with camera switching performed through a configuration attribute that serves a separate gltf entirely.

Example

Please note that in this example we are applying a rotation to the whole set of assets loaded by the component. We have wrapped it in a component and provided to it a vertical shift along the Y axis in the negative direction, of -0.48 units to arrange the focal point of the spin to the desired location.

<TurntableControls>
  <group position={new THREE.Vector3(0, -0.48, 0)} ref={groupRef}>
    <MyScene assetId={"b1571ea4-e7f3-400f-ba58-b875b382a4a5"} />
  </group>
</TurntableControls>