text: import React, { Suspense, createRef, createContext, useContext, useCallback, useEffect, useMemo, useRef } from 'react'
  import { Canvas, useFrame, useLoader } from '@react-three/fiber'
  import { GLTFLoader } from 'three-stdlib/loaders/GLTFLoader'
  import { Physics, useBox, useCompoundBody, useCylinder, useSphere, usePlane, useConeTwistConstraint, usePointToPointConstraint } from '@react-three/cannon'
  import { createRagdoll } from './createConfig'
  
  const { shapes, joints } = createRagdoll(4.8, Math.PI / 16, Math.PI / 16, 0)
  const context = createContext()
  const cursor = createRef()
  
  function useDragConstraint(child) {
    const [, , api] = usePointToPointConstraint(cursor, child, { pivotA: [0, 0, 0], pivotB: [0, 0, 0] })
    useEffect(() => void api.disable(), [])
    const onPointerUp = useCallback(() => api.disable(), [])
    const onPointerDown = useCallback((e) => {
      e.stopPropagation()
      e.target.setPointerCapture(e.pointerId)
      api.enable()
    }, [])
    return { onPointerUp, onPointerDown }
  }
  
  const BodyPart = ({ config, children, render, name, ...props }) => {
    const { color, args, mass, position } = shapes[name]
    const scale = useMemo(() => args.map((s) => s * 2), [args])
    const parent = useContext(context)
    const [ref] = useBox(() => ({ mass, args, position, linearDamping: 0.99 }))
    useConeTwistConstraint(ref, parent, config)
    const bind = useDragConstraint(ref)
    return (
      <context.Provider value={ref}>
        <Box castShadow receiveShadow ref={ref} {...props} {...bind} scale={scale} name={name} color={color}>
          {render}
        </Box>
        {children}
      </context.Provider>
    )
  }
  
  function Ragdoll(props) {
    const mouth = useRef()
    const eyes = useRef()
    const [ref, api] = useSphere(() => ({ type: 'Static', args: [0.5], position: [0, 0, 10000] }), cursor)
    useFrame((e) => {
      eyes.current.position.y = Math.sin(e.clock.getElapsedTime() * 1) * 0.06
      mouth.current.scale.y = (1 + Math.sin(e.clock.getElapsedTime())) * 1.5
      const x = e.mouse.x * e.viewport.width
      const y = (e.mouse.y * e.viewport.height) / 1.9 + -x / 3.5
      api.position.set(x / 1.4, y, 0)
    })
    return (
      <BodyPart name={'upperBody'} {...props}>
        <mesh ref={ref}>
          <sphereBufferGeometry args={[0.5, 32, 32]} />
          <meshBasicMaterial fog={false} depthTest={false} transparent opacity={0.5} />
        </mesh>
        <BodyPart
          {...props}
          name={'head'}
          config={joints['neckJoint']}
          render={
            <>
              <group ref={eyes}>
                <Box position={[-0.3, 0.1, 0.5]} args={[0.3, 0.01, 0.1]} color="black" transparent opacity={0.8} />
                <Box position={[0.3, 0.1, 0.5]} args={[0.3, 0.01, 0.1]} color="black" transparent opacity={0.8} />
              </group>
              <Box ref={mouth} position={[0, -0.2, 0.5]} args={[0.3, 0.05, 0.1]} color="#270000" transparent opacity={0.8} />
            </>
          }
        />
        <BodyPart {...props} name={'upperLeftArm'} config={joints['leftShoulder']}>
          <BodyPart {...props} name={'lowerLeftArm'} config={joints['leftElbowJoint']} />
        </BodyPart>
        <BodyPart {...props} name={'upperRightArm'} config={joints['rightShoulder']}>
          <BodyPart {...props} name={'lowerRightArm'} config={joints['rightElbowJoint']} />
        </BodyPart>
        <BodyPart {...props} name={'pelvis'} config={joints['spineJoint']}>
          <BodyPart {...props} name={'upperLeftLeg'} config={joints['leftHipJoint']}>
            <BodyPart {...props} name={'lowerLeftLeg'} config={joints['leftKneeJoint']} />
          </BodyPart>
          <BodyPart {...props} name={'upperRightLeg'} config={joints['rightHipJoint']}>
            <BodyPart {...props} name={'lowerRightLeg'} config={joints['rightKneeJoint']} />
          </BodyPart>
        </BodyPart>
      </BodyPart>
    )
  }
  
  function Plane(props) {
    const [ref] = usePlane(() => ({ ...props }))
    return (
      <mesh ref={ref} receiveShadow>
        <planeBufferGeometry args={[1000, 1000]} />
        <meshStandardMaterial color="#171720" />
      </mesh>
    )
  }
  
  const Box = React.forwardRef(({ children, transparent = false, opacity = 1, color = 'white', args = [1, 1, 1], ...props }, ref) => {
    return (
      <mesh receiveShadow castShadow ref={ref} {...props}>
        <boxBufferGeometry args={args} />
        <meshStandardMaterial color={color} transparent={transparent} opacity={opacity} />
        {children}
      </mesh>
    )
  })
  
  function Chair() {
    const [ref] = useCompoundBody(() => ({
      mass: 1,
      type: 'Dynamic',
      position: [-6, 0, 0],
      shapes: [
        { type: 'Box', mass: 1, position: [0, 0, 0], args: [1.5, 1.5, 0.25] },
        { type: 'Box', mass: 1, position: [0, -1.75, 1.25], args: [1.5, 0.25, 1.5] },
        { type: 'Box', mass: 10, position: [5 + -6.25, -3.5, 0], args: [0.25, 1.5, 0.25] },
        { type: 'Box', mass: 10, position: [5 + -3.75, -3.5, 0], args: [0.25, 1.5, 0.25] },
        { type: 'Box', mass: 10, position: [5 + -6.25, -3.5, 2.5], args: [0.25, 1.5, 0.25] },
        { type: 'Box', mass: 10, position: [5 + -3.75, -3.5, 2.5], args: [0.25, 1.5, 0.25] },
      ],
    }))
    const bind = useDragConstraint(ref)
    return (
      <group ref={ref} {...bind}>
        <Box position={[0, 0, 0]} scale={[3, 3, 0.5]} />
        <Box position={[0, -1.75, 1.25]} scale={[3, 0.5, 3]} />
        <Box position={[5 + -6.25, -3.5, 0]} scale={[0.5, 3, 0.5]} />
        <Box position={[5 + -3.75, -3.5, 0]} scale={[0.5, 3, 0.5]} />
        <Box position={[5 + -6.25, -3.5, 2.5]} scale={[0.5, 3, 0.5]} />
        <Box position={[5 + -3.75, -3.5, 2.5]} scale={[0.5, 3, 0.5]} />
      </group>
    )
  }
  
  //radiusTop, radiusBottom, height, numSegments
  function Mug() {
    const { nodes, materials } = useLoader(GLTFLoader, '/cup.glb')
    const [cup] = useCylinder(() => ({
      mass: 1,
      rotation: [Math.PI / 2, 0, 0],
      position: [9, 0, 0],
      args: [0.6, 0.6, 1, 16],
    }))
    const bind = useDragConstraint(cup)
    return (
      <group ref={cup} {...bind} dispose={null}>
        <group scale={[0.01, 0.01, 0.01]}>
          <mesh receiveShadow castShadow material={materials.default} geometry={nodes['buffer-0-mesh-0'].geometry} />
          <mesh receiveShadow castShadow material={materials.Liquid} geometry={nodes['buffer-0-mesh-0_1'].geometry} />
        </group>
      </group>
    )
  }
  
  function Table() {
    const [seat] = useBox(() => ({ type: 'Static', position: [9, -0.8, 0], args: [2.5, 0.25, 2.5] }))
    const [leg1] = useBox(() => ({ type: 'Static', position: [7.2, -3, 1.8], args: [0.25, 2, 0.25] }))
    const [leg2] = useBox(() => ({ type: 'Static', position: [10.8, -3, 1.8], args: [0.25, 2, 0.25] }))
    const [leg3] = useBox(() => ({ type: 'Static', position: [7.2, -3, -1.8], args: [0.25, 2, 0.25] }))
    const [leg4] = useBox(() => ({ type: 'Static', position: [10.8, -3, -1.8], args: [0.25, 2, 0.25] }))
    return (
      <>
        <Box scale={[5, 0.5, 5]} ref={seat} />
        <Box scale={[0.5, 4, 0.5]} ref={leg1} />
        <Box scale={[0.5, 4, 0.5]} ref={leg2} />
        <Box scale={[0.5, 4, 0.5]} ref={leg3} />
        <Box scale={[0.5, 4, 0.5]} ref={leg4} />
        <Suspense fallback={null}>
          <Mug />
        </Suspense>
      </>
    )
  }
  
  const Lamp = () => {
    const light = useRef()
    const [fixed] = useSphere(() => ({ type: 'Static', args: 1, position: [0, 16, 0] }))
    const [lamp] = useBox(() => ({
      mass: 1,
      args: [1, 0, 5, 1],
      linearDamping: 0.9,
      angulardamping: 1.99,
      position: [0, 16, 0],
    }))
    usePointToPointConstraint(fixed, lamp, { pivotA: [0, 0, 0], pivotB: [0, 2, 0] })
    const bind = useDragConstraint(lamp)
    return (
      <>
        <mesh ref={lamp} {...bind}>
          <coneBufferGeometry attach="geometry" args={[2, 2.5, 32]} />
          <meshStandardMaterial attach="material" />
          <pointLight intensity={10} distance={5} />
          <spotLight ref={light} position={[0, 20, 0]} angle={0.4} penumbra={1} intensity={0.6} castShadow />
        </mesh>
      </>
    )
  }
  
  export default () => (
    <Canvas style={{ cursor: 'none' }} shadows orthographic camera={{ position: [-25, 20, 25], zoom: 25, near: 1, far: 100 }}>
      <color attach="background" args={['#171720']} />
      <fog attach="fog" args={['#171720', 20, 70]} />
      <ambientLight intensity={0.2} />
      <pointLight position={[-10, -10, -10]} color="red" intensity={1.5} />
      <Physics iterations={15} gravity={[0, -200, 0]} allowSleep={false}>
        <Ragdoll position={[0, 0, 0]} />
        <Plane position={[0, -5, 0]} rotation={[-Math.PI / 2, 0, 0]} />
        <Chair />
        <Table />
        <Lamp />
      </Physics>
    </Canvas>
  )
  