Nothing renders not clear Babylonjs Reactjs - reactjs

I am trying to load a model, and enable FPS view and allow the movements within this models view. The model is basically a room (https://sketchfab.com/3d-models/white-modern-living-room-afb8cb0cbee1488caf61471ef14041e9), and I want to be able to move around in it. Currently nothing happens.
import './App.css';
import {
Engine,
Scene,
Model
} from "react-babylonjs";
import { Vector3 } from "#babylonjs/core";
import "#babylonjs/loaders/glTF";
const App = () => {
return (
<>
<header>
<h2>DEMO</h2>
</header>
<main>
<Engine antialias adaptToDeviceRatio canvasId="babylonJS">
<Scene>
<Model rootUrl="assets/models/modern/" sceneFileName="scene.gltf" position={ new Vector3(1,1,1)} />
<hemisphericLight name="light1" intensity={0.7} direction={Vector3.Down()} />
<followCamera name="follow" target={ new Vector3(1,1,1) }
alpha={-Math.PI / 2} beta={(0.5 + (Math.PI / 4))}
radius={4} minZ={0.001} wheelPrecision={50}
lowerRadiusLimit={8} upperRadiusLimit={20} upperBetaLimit={Math.PI / 2} />
</Scene>
</Engine>
</main>
<footer></footer>
</>
);
}
export default App;

Related

OBJ Model not loading in react three fiber

I'm trying to add an OBJ Model (armchair.obj) but it's not being loaded. I'm using React three fiber library.
Here's my codesandbox: CodeSandbox
There's no problem with the model because I tried to load it using some other website and it is being loaded.
Anyway, I tried uploading another model (spongebob.obj) but it's not being really visible
However, in the other website, it's visible:
So, here's my codesandbox link
But, if you prefer the code here:
My App.js component:
import React, { Suspense } from "react";
import { Canvas } from "#react-three/fiber";
import { OrbitControls } from "#react-three/drei";
import LoadModel from "./components/LoadModel";
import Loader from "./components/Loader";
const App = () => {
return (
<main className="main-area">
<div id="canvas-container">
<Canvas pixelratio={[1, 2]} camera={{ position: [15, 15, 15], fov: 50, scale: [2,2,2] }}>
<ambientLight intensity={1} />
<Suspense fallback={<Loader />}>
<LoadModel url="./spongebob.obj" />
</Suspense>
<OrbitControls />
</Canvas>
</div>
</main>
);
};
export default App;
My LoadModel.js component:
import React, { useMemo, useState } from "react";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
const LoadModel = ({ url }) => {
const [obj, set] = useState();
// useMemo(() => new OBJLoader().load(url, set), [url])
useMemo(() => new OBJLoader().load(url, set), [url]);
//useMemo(() => new GLTFLoader().load(url, set), [url])
return obj ? <primitive object={obj} dispose={null} /> : null;
};
export default LoadModel;
You can improve the rendering of the SpongeBob model by adding a directional light to your scene. A single ambient light is not sufficient for proper illumination. Try adding the following line to your codesandbox:
<directionalLight />
The chair model has unfortunately some design issues. It has an extreme scale and is highly translated. I suggest you scale the model down and then center it after the loading process. However, it would be better to fix the model in a DCC tool like Blender and model the chair according to real world units.

can you portal elements into a Canvas in react-three-fibre?

Is it possible to portal some react-three-fibre elements into a canvas from outside of one?
I.e. in the following example, which was my first attempt to portal a mesh into a Canvas which errored with "target element is not a DOM element" (which makes sense, given these aren't DOM elements)
import * as React from 'react'
import { Canvas, createPortal } from '#react-three/fiber'
export const Element = ({ parent }) => {
return createPortal(
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
, parent);
};
export const MyPage = () => {
const ref = React.useRef(null);
return (
<div>
<Canvas>
<group ref={ref} />
</Canvas>
<Element parent={ref.current} />
</div>
);
};
So I found the createPortal method (which has incredibly limited documentation) from react-three-fiber, but with that it throws errors, presumably here because the createPortal method only works when already inside a Canvas context and is for portaling around inside a Canvas
import * as ReactDOM from 'react-dom'
import * as React from 'react'
import { Canvas } from '#react-three/fiber'
export const Element = ({ parent }) => {
return ReactDOM.createPortal(
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
, parent);
};
export const MyPage = () => {
const ref = React.useRef(null);
return (
<div>
<Canvas>
<group ref={ref} />
</Canvas>
<Element parent={ref.current} />
</div>
);
};
the above throws this error:
react-dom.development.js:22839 Uncaught R3F hooks can only be used within the Canvas component!
Is there a way of doing this?
My hope is to be able to portal elements from elsewhere in the project into a single document-sized Canvas element that sits at the root.
update:
I have managed to get this to work by wrapping the call to createPortal in a second <Canvas />
export const Element = ({ parent }) => {
return (
<Canvas>
{createPortal(
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="blue" />
</mesh>,
portalTo
)}
</Canvas>
);
};
</code>
however this is unworkable as three.js lmits the number of canvases in a project (and having unused actual canvases everywhere in the DOM is really gross anyway)
is there a way of giving createPortal access to that context without needing to render it inside a Canvas?

Cannot update a component while rendering a different component - Environment / React- Three-Fiber

My App.jsx
import "./App.css";
import { Canvas } from "#react-three/fiber";
import React, { Suspense } from "react";
import { Html, useProgress } from "#react-three/drei";
const Scene = React.lazy(() => import('./components/Scene.jsx'));
function Loader() {
const { progress } = useProgress();
return <Html center>{progress} % loaded</Html>
}
function App() {
return (
<div id="canvas">
<Canvas>
<Suspense fallback={<Loader />}>
<Scene />
</Suspense>
</Canvas>
</div>
);
}
export default App;
My Scene.jsx
function Scene() {
<>
<ambientLight intensity={0.2} />
<directionalLight />
<GizmoHelper
alignment="bottom-right"
margin={[80, 80]}
>
<GizmoViewport
axisColors={["red", "green", "blue"]}
labelColor="black"
/>
</GizmoHelper>
<Text
position={[-3, 0, 0]}
anchorX="center"
anchorY="middle"
fontSize="1.5"
outLineBlur="5"
outlineColor="red"
curveRadius="2.9"
font="db.onlinewebfonts.com/t/02654c5f87934978cd2129477ad40869.ttf">
Hello world
<meshStandardMaterial
color="#ec2d2d"
opacity="1"
roughness="0"
metalness="0.3"
normalMap={normalMap}
/>
</Text>
<OrbitControls />
<Environment preset="city" background="true" />
</>
);
}
My problem:
Every time I start the app the environment doesn't load and I get the following error:
Warning: Cannot update a component (`Loader`) while rendering a different component (`Environment`)
If I change the preset while the live server is running the environment updates and shows instantly.
I have already tried to write a function and render the environment only when the window is loaded but I still got the same error.
I have read somewhere I could use a setTimeout but not sure whether this the right approach.
If anyone could point me in the right direction.
Update:
I did not include the fact that I was using the GizmoHelper component.
Removing the GizmoHelper from the scene solved the problem.
I have added the GizmoHelper in above code in case someone else has the same problem.
Further Update:
I noticed the error is still logged in the console but now it does not affect the initial rendering of the environment anymore.
Might still cause an issue further down the road though.

How do I route between pages in Embedded React App?

Background:
I am trying to create some links in my embedded Shopify app.
I understand that I cannot use the simple <a> tag due to the fact that Shopify embedded apps are rendered as iframes.
I made some headway with this tutorial, but I am stuck: https://theunlikelydeveloper.com/shopify-app-bridge-react-router/
What I am trying to do:
I have 3 pages (index.js, dashboard.js, and support.js). I would like to allow the user to navigate from one page to another (with links and/or buttons).
My Code:
By following the tutorial above, I've gotten this far:
// index.js
import { Page, Frame } from "#shopify/polaris";
const Index = () => {
return (
<Page>
<Frame>
{/* LINK TO DASHBOARD PAGE*/}
{/* LINK TO SUPPORT PAGE */}
</Frame>
</Page>
);
};
export default Index;
// routes.js
import React from "react";
import { Switch, Route, withRouter } from "react-router";
import { ClientRouter, RoutePropagator } from "#shopify/app-bridge-react";
function Routes(props) {
const { history, location } = props;
return (
<>
<ClientRouter history={history} />
<RoutePropagator location={location} />
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/support">
<Support />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</>
);
}
export default withRouter(Routes);
// link.js
import React from "react";
import { Link as ReactRouterLink } from "react-router";
const IS_EXTERNAL_LINK_REGEX = /^(?:[a-z][a-z\d+.-]*:|\/\/)/;
function Link({ children, url = "", external, ref, ...rest }) {
if (external || IS_EXTERNAL_LINK_REGEX.test(url)) {
rest.target = "_blank";
rest.rel = "noopener noreferrer";
return (
<a href={url} {...rest}>
{children}
</a>
);
}
return (
<ReactRouterLink to={url} {...rest}>
{children}
</ReactRouterLink>
);
}
export default Link;
Additional:
I believe I'm supposed to implement the following code somewhere, but I don't see how it fits into the picture of navigating between pages with a link or button.
<AppProvider linkComponent={Link}>
{/* App content including your <Route> components */}
</AppProvider>
Link to Shopify Docs: https://polaris.shopify.com/components/structure/app-provider#section-using-linkcomponent
At this time of building embedded app you can make client-side navigation using app-bridge utilities as referred to in this answer
You just need to edit _app file and consider making client-side navigation from your components(can't use a normal Link)
import {useEffect} from 'react';
import Router, { useRouter } from "next/router";
import { RoutePropagator as ShopifyRoutePropagator } from "#shopify/app-bridge-react";
function RoutePropagator () {
const router = useRouter();
const { route } = router;
const app= useAppBridge();
useEffect(() => {
app.subscribe(Redirect.Action.APP, ({ path }) => {
Router.push(path);
});
}, []);
return app && route ? (
<ShopifyRoutePropagator location={route} />
) : null;
}
Then use this component in your _app file
_app.tsx
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props as any;
return (
<PolarisProvider i18n={translations}>
<ShopifyBridgeProvider
config={{
apiKey: API_KEY,
host,
forceRedirect: true,
}}
>
<RoutePropagator />
<ApolloClientProvider Component={Component} {...pageProps} />
</ShopifyBridgeProvider>
</PolarisProvider>
);
}
}
Now you've subscribed for routing events in _app file, we just require to make client-side navigation right in your pages
import {useAppBridge} from '#shopify/app-bridge-react';
import { Redirect } from '#shopify/app-bridge/actions';
function IndexPage(props) {
const app = useAppBridge();
return (
<>
<div>{'you are in main page'}</div>
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/dashboard'
}));
}}>
{'to dashboard'}
</div>
</>
);
}
And for going back to the main page / route, I've found that it trigger an oauth again if not provided with the shop name, so we will use the shop query params for that
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/?shop=<shop-name>.myshopify.com'
}));
}}>
{'to main'}
</div>

Render a new page on react-routing but it renders it inside the current page

This displays a card component that the user sees if the user clicks the <Link>read</Link> it should re-render a new page.
import React from "react";
import { Button } from "react-bootstrap";
import "./CardComponent.css";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Content from "./ContentFolder/Content";
function CardComponent(props) {
return (
<Router>
<div class="card">
<div className="uppercard">
<img
className="bookCover"
src={props.img}
alt=""
width="120px"
height="150px"
/>
<h3>{props.title}</h3>
<h6>By{props.author}</h6>
</div>
<div className="lowerCard">{props.points}</div>
<Link to={"/" + props.title + props.author}>Read</Link>
</div>
<Switch>
<Route
exact
path={`/${props.title+props.author}`}
component={Content}
>
<Content title={props.title} author={props.author}
points={props.points}
/>
</Route>
</Switch>
</Router>
);
}
export default CardComponent;
On clicking read I want to render this content component on a different page.
In summary, the goal is to display all the information on a new page when the user clicks on one of the card components.
import React from "react";
import Mynavbar from "../Partials/Mynavbar";
import MyFooter from "../Partials/Footer";
import { Container } from "react-bootstrap";
import "./Content.css";
function Content(props) {
return (
<div>
<Mynavbar />
<Container className="main">
<h4>{props.title}</h4>
<h6>By {props.author}</h6>
<ul>
{props.points.map((point, i) => {
return <li>{point}</li>;
})}
</ul>
</Container>
<MyFooter />
</div>
);
}
export default Content;
Problem: Router inside of CardComponent
The Router needs to exist at the highest level of the App. Everything that is inside of the Router and outside of the Switch will be rendered on every page. So right now your card code will show up even on the Content route. We want the Card and the Content to be separate Routes.
Problem: Ambiguous URL Structure
Do you need for your urls to look like "/${props.title+props.author}"? This is a very bad structure because you cannot possibly work backwards from the URL to the content. What is the content for "/Harry PotterJ.K. Rowling"? Which part is the title and which part is the author? There is no separator so you don't know. You would have to loop through a list of all books, joining their title and author and comparing it to your string.
A typical URL would be based on an id, like "/book/5". I don't see any mention of an id here so we can use the title.
Solution
An app routing might look like this:
function App() {
return (
<Router>
<Switch>
<Route path="/book/:title" component={BookDetails}/>
<Route path="/" component={BookList}/>
</Switch>
</Router>
)
}
Let's get rid of all the routing in CardComponent and make it just show a card for a book with a link to the book details.
function CardComponent(props: Book) {
return (
<div className="card">
<div className="uppercard">
<img
className="bookCover"
src={props.img}
alt=""
width="120px"
height="150px"
/>
<h3>{props.title}</h3>
<h6>By{props.author}</h6>
</div>
<div className="lowerCard">{props.points}</div>
<Link to={"/book/" + props.title}>Read</Link>
</div>
);
}
Our home page might show a list of these cards.
function BookList() {
// get books from somewhere -- a database? a json file?
const books = ???;
return (
<ul className="bookList">
{books.map((book) => (
<CardComponent {...book} key={book.title} />
))}
</ul>
);
}
BookDetails is a separate route, so we need to get the book from the URL.
function BookDetails(props: RouteComponentProps) {
// get the title from the URL
// is automatically encoded and needs to be decoded
const title = decodeURIComponent(props.match.params.title);
// find the book object from your data source
const book = ???
// from a JSON array: BOOKS.find(book => book.title.toLowerCase() === title.toLowerCase() );
// redirect to error page if no matching book
if ( ! book ) {
return <Redirect to="/404" />
}
// can render your Content component, but only after we get the book
return (
<Content {...book} />
)
}

Resources