Skip to content

Commit

Permalink
code review
Browse files Browse the repository at this point in the history
  • Loading branch information
Jens Schmidt committed Feb 16, 2021
1 parent 4c8e231 commit b99791e
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 96 deletions.
10 changes: 6 additions & 4 deletions .storybook/stories/BlenderViewportGizmo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ const BlenderViewportGizmoStory = () => {
</>
)
}
export const DefaultStory = () => (
<React.Suspense fallback={null}>
<BlenderViewportGizmoStory />
</React.Suspense>
)

export const BlenderViewportGizmoStorySt = () => <BlenderViewportGizmoStory />
BlenderViewportGizmoStorySt.story = {
name: 'Default',
}
DefaultStory.storyName = 'Default'
15 changes: 8 additions & 7 deletions .storybook/stories/ViewCubeGizmo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ const ViewCubeGizmoStory = () => {
return (
<>
<PerspectiveCamera makeDefault position={[0, 0, 10]} />
<React.Suspense fallback={null}>
<primitive object={scene} scale={[0.01, 0.01, 0.01]} />
</React.Suspense>
<primitive object={scene} scale={[0.01, 0.01, 0.01]} />
<GizmoHelper
alignment={select('alignment', ['top-left', 'top-right', 'bottom-right', 'bottom-left'], 'bottom-right')}
margin={[number('marginX', 80), number('marginY', 80)]}
Expand All @@ -44,7 +42,10 @@ const ViewCubeGizmoStory = () => {
)
}

export const ViewCubeGizmoStorySt = () => <ViewCubeGizmoStory />
ViewCubeGizmoStorySt.story = {
name: 'Default',
}
export const DefaultStory = () => (
<React.Suspense fallback={null}>
<ViewCubeGizmoStory />
</React.Suspense>
)

DefaultStory.storyName = 'Default'
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ Adds a `<Plane />` that always faces the camera.

Used by widgets that visualize and control camera position.

Two example gizmos are included: BlenderViewportGizmo and ViewCubeGizmo, and `useGizmoHelper` makes it easy to create your own.
Two example gizmos are included: BlenderViewportGizmo and ViewCubeGizmo, and `useGizmoContext` makes it easy to create your own.

```jsx
<GizmoHelper
Expand Down
32 changes: 16 additions & 16 deletions src/core/GizmoHelper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type GizmoHelperContext = {

const Context = React.createContext<GizmoHelperContext>({} as GizmoHelperContext)

export const useGizmoHelper = () => {
export const useGizmoContext = () => {
return React.useContext<GizmoHelperContext>(Context)
}

Expand Down Expand Up @@ -41,43 +41,43 @@ export const GizmoHelper = ({
const gizmoRef = React.useRef<Group>()
const virtualCam = React.useRef<any>()
const virtualScene = React.useMemo(() => new Scene(), [])
const [animating, setAnimating] = React.useState(false)
const [radius, setRadius] = React.useState(0)
const [focusPoint, setFocusPoint] = React.useState(new Vector3(0, 0, 0))

function tweenCamera(direction: Vector3) {
const radius = mainCamera.position.distanceTo(target)
setRadius(radius)
setFocusPoint(onTarget())
setAnimating(true)
const animating = React.useRef(false)
const radius = React.useRef(0)
const focusPoint = React.useRef(new Vector3(0, 0, 0))

const tweenCamera = (direction: Vector3) => {
animating.current = true
focusPoint.current = onTarget()
radius.current = mainCamera.position.distanceTo(target)

// Rotate from current camera orientation
dummy.position.copy(target)
dummy.lookAt(mainCamera.position)
q1.copy(dummy.quaternion)

// To new current camera orientation
targetPosition.copy(direction).multiplyScalar(radius).add(target)
targetPosition.copy(direction).multiplyScalar(radius.current).add(target)
dummy.lookAt(targetPosition)
q2.copy(dummy.quaternion)
}

function animateStep(delta: number): void {
if (!animating) return
const animateStep = (delta: number) => {
if (!animating.current) return

const step = delta * turnRate

// animate position by doing a slerp and then scaling the position on the unit sphere
q1.rotateTowards(q2, step)
mainCamera.position.set(0, 0, 1).applyQuaternion(q1).multiplyScalar(radius).add(focusPoint)
mainCamera.position.set(0, 0, 1).applyQuaternion(q1).multiplyScalar(radius.current).add(focusPoint.current)

// animate orientation
mainCamera.quaternion.rotateTowards(targetQuaternion, step)
mainCamera.updateProjectionMatrix()
onUpdate && onUpdate()

if (q1.angleTo(q2) === 0) {
setAnimating(false)
if (q1.angleTo(q2) < 0.01) {
animating.current = false
}
}

Expand All @@ -98,7 +98,7 @@ export const GizmoHelper = ({
gl.clearDepth()
gl.render(virtualScene, virtualCam.current)
}
}, 1)
})

const gizmoHelperContext = {
tweenCamera,
Expand Down
2 changes: 0 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ export * from './PointerLockControls'

// Gizmos
export * from './GizmoHelper'
export * from './BlenderViewportGizmo'
export * from './ViewCubeGizmo'

// Loaders
export * from './useCubeTexture'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import * as React from 'react'
import { BoxGeometry, CanvasTexture, Event } from 'three'
import { useGizmoHelper } from './GizmoHelper'
import { CanvasTexture, Event } from 'three'
import { useGizmoContext } from '../core/GizmoHelper'

function Axis({ color, rotation }: JSX.IntrinsicElements['mesh'] & { color: string }) {
const geometry = React.useMemo(() => new BoxGeometry(0.8, 0.05, 0.05).translate(0.4, 0, 0), [])
type NewType = JSX.IntrinsicElements['mesh'] & {
color: string
}

function Axis({ color, rotation }: NewType) {
return (
<mesh geometry={geometry} rotation={rotation}>
<meshBasicMaterial color={color} toneMapped={false} />
</mesh>
<group rotation={rotation}>
<mesh position={[0.4, 0, 0]}>
<boxGeometry args={[0.8, 0.05, 0.05]} />
<meshBasicMaterial color={color} toneMapped={false} />
</mesh>
</group>
)
}

function AxisHead({
arcStyle,
label,
labelColor,
...props
}: JSX.IntrinsicElements['sprite'] & { arcStyle: string; label?: string; labelColor: string }) {
type AxisHeadProps = JSX.IntrinsicElements['sprite'] & {
arcStyle: string
label?: string
labelColor: string
}

function AxisHead({ arcStyle, label, labelColor, ...props }: AxisHeadProps) {
const texture = React.useMemo(() => {
const canvas = document.createElement('canvas')
canvas.width = 64
Expand Down Expand Up @@ -49,7 +56,7 @@ function AxisHead({
)
}

type BlenderViewportGizmoProps = {
type BlenderViewportGizmoProps = JSX.IntrinsicElements['group'] & {
axisColors?: [string, string, string]
labelColor?: string
}
Expand All @@ -60,7 +67,7 @@ export const BlenderViewportGizmo = ({
...props
}: BlenderViewportGizmoProps) => {
const [colorX, colorY, colorZ] = axisColors
const { tweenCamera, raycast } = useGizmoHelper()
const { tweenCamera, raycast } = useGizmoContext()
const axisHeadProps = {
labelColor,
onPointerDown: (e: Event) => void (tweenCamera(e.object.position), e.stopPropagation()),
Expand Down
114 changes: 63 additions & 51 deletions src/core/ViewCubeGizmo.tsx → src/web/ViewCubeGizmo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import * as React from 'react'
import { useGizmoContext } from '../core/GizmoHelper'
import { CanvasTexture, Event, Vector3 } from 'three'
import { useGizmoHelper } from './GizmoHelper'

const faces = ['RIGHT', 'LEFT', 'TOP', 'BOTTOM', 'FRONT', 'BACK']
type XYZ = [number, number, number]

const edges: Array<[number, number, number]> = [
const faces = ['right', 'left', 'top', 'bottom', 'front', 'back']

const makePositionVector = (xyz) => new Vector3(...xyz).multiplyScalar(0.38)

const corners: Vector3[] = [
[1, 1, 1],
[1, 1, -1],
[1, -1, 1],
[1, -1, -1],
[-1, 1, 1],
[-1, 1, -1],
[-1, -1, 1],
[-1, -1, -1],
].map(makePositionVector)

const cornerDimensions: XYZ = [0.25, 0.25, 0.25]

const edges: Vector3[] = [
[1, 1, 0],
[1, 0, 1],
[1, 0, -1],
Expand All @@ -17,18 +34,11 @@ const edges: Array<[number, number, number]> = [
[-1, 0, 1],
[-1, 0, -1],
[-1, -1, 0],
]
].map(makePositionVector)

const corners: Array<[number, number, number]> = [
[1, 1, 1],
[1, 1, -1],
[1, -1, 1],
[1, -1, -1],
[-1, 1, 1],
[-1, 1, -1],
[-1, -1, 1],
[-1, -1, -1],
]
const edgeDimensions = edges.map(
(edge) => edge.toArray().map((axis: number): number => (axis == 0 ? 0.5 : 0.25)) as XYZ
)

const FaceMaterial = ({ hover, index }: { hover: boolean; index: number }) => {
const texture = React.useMemo(() => {
Expand All @@ -45,21 +55,29 @@ const FaceMaterial = ({ hover, index }: { hover: boolean; index: number }) => {
context.font = '28px Arial'
context.textAlign = 'center'
context.fillStyle = '#222'
context.fillText(faces[index], 64, 76)
context.fillText(faces[index].toUpperCase(), 64, 76)
return new CanvasTexture(canvas)
}, [index])
return <meshLambertMaterial map={texture} attachArray="material" color={hover ? 'hotpink' : 'white'} />
}

const FaceCube = () => {
const { tweenCamera, raycast } = useGizmoHelper()
const { tweenCamera, raycast } = useGizmoContext()
const [hover, set] = React.useState<number | null>(null)
const handlePointerDown = (e: Event) => {
tweenCamera(e.face.normal)
e.stopPropagation()
}
const handlePointerMove = (e: Event) => {
set(Math.floor(e.faceIndex / 2))
e.stopPropagation()
}
return (
<mesh
raycast={raycast}
onPointerOut={() => set(null)}
onPointerMove={(e: Event) => set(Math.floor(e.faceIndex / 2))}
onPointerDown={(e: Event) => void (tweenCamera(e.face.normal), e.stopPropagation())}
onPointerMove={handlePointerMove}
onPointerDown={handlePointerDown}
>
{[...Array(6)].map((_, index) => (
<FaceMaterial key={index} index={index} hover={hover === index} />
Expand All @@ -69,56 +87,50 @@ const FaceCube = () => {
)
}

const EdgeCube = ({ direction }: { direction: [number, number, number] }): JSX.Element => {
const [x, y, z] = direction
const dimensions: [number, number, number] = [x == 0 ? 1 / 2 : 1 / 4, y == 0 ? 1 / 2 : 1 / 4, z == 0 ? 1 / 2 : 1 / 4]
const { tweenCamera, raycast } = useGizmoHelper()
const [hover, set] = React.useState<boolean>(false)
const position = new Vector3().set(...direction).setLength(1 - 1 / 2 + 0.1)
return (
<mesh
position={position}
raycast={raycast}
onPointerOut={(e: Event) => void (set(false), e.stopPropagation())}
onPointerOver={(e: Event) => void (set(true), e.stopPropagation())}
onPointerDown={(e: Event) => void (tweenCamera(position), e.stopPropagation())}
>
<meshBasicMaterial color={hover ? 'hotpink' : 'white'} transparent={true} opacity={0.5} visible={hover} />
<boxBufferGeometry attach="geometry" args={dimensions} />
</mesh>
)
type EdgeCubeProps = {
dimensions: XYZ
position: Vector3
}

const CornerCube = ({ direction }: { direction: [number, number, number] }) => {
const size = 1 / 4
const { tweenCamera, raycast } = useGizmoHelper()
const EdgeCube = ({ dimensions, position }: EdgeCubeProps): JSX.Element => {
const { tweenCamera, raycast } = useGizmoContext()
const [hover, set] = React.useState<boolean>(false)
const position = new Vector3().set(...direction).setLength(1 - 2 * size + 0.2)
const handlePointerOut = (e: Event) => {
set(false)
e.stopPropagation()
}
const handlePointerOver = (e: Event) => {
set(true)
e.stopPropagation()
}
const handlePointerDown = (e: Event) => {
tweenCamera(position)
e.stopPropagation()
}
return (
<mesh
position={position}
raycast={raycast}
onPointerOut={(e: Event) => void (set(false), e.stopPropagation())}
onPointerOver={(e: Event) => void (set(true), e.stopPropagation())}
onPointerDown={(e: Event) => void (tweenCamera(position), e.stopPropagation())}
onPointerOver={handlePointerOver}
onPointerOut={handlePointerOut}
onPointerDown={handlePointerDown}
>
<meshBasicMaterial color={hover ? 'hotpink' : 'white'} transparent={true} opacity={0.5} visible={hover} />
<boxBufferGeometry attach="geometry" args={[size, size, size]} />
<meshBasicMaterial color={hover ? 'hotpink' : 'white'} transparent={true} opacity={0.75} visible={hover} />
<boxBufferGeometry attach="geometry" args={dimensions} />
</mesh>
)
}

export const ViewCubeGizmo = () => {
return (
<group scale={[60, 60, 60]}>
{corners.map((corner, index) => (
<CornerCube key={index} direction={corner} />
))}
<FaceCube />
{edges.map((edge, index) => (
<EdgeCube key={index} direction={edge} />
<EdgeCube key={index} position={edge} dimensions={edgeDimensions[index]} />
))}
{corners.map((corner, index) => (
<EdgeCube key={index} position={corner} dimensions={cornerDimensions} />
))}
<FaceCube />

<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={0.5} />
</group>
Expand Down
4 changes: 4 additions & 0 deletions src/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ export { Html } from './Html'

export { Loader } from './Loader'

export { BlenderViewportGizmo } from './BlenderViewportGizmo'

export { ViewCubeGizmo } from './ViewCubeGizmo'

export * from '../core'
export * from '../materials'

0 comments on commit b99791e

Please sign in to comment.