# Problem implementing orbit camera and turntable in React.js

**URL:** <https://forum.techsoft3d.com/t/problem-implementing-orbit-camera-and-turntable-in-react-js/3644>\
**Category:** HOOPS Visualize Web\
**Created:** [May 7, 2024, 10:45am UTC](https://forum.techsoft3d.com/t/problem-implementing-orbit-camera-and-turntable-in-react-js/3644 "2024-05-07T10:45:42Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![dayana](https://avatars.discourse-cdn.com/v4/letter/d/ea666f/32.png) [@dayana](https://forum.techsoft3d.com/u/dayana)\
**Post date:** [May 7, 2024, 10:45am UTC](https://forum.techsoft3d.com/t/problem-implementing-orbit-camera-and-turntable-in-react-js/3644/1 "2024-05-07T10:45:43Z")

</div>

Hi,  
Оne of the next problems I encountered is implementing orbit camera, and turntable. I was trying to follow the following pages:

[[OperatorId — HOOPS Communicator 2024.2.0 Documentation](https://docs.techsoft3d.com/communicator/latest/api_ref/viewing/enum/Communicator.OperatorId.html)]

[[OperatorManager — HOOPS Communicator 2024.2.0 Documentation](https://docs.techsoft3d.com/communicator/latest/api_ref/viewing/classes/Communicator.OperatorManager.html#Communicator.OperatorManager.size)]

Here is an example implementation(reffer to handleOperationChange):

```auto
import React, { useState, useEffect } from "react";
import ModelTree from "./ModelTree";
import LayerTree from "./LayerTree";
import HomeIcon from "~/icons/HomeIcon";
import ExplodeCubeIcon from "~/icons/ExplodeCubeIcon";
import Select from 'react-select';
import Tooltip from "../basics/Tooltip";

const operatorOptions = [
  { value: 'Orbit', label: 'Orbit' },
  { value: 'Turntable', label: 'Turntable' }
];

const cameraPositions = {
  Front: { position: { x: 10, y: 0, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Back: { position: { x: -10, y: 0, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Right: { position: { x: 0, y: 10, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Left: { position: { x: 0, y: -10, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Top: { position: { x: 0, y: 0, z: 10 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 1, z: 1 } },
  Bottom: { position: { x: 0, y: 0, z: -10 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: -1, z: 1 } }
};

const cameraOptions = [
  { value: 'Front', label: 'Front View' },
  { value: 'Back', label: 'Back View' },
  { value: 'Right', label: 'Right View' },
  { value: 'Left', label: 'Left View' },
  { value: 'Top', label: 'Top View' },
  { value: 'Bottom', label: 'Bottom View' }
];

const cuttingPlaneOptions = [
  { value: 'X', label: 'X-axis' },
  { value: 'Y', label: 'Y-axis' },
  { value: 'Z', label: 'Z-axis' },
  { value: 'FaceNormal', label: 'Face Normal' }
];

const drawModeOptions = [
  {
    value: 'Shaded', label: "Shaded"
  },
  {
    value: 'HiddenLine', label: "Hidden Line"
  },
  {
    value: 'Wireframe', label: "Wireframe"
  },
  {
    value: 'XRay', label: "X Ray"
  }
];

const buttonStyle = {
  backgroundColor: 'rgba(255, 255, 255, 0.50)',
  border: '1px solid rgba(156, 163, 175, 0.5)',
  borderRadius: '0.375rem',
  cursor: 'pointer',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
  width: '40px',
  height: '40px'
};

const HoopsViewer = () => {
  const [hwv, setHwv] = useState(null);
  const [explodeMagnitude, setExplodeMagnitude] = useState(0);
  const [showExplode, setShowExplode] = useState(false);
  const [drawMode, setDrawMode] = useState({ value: 'Shaded', label: "Shaded" });
  const [cameraView, setCameraView] = useState('Back');
  const [cuttingPlanes, setCuttingPlanes] = useState([]);

  useEffect(() => {
    const script = document.createElement("script");
    script.src = "/js/hoops/hoops_web_viewer.js";
    script.onload = () => initViewer();
    document.body.appendChild(script);
    return () => {
      document.body.removeChild(script);
      hwv?.shutdown();
    };
  }, []);

  const initViewer = () => {
    const hwvInstance = new window.Communicator.WebViewer({
      containerId: "viewer",
      endpointUri: "/data/microengine.scs",
    });

    try {
      hwvInstance.start();
      setHwv(hwvInstance);
      initNavCube(hwvInstance);
    } catch (error) {
      console.error("Failed to start HWV", error);
    }
  };

  const initNavCube = (hwvInstance) => {
    const navCube = hwvInstance.view.getNavCube();
    navCube.setAnchor(window.Communicator.OverlayAnchor.TopRight);
    navCube.enable();
  };

  const addCuttingPlane = async (type) => {
    if (!hwv || cuttingPlanes.length >= 6) return;

    try {
      const bounding = await hwv.model.getModelBounding(true, true);
      let axis, normal, position;

      switch (type) {
        case 'X':
          axis = Communicator.Axis.X;
          normal = new Communicator.Point3(1, 0, 0);
          position = bounding.min;
          break;
        case 'Y':
          axis = Communicator.Axis.Y;
          normal = new Communicator.Point3(0, 1, 0);
          position = bounding.min;
          break;
        case 'Z':
          axis = Communicator.Axis.Z;
          normal = new Communicator.Point3(0, 0, 1);
          position = bounding.min;
          break;
        case 'FaceNormal':
          normal = new Communicator.Point3(0, 0, 1);
          position = bounding.center();
          break;
        default:
          console.error("Invalid plane type selected");
          return;
      }

      const referenceGeometry = hwv.cuttingManager.createReferenceGeometryFromAxis(Communicator.Axis.X, bounding);
      const plane = Communicator.Plane.createFromPointAndNormal(position, normal);
      const cuttingSection = hwv.cuttingManager.getCuttingSection(0);
      await cuttingSection.addPlane(plane, referenceGeometry);
      await cuttingSection.activate();
      setCuttingPlanes([...cuttingPlanes, { type, plane }]);
    } catch (error) {
      console.error("Error adding cutting plane:", error);
    }
  };

  const handleCuttingPlaneChange = (selectedOption) => {
    addCuttingPlane(selectedOption.value);
  };

  const resetCamera = () => {
    hwv.view.resetCamera(750);
  };

  const handleChangeDrawMode = (selectedOption) => {
    setDrawMode(selectedOption);
    hwv.view.setDrawMode(Communicator.DrawMode[selectedOption.value]);
  };

  const handleChangeCameraView = (selectedOption) => {
    setCameraView(selectedOption.value);
    const config = cameraPositions[selectedOption.value];
    const newCamera = new Communicator.Camera();
    newCamera.setPosition(new Communicator.Point3(config.position.x, config.position.y, config.position.z));
    newCamera.setTarget(new Communicator.Point3(config.target.x, config.target.y, config.target.z));
    newCamera.setUp(new Communicator.Point3(config.up.x, config.up.y, config.up.z));

    const aspectRatio = hwv.view.getCanvasSize().x / hwv.view.getCanvasSize().y;
    const baseSize = 200;
    newCamera.setWidth(baseSize * aspectRatio);
    newCamera.setHeight(baseSize);

    hwv.view.setCamera(newCamera, 750);
  };

  const handleChangeExplodeMagnitude = (event) => {
    const newMagnitude = Number(event.target.value);
    setExplodeMagnitude(newMagnitude);
    if (hwv) {
      hwv.getExplodeManager().setMagnitude(newMagnitude / 100);
    }
  };

  const createSnapshotConfig = () => {
    const config = new Communicator.SnapshotConfig();
    config.width = 1920;
    config.height = 1080;
    config.layers = Communicator.SnapshotLayer.All;

    return config;
  };

  const takeSnapshot = () => {
    if (!hwv) {
      console.error("HWV instance not initialized");
      return;
    }

    const snapshotConfig = createSnapshotConfig();

    hwv.takeSnapshot(snapshotConfig).then((result) => {
      if (result instanceof Blob) {
        const url = URL.createObjectURL(result);
        const link = document.createElement('a');
        link.href = url;
        link.download = "snapshot.png";
        link.click();
        URL.revokeObjectURL(url);
      } else if (result instanceof HTMLImageElement) {
        const src = result.src;
        const link = document.createElement('a');
        link.href = src;
        link.download = "snapshot.png";
        link.click();
      } else {
        console.error("Snapshot did not return a valid result:", result);
      }
    }).catch((error) => {
      console.error("Error taking snapshot:", error);
    });
  };

  const handleOperationChange = (selectedOption) => {
    if (!hwv) {
      console.error("HWV instance not initialized");
      return;
    }

    const operatorManager = hwv.operatorManager;
    operatorManager.pop();

    let operatorId;
    switch (selectedOption.value) {
      case 'Orbit':
        operatorId = Communicator.OperatorId.Orbit;
        break;
      case 'Turntable':
        operatorId = Communicator.OperatorId.Turntable;
        break;
      default:
        console.error("Unsupported operation:", selectedOption.value);
        return;
    }

    if (operatorId !== undefined) {
      operatorManager.push(operatorId);
    } else {
      console.error("Invalid operator ID for", selectedOption.value);
    }
  };

  return (
    <div className="flex h-full w-full relative h-screen">
      <div id="viewer" className="absolute top-0 left-0 w-full h-full" />
      <div className="absolute bottom-10 left-1/2 transform -translate-x-1/2 flex items-center">
        <Tooltip text={"Reset camera"} position="left">
          <div style={{ ...buttonStyle, marginRight: '10px' }} onClick={resetCamera}>
            <HomeIcon className="w-4 h-4 text-black" />
          </div>
        </Tooltip>

        <Tooltip text="Take Snapshot" position="left">
          <div style={{ ...buttonStyle }} onClick={takeSnapshot}>
            <span>📸</span>
          </div>
        </Tooltip>

        <Tooltip text="Camera Operation" position="left">
          <Select
            placeholder="Ca.."
            onChange={handleOperationChange}
            options={operatorOptions}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                minWidth: 80,
                marginLeft: "10px",
                cursor: "pointer",
                marginRight: "10px",
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Camera menu" position="left">
          <Select
            value={cameraOptions.find(option => option.value === cameraView)}
            onChange={handleChangeCameraView}
            options={cameraOptions}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                marginRight: "10px",
                cursor: "pointer",
                marginLeft: "10px",
                minWidth: 80,
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Draw mode" position="left">
          <Select
            value={drawMode}
            onChange={handleChangeDrawMode}
            options={drawModeOptions}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                minWidth: 80,
                cursor: "pointer",
                marginRight: "10px",
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Explode Menu" position="left">
          <div style={{ ...buttonStyle, position: 'relative', marginRight: "10px" }} onClick={() => setShowExplode(!showExplode)}>
            <ExplodeCubeIcon className="w-4 h-4" />
            {showExplode && (
              <input
                type="range"
                min="0"
                max="100"
                value={explodeMagnitude}
                onChange={handleChangeExplodeMagnitude}
                className="slider-vertical"
              />
            )}
          </div>
        </Tooltip>

        <Tooltip text="Cutting Plane Menu" position="left">
          <Select
            placeholder="Cu.."
            onChange={handleCuttingPlaneChange}
            options={cuttingPlaneOptions}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                minWidth: 80,
                cursor: "pointer",
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>
      </div>
      {hwv && <ModelTree hwv={hwv} />}
      {hwv && <LayerTree hwv={hwv} />}
    </div>
  );
};

export default HoopsViewer;

```

When I selectt ‘Orbit’ for example the camera is breaking, and when I select ‘Turntable’ camera mode is not changing at all. Any advice will be very helpfull for me. Thank you in advance !

---

<div class="post-metadata">

**Author:** ![tino](https://avatars.discourse-cdn.com/v4/letter/t/9dc877/32.png) [@tino](https://forum.techsoft3d.com/u/tino)\
**Post date:** [May 7, 2024, 1:38pm UTC](https://forum.techsoft3d.com/t/problem-implementing-orbit-camera-and-turntable-in-react-js/3644/2 "2024-05-07T13:38:05Z")

</div>

Hello @dayana,

By default, the operator stack will have its own set of operators already in the stack. Of note among these operators is [CameraNavigationOperator](https://docs.techsoft3d.com/communicator/latest/api_ref/viewing/classes/Communicator.Operator.CameraNavigationOperator.html) – which is at the bottom of the stack. As mentioned in our [docs](https://docs.techsoft3d.com/communicator/latest/prog_guide/viewing/operators/operators.html#activating-a-predefined-operator), it is not recommended to push an operator to the top of stack as this could interfere with “similar” operators already in the stack. In this case, `Navigate` and `Orbit` are both camera-related operators.

Rather than calling [pop](https://docs.techsoft3d.com/communicator/latest/api_ref/viewing/classes/Communicator.OperatorManager.html#Communicator.OperatorManager.pop), you can try calling [set](https://docs.techsoft3d.com/communicator/latest/api_ref/viewing/classes/Communicator.OperatorManager.html#Communicator.OperatorManager.set) to replace `Navigate` with `Orbit`:

`operatorManager.set(operatorId, 0);`

This should also work with `Turntable`.

Thanks,  
Tino

---

<div class="post-metadata">

**Author:** ![dayana](https://avatars.discourse-cdn.com/v4/letter/d/ea666f/32.png) [@dayana](https://forum.techsoft3d.com/u/dayana)\
**Post date:** [May 8, 2024, 8:35am UTC](https://forum.techsoft3d.com/t/problem-implementing-orbit-camera-and-turntable-in-react-js/3644/3 "2024-05-08T08:35:00Z")

</div>

Hello, @tino

Thank you for providing me this solution! It works now as expected. Thank you !

Best regards,  
Dayana
