Open sidealice opened 4 years ago
The web worker is not native to Cannon, and the Cannon lib can be used without the worker (cannon-es
).
However the worker is a core component of this use-cannon
hooks abstraction and this lib cannot be used without the worker - too much logic is dependent upon it.
It's possible to create your own useCannon
hook which does not utilize the web worker, but that's outside the scope of this lib. @drcmda made a solid version of a basic useCannon
hook in some of his react-three-fiber
examples:
import React, { createContext, useEffect, useRef, useContext, useState } from 'react'
import { useFrame } from 'react-three-fiber'
import * as CANNON from 'cannon'
const WorldContext = createContext({})
export const PhysicsProvider = ({ children }) => {
const [ world ] = useState(() => new CANNON.World())
useEffect(() => {
world.broadphase = new CANNON.NaiveBroadphase()
world.solver.iterations = 10
world.gravity.set(0, 0, -10)
}, [ world ])
// Run world stepper every frame
useFrame(() => world.step(1 / 60))
return (
<WorldContext.Provider value={world}>
{children}
</WorldContext.Provider>
)
}
export const useCannon = ({ ...props }, fn, deps = []) => {
const ref = useRef()
const world = useContext(WorldContext)
const [ body ] = useState(() => new CANNON.Body(props))
useEffect(() => {
// Call function so the user can add shapes, positions, etc. to the body
fn(body)
world.addBody(body)
return () => world.remove(body)
}, deps)
useFrame(() => {
if (ref.current) {
// Transport cannon physics into the referenced threejs object
const { position, quaternion } = body
const { x: px, y: py, z: pz } = position
const { x: qx, y: qy, z: qz, w: qw } = quaternion
ref.current.position.copy(new Vector3(px, py, pz))
ref.current.quaternion.copy(new Quaternion(qx, qy, qz, qw))
}
})
return ref
}
then to consume:
import React from 'react'
import { Canvas} from 'react-three-fiber'
import { useCannon } from './index'
const Box = ({ position, width, height, depth }) => {
const ref = useCannon({ mass: 10 }, body => {
body.addShape(
new CANNON.Box(
new CANNON.Vec3(width * 0.5, height * 0.5, depth * 0.5)
)
)
body.position.set(...position)
})
return (
<mesh ref={ref}>
<boxGeometry attach="geometry" args={[ width, height, depth ]} />
<meshStandardMaterial attach="material" />
</mesh>
)
}
const App = () => (
<Canvas>
<PhysicsProvider>
<Box position={[ 0, 0, 5 ]} width={2} height={2} depth={2} />
</PhysicsProvider>
</Canvas>
)
Note that you will want a second physics object in the scene (such as a floor plane) for your box to collide with. You can combine this with a wireframe debugger (found here) like so:
import { PhysicsBodyWireframes } from './debug'
const App = () => (
<Canvas>
<PhysicsProvider>
<PhysicsBodyWireframes />
<Box position={[ 0, 0, 5 ]} width={2} height={2} depth={2} />
</PhysicsProvider>
</Canvas>
)
I would be curious to know what environment you can't use web workers in? They are supported pretty well even in older browsers, although I'm not sure if the API might be different in old versions of IE
If you emulate the API of a web worker with a class on the main thread I guess it might technically be possible to run use-cannon
without a web worker but at that point the abstraction is not ideal for performance
enabled
could also be a flag on the Physics provider, i think i would also use this in my projects when objects are outside the frustrum.
@drcmda do you just mean a way to pause the World simulation, or are we talking about a second Provider whose context isnt a web worker?
Thank you for the detailed explaination. The environment which I used is a 3rd party open platform. They forbidden to use service-worker api for the reason of security.(I think they may crorrect to ban the api, because many open platforms banned this api.)
i think pausing would be a nice feature. similar to enabled/disabled on threejs controls. that would also solve @sidealice usecase since it's requesting the webworker runtime.
I'm missing how pausing the simulation would help with the worker runtime, but I know we can implement pausing - it should be as easy as skipping the world.step
call when we want to pause 😺
if it's not enabled, maybe it wouldn't have to create the worker. then again, there would be lots of awkward rewiring, looking over the code it's probably not worth it because everything expects the worker to be in place - if we change that it would affect everything. better yet to not add Physics to the scene graph at all, and that can easily be made conditional.
I played around with a substitute for the worker API on the main thread the other day (specifically to address this issue). What would be the equivalent of worker.terminate()
? I'm not sure of the most elegant way to release the resource references
I would be curious to know what environment you can't use web workers in? They are supported pretty well even in older browsers, although I'm not sure if the API might be different in old versions of IE
Hi from React Native land... I've been using the useCannon hook from the examples folder but having access to the full api would be awesome.
@jwrubel You technically have access to more of the Cannon API with that example hook than you do within use-cannon
. Because use-cannon
wraps the physics simulation in a web worker, access to the Cannon API has to be manually abstracted. Since the example hook does not implement a web worker, you have access to the entire Cannon simulation at runtime between your physics Provider and Body hook. However, use-cannon
comes with other benefits, such as not blocking the main thread with the physics simulation, and a more declarative API.
Making use-cannon
work in environments that don't support web workers would remove the primary benefit of use-cannon
in my opinion. You could probably build a much more ergonomic API and better DX if you were to reconsider what a declarative API would look like for Cannon outside of a web worker.
It's possible I'm getting lost in the code a bit. The hook only returns ref
whereas the use-cannon
package returns an api that the docs show being referenced in useFrame
, etc. When I use the hook like:
const ref = useCannon({
mass: 1000
}, body => {
let payload = new CANNON.Sphere(1);
body.addShape(payload);
body.position.set(...position)
...
}
I can't access ref.current
in useFrame
(or it's entirely possible I'm doing something wrong). Do you have an example of being able to access the ref outside the body hook?
The ref
in the hook from the example code you posted is a reference to a ThreeJS mesh that will receive values from the Cannon physics body - it's not a reference to the actual Cannon physics body.
The api
object that's returned by the hooks in this use-cannon
package is a bridge into the web worker, to allow you to access a subset of Cannon APIs from the main thread - this is the manual abstraction that I mentioned in my last reply.
Since the hook from the example code you posted is already running on the main thread, you have access to the entire cannon-es
lib (the entire Cannon API). If you want access to the body object outside the hook, you can assign it to a ref.
I would be curious to know what environment you can't use web workers in? They are supported pretty well even in older browsers, although I'm not sure if the API might be different in old versions of IE
I don't personally need this, but thought I'd share some results from having implemented cannon.js support (both with and without a Web Worker) in a past project. When running a large simulation, or a simulation that isn't highly interactive, a Web Worker is a great choice. But there's a minimum to the latency of interaction with a physics simulation running in a web worker. This is particularly noticeable with WebXR input devices, where (1) the display is likely refreshing at 90-120fps, and (2) the user's perception of their own motion is quite precise. In that scenario, with interaction wired up to WebXR controllers using Cannon.JS constraints, I found that not using a Web Worker for lightweight simulations worked really well.
Context: https://github.com/n5ro/aframe-physics-system/pull/33
got previous useCannon
https://github.com/pmndrs/use-cannon/issues/63#issuecomment-609457998 r3f example working in this csb: https://codesandbox.io/s/cannon-es-r3f-921gwn
I've simplified the original useCannon(options, fn, deps)
signature, so it now only takes one param. No more limited to a single fn
with dynamic deps
args but: you can have as many fns/deps since the body
is now returned by the hook, like here with different/separate effects
https://codesandbox.io/s/cannon-es-r3f-forked-66mobn?file=/src/App.js:1211-1225
It would be great to allow use-cannon
to disable the worker backend for use in react-native
or for latency sensitive cases as mentioned above. Having the workaround above is nice, but we don't have all the nice @react-three/cannon
api.
It would be great to allow
use-cannon
to disable the worker backend for use inreact-native
or for latency sensitive cases as mentioned above. Having the workaround above is nice, but we don't have all the nice@react-three/cannon
api.
Agreed, this would be great for React Native support (afaik this is the only thing blocking)
The web worker is not native to Cannon, and the Cannon lib can be used without the worker (
cannon-es
).However the worker is a core component of this
use-cannon
hooks abstraction and this lib cannot be used without the worker - too much logic is dependent upon it.It's possible to create your own
useCannon
hook which does not utilize the web worker, but that's outside the scope of this lib. @drcmda made a solid version of a basicuseCannon
hook in some of hisreact-three-fiber
examples:import React, { createContext, useEffect, useRef, useContext, useState } from 'react' import { useFrame } from 'react-three-fiber' import * as CANNON from 'cannon' const WorldContext = createContext({}) export const PhysicsProvider = ({ children }) => { const [ world ] = useState(() => new CANNON.World()) useEffect(() => { world.broadphase = new CANNON.NaiveBroadphase() world.solver.iterations = 10 world.gravity.set(0, 0, -10) }, [ world ]) // Run world stepper every frame useFrame(() => world.step(1 / 60)) return ( <WorldContext.Provider value={world}> {children} </WorldContext.Provider> ) } export const useCannon = ({ ...props }, fn, deps = []) => { const ref = useRef() const world = useContext(WorldContext) const [ body ] = useState(() => new CANNON.Body(props)) useEffect(() => { // Call function so the user can add shapes, positions, etc. to the body fn(body) world.addBody(body) return () => world.remove(body) }, deps) useFrame(() => { if (ref.current) { // Transport cannon physics into the referenced threejs object const { position, quaternion } = body const { x: px, y: py, z: pz } = position const { x: qx, y: qy, z: qz, w: qw } = quaternion ref.current.position.copy(new Vector3(px, py, pz)) ref.current.quaternion.copy(new Quaternion(qx, qy, qz, qw)) } }) return ref }
then to consume:
import React from 'react' import { Canvas} from 'react-three-fiber' import { useCannon } from './index' const Box = ({ position, width, height, depth }) => { const ref = useCannon({ mass: 10 }, body => { body.addShape( new CANNON.Box( new CANNON.Vec3(width * 0.5, height * 0.5, depth * 0.5) ) ) body.position.set(...position) }) return ( <mesh ref={ref}> <boxGeometry attach="geometry" args={[ width, height, depth ]} /> <meshStandardMaterial attach="material" /> </mesh> ) } const App = () => ( <Canvas> <PhysicsProvider> <Box position={[ 0, 0, 5 ]} width={2} height={2} depth={2} /> </PhysicsProvider> </Canvas> )
Note that you will want a second physics object in the scene (such as a floor plane) for your box to collide with. You can combine this with a wireframe debugger (found here) like so:
import { PhysicsBodyWireframes } from './debug' const App = () => ( <Canvas> <PhysicsProvider> <PhysicsBodyWireframes /> <Box position={[ 0, 0, 5 ]} width={2} height={2} depth={2} /> </PhysicsProvider> </Canvas> )
I just used this solution for my project (a few objects collisioning). On web works perfect, but on Android and iOS It gets super laggy. Any solution to the poor performance?
I recently rewrote this library and removed the web worker functionality. I thought I could release another version for everyone to use directly.
I got a problem that in some environment javascript worker api should be disabled.
Shall I disable it in cannon manually?