Reputation: 307
Google's <model-viewer> provides all the key features I need without having write a custom solution via something like react-three-fiber
or directly in three.js.
I am struggling with how to properly integrate it into a Reagent (and React for that matter) structure.
In order to make it easy to use with vanilla JS, is built as a web component and is largely controlled via the attributes on its html element. Normally that wouldn't be much of a problem, but with the overhead of 3D and loading large models re-rendering this is expensive and in many cases functionality-breaking.
I've tried to naively use it inside a component and trying to eliminate the possibility of re-rendering. Using a ref to mutate it directly.
I have also tried setting it up as a manually created html element from the Reagent/React controlled application and reference it in various events via its dom element.
Both of these options introduced a lot of hacks and were not ideal in a single page application.
I am wondering if anyone has any tips on how to best wrap this in a React/Reagent shell, while still having access to the core element to use their underlying JS api.
Answers don't have to be in ClojureScript.
Here is the example of its usage from their page:
<model-viewer
alt="Neil Armstrong's Spacesuit from the Smithsonian Digitization Programs Office and National Air and Space Museum"
src="shared-assets/models/NeilArmstrong.glb"
ar ar-modes="webxr scene-viewer quick-look" environment-image="shared-assets/environments/moon_1k.hdr"
poster="shared-assets/models/NeilArmstrong.webp"
seamless-poster
shadow-intensity="1"
camera-controls>
</model-viewer>
Thanks for any tips.
Additional discussion on Clojurians Slack (link requires access to Slack)
Additional discussion on Clojureverse
Upvotes: 6
Views: 9186
Reputation: 7
To use the <model-viewer>
element in React, you can use the react-model-viewer
library, which provides a React component wrapper around the <model-viewer>
element.
First, you need to install react-model-viewer
by running the following command in your project directory
npm install react-model-viewer
Then, in your React component file, you can import the ModelViewer
component from react-model-viewer
:
import React from 'react';
import { ModelViewer } from 'react-model-viewer';
const MyComponent = () => {
return (
<ModelViewer
alt="Neil Armstrong's Spacesuit from the Smithsonian Digitization Programs Office and National Air and Space Museum"
src="shared-assets/models/NeilArmstrong.glb"
environmentImage="shared-assets/environments/moon_1k.hdr"
poster="shared-assets/models/NeilArmstrong.webp"
shadowIntensity={1}
cameraControls
touchAction="pan-y"
/>
);
}
export default MyComponent;
Note that the ModelViewer
component props are written in camelCase instead of kebab-case, so environment-image
becomes environmentImage
in the example you provided.
Upvotes: -1
Reputation: 1943
module
type script.<!DOCTYPE html> <html lang="en"> <head> .... <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js" ></script> ... </head> ... </html>
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const modelRef = React.useRef();
return (
<model-viewer
// className="model-viewer"
src="./M08.glb"
alt="A rock"
exposure="0.008"
camera-controls
ar
ar-modes="webxr"
ref={(ref) => {
modelRef.current = ref;
}}
>
</model-viewer>
);
}
here is an interaction, where an onclick event translates screen coordinates to model coordinates using a function available from the model. It stores these annotations and renders them as children of the model. This is available in the documentation
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const modelRef = React.useRef();
const [annots, setAnnots] = useState([]);
const handleClick = (event) => {
const { clientX, clientY } = event;
if (modelRef.current) {
let hit = modelRef.current.positionAndNormalFromPoint(clientX, clientY);
if (hit) {
setAnnots((annots) => {
return [...annots, hit];
});
}
}
};
const getDataPosition = (annot) => {
return `${annot.position.x} ${annot.position.y} ${annot.position.z}`;
};
const getDataNormal = (annot) => {
return `${annot.normal.x} ${annot.normal.y} ${annot.normal.z}`;
};
return (
<model-viewer
// className="model-viewer"
src="./M08.glb"
alt="A rock"
exposure="0.008"
camera-controls
ar
ar-modes="webxr"
onClick={handleClick}
ref={(ref) => {
modelRef.current = ref;
}}
>
{annots.map((annot, idx) => (
<button
key={`hotspot-${idx}`}
className="view-button"
slot={`hotspot-${idx}`}
data-position={getDataPosition(annot)}
data-normal={getDataNormal(annot)}
></button>
))}
</model-viewer>
);
}
Code Sandbox: https://codesandbox.io/s/lingering-tree-d41cr?file=/src/App.js:0-1287
Upvotes: 11