Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Panning / Orbit Controls #27

Closed
ryanking1809 opened this issue Mar 13, 2019 · 20 comments
Closed

Panning / Orbit Controls #27

ryanking1809 opened this issue Mar 13, 2019 · 20 comments

Comments

@ryanking1809
Copy link
Contributor

I'm trying to implement a panning system on scroll events, and I have managed to get something working but it appears I've done it in the slowest way possible.

I've noticed you're using orbitalControls here (https://codesandbox.io/embed/mo0xrqrj79) and I'm trying to jam it into my system with little success. Are you able to explain a little how that all works?

It looks like the orbital controls tell the renderer how to position the camera, and you need to use useRender to tell the objects to rerender, and. Is that correct?

Here's my very slow attempt a panning (sorry, I couldn't get it rendering correctly on CodeSandbox)

import React, { useMemo, useState } from 'react';
import * as THREE from 'three/src/Three'
import { Canvas } from 'react-three-fiber'
import _ from 'lodash'

function Extrusion({ x, y, width, height, radius, ...props }) {
  const roundedRect = useMemo(() => {
    const roundedRect = new THREE.Shape()
    roundedRect.moveTo(x, y + radius);
    roundedRect.lineTo(x, y + height - radius);
    roundedRect.quadraticCurveTo(x, y + height, x + radius, y + height);
    roundedRect.lineTo(x + width - radius, y + height);
    roundedRect.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
    roundedRect.lineTo(x + width, y + radius);
    roundedRect.quadraticCurveTo(x + width, y, x + width - radius, y);
    roundedRect.lineTo(x + radius, y);
    roundedRect.quadraticCurveTo(x, y, x, y + radius);
    return roundedRect
  }, [x, y, width, height, radius])

  return (
    <mesh>
      <extrudeGeometry name="geometry" args={[roundedRect, props]} />
      <meshNormalMaterial name="material" />
    </mesh>
  )
}

function Bar({ start, end, yIndex }) {
  return <Extrusion
    x={start}
    y={yIndex}
    height={10}
    width={end - start}
    radius={3}
    bevelEnabled={false}
    depth={10}
  />
}

const makeRandomBar = () => {
  const start = _.random(-1000, 1000)
  const length = _.random(50, 500)
  const yIndex = _.random(-1000, 1000)
  return [start, start + length, yIndex]
}

const bars = _.range(0, 250).map(n => makeRandomBar())

function App() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  const scrollFunc = e => {
    setX(x + e.deltaX)
    setY(y + e.deltaY)
  }
  return (
    <div style={{ width: "100%", height: "100%", position: "absolute" }}>
      <Canvas camera={{ position: [0, 0, 300] }} onWheel={scrollFunc}>
        <group position={[x, y, 0]}>
          {bars.map(b => <Bar start={b[0]} end={b[1]} yIndex={b[2]} />)}
        </group>
      </Canvas>
    </div>
  );
}

export default App;
@drcmda
Copy link
Member

drcmda commented Mar 13, 2019

I would recommend three js controls for sure. There exist a couple, orbit is just one of them. It sucks that three isn't fully modular still, so i just copy stuff from this PR: mrdoob/three.js#15832

Controls need a reference to the camera, you can either create your own camera or use the default camera and fetch it via useThree, and it needs to be updated every frame, for this you should useRender. So basically:

import { apply, Canvas, useRender, useThree } from 'react-three-fiber'
import { OrbitControls } from '../resources/controls/OrbitControls'

// Make OrbitControls known as <orbitControls />
apply({ OrbitControls })

function Controls(props) {
  const ref = useRef()
  const { camera } = useThree()
  useRender(() => ref.current.obj.update())
  return <orbitControls ref={ref} args={[camera]} {...props} />
}

function App() {
  return (
    <div style={{ width: '100%', height: '100%', position: 'absolute' }}>
      <Canvas camera={{ position: [0, 0, 300] }}>
        <Controls />
        <group position={[x, y, 0]}>
          {bars.map(b => (
            <Bar start={b[0]} end={b[1]} yIndex={b[2]} />
          ))}
        </group>
      </Canvas>
    </div>
  )
}

Orbit has lots of options: https://threejs.org/docs/index.html#examples/controls/OrbitControls you can switch off rotation and zoom so that only pan is allowed.

@ryanking1809
Copy link
Contributor Author

ryanking1809 commented Mar 14, 2019

Awesome, thanks! I've managed to get a few different control systems working.
However, everything is disappearing on my during pan and orbit no matter which system I use. I've logged the camera during the useRender function and it appears to be getting a few NaN values.

It doesn't appear to be doing it with your example in CodeSandbox. I have the exact same OrbitControl code so maybe it has something to do with the default camera? I'll play around with setting up a custom camera.

function Controls(props) {
  const ref = useRef()
  const { camera } = useThree()
  useRender(() => {
    console.log('Controls -> useRender -> camera', camera)
    ref.current.obj.update()
  })
  return <orbitControls ref={ref} args={[camera]} {...props} />
}

function App() {
  return (
    <div style={{ width: "100%", height: "100%", position: "absolute" }}>
      <Canvas camera={{ position: [0, 0, 300] }}>
        <Controls />
        <group>
            {bars.map((b,i) => <Bar key={i} start={b[0]} end={b[1]} yIndex={b[2]} />)}
        </group>
      </Canvas>
    </div>
  );
}

@ryanking1809
Copy link
Contributor Author

ryanking1809 commented Mar 14, 2019

Nope didn't seem to make a difference. Any ideas?

function CanvasContent() {
  const camera = useRef()
  const controls = useRef()
  const { size, setDefaultCamera } = useThree()
  useEffect(() => void setDefaultCamera(camera.current), [])
  useRender(() => {
    console.log('cam', camera.current)
    controls.current.obj.update()
  })
  return (
    <group>
      <perspectiveCamera
        ref={camera}
        aspect={size.width / size.height}
        radius={(size.width + size.height) / 4}
        fov={55}
        position={[0, 0, 300]}
        onUpdate={self => self.updateProjectionMatrix()}
      />
      {camera.current && (
        <group>
          <orbitControls ref={controls} args={[camera.current]} enableDamping dampingFactor={0.1} rotateSpeed={0.1} />
          <group>
            {bars.map((b, i) => <Bar key={i} start={b[0]} end={b[1]} yIndex={b[2]} />)}
          </group>
        </group>
      )}
    </group>
  )
}

function App() {
  return (
    <div style={{ width: "100%", height: "100%", position: "absolute" }}>
      <Canvas>
        <CanvasContent />
      </Canvas>
    </div>
  );
}

@ryanking1809
Copy link
Contributor Author

Camera log
Screen Shot 2019-03-14 at 11 54 59 am

@drcmda
Copy link
Member

drcmda commented Mar 14, 2019

I haven't tried the code i posted, but doing it now ... and seems fine: https://codesandbox.io/s/387z7o2zrq it doesn't seem to pan up/down, but i think that is related to how these controls work, probably they need a specific up vector or something like that, it's purely threejs at that point.

As for NaN, controls write into the camera, if they are set up wrong or are missing any critical data, they can screw up the camera. Must be a reason somewhere why it's doing that. If you put that into a sandbox, i can investigate - would also be interesting to me if it's three-fibers fault.

@ryanking1809
Copy link
Contributor Author

Code sandbox is failing to load dependencies for me atm but I'm sure this is working https://codesandbox.io/s/14r8y3k144

I haven't been able to test the code above in sandbox, but strangely I'm having the same problems locally. The only difference I can possibly think of is the wrapper div <div style={{ width: "100%", height: "100%", position: "absolute" }}>.

@ryanking1809
Copy link
Contributor Author

ryanking1809 commented Mar 14, 2019

Ok, the div is definitely this issue! But I need it to stop the canvas from expanding in size indefinitely. Trying to figure out another approach. Specifically, it's the position: absolute style, no problems with fixed dimensions.

@drcmda
Copy link
Member

drcmda commented Mar 14, 2019

your box worked for me, i upgraded three-fiber to latest and changed three/src/Three to just 'three'

https://codesandbox.io/s/o5mj1l8y0z

But i can pan the contents.

Ok, the div is definitely this issue! But I need it to stop the canvas from expanding in size indefinitely. Trying to figure out another approach.

The expanding happens when the container doesn't have a size. needs to be relative/absolute to something and have a width/height, percentages, fixed values, everything goes. Otherwise the resize observer will start to push the bounds.

@ryanking1809
Copy link
Contributor Author

Ok, got it working thanks! Interestingly you need to add the following css otherwise the orbit / pan breaks if any parent container has position: absolute.

html, body {
  height: 100%;
  width: 100%;
} 

@drcmda
Copy link
Member

drcmda commented Mar 14, 2019

I don't know yet - you think it's a good idea to have a resize observer inside the canvas? I can understand it might look like magic, and usually that's not a good thing. On the other hand, three canvas isn't responsive, if you resize the browser the view stays put and you would have to await resize events (which isn't enough if it's inside a dynamically sized div) and update the context renderer manually.

@ryanking1809
Copy link
Contributor Author

Yeah, I had a bit of trouble with it at first - but in saying that I would love things to resize automatically, and would end up trying to implement it myself anyway.

I've been trying to think if there's a way to stop the auto-expanding from inside react-three-fibre but it seems like maybe you need to know what's happening all the way up the dom tree.

Perhaps you could add an autoResize flag to the Canvas, and have it off by default. That will stop people from running into trouble when they first use the library, but they have the option to turn it on when they start diving deeper.

@drcmda
Copy link
Member

drcmda commented Mar 14, 2019

that would be an option. maybe also cutting out the resize-observer-polyfill and letting users decide to include that on their own. it's just additional weight if it's running in evergreen browsers.

@ryanking1809
Copy link
Contributor Author

ryanking1809 commented Mar 14, 2019

Yeah, that could work. Say the polyfill was included, another approach would be to just throw an error if the container is dimensionless. It's not that it's a hard issue to fix, but because you don't know that react-three-fiber auto-resizes it feels like it's doing something weird on its own, that you never told it to do.

If you just say error: Canvas inside dimensionless container. Please ensure Canvas container has dimensions. then I would be able to fix things in 2 seconds. But when it grows on its own, it becomes a bit of a puzzle as to why it's doing that.

@talentlessguy
Copy link
Member

Please help! <orbitControls /> don't work here. OrbitControls.js throws TypeError: this.object is undefined

Here's the code I wrote:

import {
	DodecahedronBufferGeometry, EdgesGeometry,
	LineSegments, LineBasicMaterial
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import React, { useRef } from 'react'
import { Canvas, extend, useThree, useRender } from 'react-three-fiber'

const geometry = new DodecahedronBufferGeometry(1, 1)
const edges = new EdgesGeometry(geometry)
const lines = new LineSegments(edges, new LineBasicMaterial({ color: 'red' }))

extend({ OrbitControls })

const Sphere = () => {
	// Setup OrbitControls
	const { camera } = useThree()
	const ref = useRef()
	useRender(() => ref.current.obj.update())

	return (
		<Canvas>
			<orbitControls args={[camera]} ref={ref} />
		  	<primitive object={lines} />
		</Canvas>
	  )
}

export default Sphere

What should I do?

@drcmda
Copy link
Member

drcmda commented May 25, 2019

useThree is context based, it doesn't work outside of canvas, plus, you can have multiple canvases. best wrap it into a component, here's an easy example: https://codesandbox.io/s/xvvn4vxqnz

@talentlessguy
Copy link
Member

Thank you @drcmda for help! Now it works. I didn't know that useThree is content-based cos' I only started using the library.

The code above works pretty fine:

import {
	DodecahedronBufferGeometry, EdgesGeometry,
	LineSegments, LineBasicMaterial
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import React, { useRef } from 'react'
import { Canvas, extend, useThree, useRender } from 'react-three-fiber'

const geometry = new DodecahedronBufferGeometry(1, 1)
const edges = new EdgesGeometry(geometry)
const lines = new LineSegments(edges, new LineBasicMaterial({ color: 'red' }))

extend({ OrbitControls })

const Model = () => <primitive object={lines} />

const Controls = props => {
  const { camera } = useThree()
  const controls = useRef()
  useRender(() => controls.current && controls.current.update())
  return <orbitControls ref={controls} args={[camera]} {...props} />
}

const Sphere = () => {
	return (
		<Canvas>
			<Controls enableDamping rotateSpeed={0.3} dampingFactor={0.1} />
			<Model />
		</Canvas>
  )
}

export default Sphere

@gino8080
Copy link

gino8080 commented Feb 8, 2020

seems that useRender is not anymore available, how to use orbitcontrols in latest v4 version?

@paulmelnikow
Copy link
Contributor

You need useFrame().

gsimone pushed a commit that referenced this issue Sep 17, 2020
Forward transforms to wrapped group
@Alex-DG
Copy link

Alex-DG commented Sep 21, 2020

@gino8080 you can also import OrbitalControl from drei

@anuragsr
Copy link

anuragsr commented Oct 7, 2020

I needed to pass the domElement in args too, else it was undefined and throwing an error:

const {
  camera,
  gl: { domElement },
} = useThree();

This article helped https://codeworkshop.dev/blog/2020-04-03-adding-orbit-controls-to-react-three-fiber/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants