Skip to content

Commit

Permalink
v1.0.10 (#34)
Browse files Browse the repository at this point in the history
* feat: Add ignoreQuickPress, which defaults to false

* fix: Avoid division by 0

* Update dependencies

* Add prepublishOnly script in package.json

* Change version to 1.0.10 and build lib
  • Loading branch information
TiagoCavalcante authored Feb 17, 2024
1 parent 907674d commit 7468e51
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 65 deletions.
55 changes: 33 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a href="https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/npm/l/r3f-native-orbitcontrols.svg" /></a>
<a href="https://bundlephobia.com/package/r3f-native-orbitcontrols"><img alt="Ziped size" src="https://img.shields.io/bundlephobia/minzip/r3f-native-orbitcontrols" /></a>

OrbitControls for React Three Fiber in React Native
OrbitControls for [React Three Fiber](https://github.com/pmndrs/react-three-fiber) in React Native

## Install

Expand All @@ -25,15 +25,15 @@ import useControls from "r3f-native-orbitcontrols"

import { Canvas } from "@react-three/fiber/native"
import { View } from "react-native"
// The import bellow is used only in Canvases:
// The import below is used only in Canvases:
import { PerspectiveCamera } from "three"

function SingleCanvas() {
const [OrbitControls, events] = useControls()

return (
// If this isn't working check if you have set the size of the View.
// The easiest way to do it is use the full size, e.g.:
// The easiest way to do it is to use the full size, e.g.:
// <View style={{ flex: 1 }} />
<View {...events}>
<Canvas>
Expand Down Expand Up @@ -71,26 +71,37 @@ function Canvases() {
}
```

You can find an example app [here](https://github.com/TiagoCavalcante/r3f-orbitcontrols-example).

## Options

The `<OrbitControls />` element _may_ receive the following properties:

| Property | Type | Description |
| :-------------- | :-------------: | ----------------------------------------------: |
| camera | Camera | readonly, available to onChange |
| enabled | boolean | |
| target | Vector3 | |
| minPolarAngle | number | how close you can orbit vertically |
| maxPolarAngle | number | how far you can orbit vertically |
| minAzimuthAngle | number | how close you can orbit horizontally |
| maxAzimuthAngle | number | how far you can orbit horizontally |
| dampingFactor | number | inertia factor |
| enableZoom | boolean | |
| zoomSpeed | number | |
| minZoom | number | |
| maxZoom | number | |
| enableRotate | boolean | |
| rotateSpeed | number | |
| enablePan | boolean | |
| panSpeed | number | |
| onChange | (event) => void | receives an event with all the properties above |
| Property | Type | Description |
| :--------------- | :-------------: | ----------------------------------------------: |
| camera | Camera | read-only, available to onChange |
| enabled | boolean | |
| target | Vector3 | |
| minPolarAngle | number | how close you can orbit vertically |
| maxPolarAngle | number | how far you can orbit vertically |
| minAzimuthAngle | number | how close you can orbit horizontally |
| maxAzimuthAngle | number | how far you can orbit horizontally |
| dampingFactor | number | inertia factor |
| enableZoom | boolean | |
| zoomSpeed | number | |
| minZoom | number | |
| maxZoom | number | |
| enableRotate | boolean | |
| rotateSpeed | number | |
| enablePan | boolean | |
| panSpeed | number | |
| ignoreQuickPress | boolean | may cause bugs when enabled\* |
| onChange | (event) => void | receives an event with all the properties above |

You can find the defaults for each option [here](...).

\*: This option is **not** recommended in modern devices. It's only useful in older devices, which don't propagate touch events to prevent "bubbling". You can find more information about this [here](...).

## Why not use [drei](https://github.com/pmndrs/drei)'s OrbitControls?

The answer is very simple: they don't work on native, only on the web and (much) older versions of [React Three Fiber](https://github.com/pmndrs/react-three-fiber).
60 changes: 51 additions & 9 deletions lib/index.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var partialScope = {
rotateSpeed: 1.0,
enablePan: true,
panSpeed: 1.0,
ignoreQuickPress: false,
};
function createControls() {
var height = 0;
Expand All @@ -100,14 +101,49 @@ function createControls() {
};
var functions = {
shouldClaimTouch: function (event) {
// If there's 1 touch it may not be related to orbit-controls,
// therefore we delay "claiming" the touch.
// If there's 1 touch it may not be related to orbit controls,
// therefore we delay "claiming" the touch, as on older devices this stops the
// event propagation to prevent bubbling.
// This option is disabled by default because on newer devices (I tested on
// Android 8+ and iOS 15+) this behavior is (happily) inexistent (the
// propagation only stops if the code explicitly tells it to do so).
// See https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/issues/27
// Unfortunately, this feature may cause bugs in newer devices or browsers,
// where the first presses (quick or long) aren't detected.
// See https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/issues/30
// See https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/issues/31
// Therefore it is **not** recommended to enable it if you are targeting newer
// devices.
// There are other options to fix this behavior on older devices:
// 1. Use the events `onTouchStart`, `onTouchMove`, `onTouchEnd`,
// `onTouchCancel` from @react-three/fiber's `Canvas`. I didn't choose this
// option because it seems to be slower than using the gesture responder
// system directly, and it would also make it harder to use these events
// in the `Canvas`.
// 2. Add a transparent `Plane` that covers the whole screen and use its
// touch events, which are exposed by @react-three/fiber. I didn't choose
// this option because it would hurt performance and just seems to be too
// hacky.
// 3. Use `View`'s `onTouchStart`, `onTouchMove`, etc. I think this would have
// the same behavior in older devices, but I still didn't test it. If you
// want me to test it, please just open an issue.
// Note that using @react-three/fiber's
// `useThree().gl.domElement.addEventListener` doesn't work, just look at the
// code of the function:
// https://github.com/pmndrs/react-three-fiber/blob/6c830bd793cfd15d980299f2582f8a70cc53e30c/packages/fiber/src/native/Canvas.tsx#L83-L84
// Ideally, this should be fixed by implementing something like an
// `addEventListener`-like in @react-three/fiber.
// I have suggested this feature here:
// https://github.com/pmndrs/react-three-fiber/issues/3173
if (!scope.ignoreQuickPress)
return true;
if (event.nativeEvent.touches.length === 1) {
var _a = event.nativeEvent.touches[0], x = _a.locationX, y = _a.locationY, t = _a.timestamp;
var dx = Math.abs(internals.moveStart.x - x);
var dy = Math.abs(internals.moveStart.y - y);
var dt = Math.pow(internals.moveStart.z - t, 2);
if (!internals.moveStart.length() || (dx * dt <= 1000 && dy * dt <= 1000)) {
if (!internals.moveStart.length() ||
(dx * dt <= 1000 && dy * dt <= 1000)) {
internals.moveStart.set(x, y, t);
return false;
}
Expand Down Expand Up @@ -200,9 +236,12 @@ function createControls() {
internals.rotateDelta
.subVectors(internals.rotateEnd, internals.rotateStart)
.multiplyScalar(scope.rotateSpeed);
// yes, height
this.rotateLeft((2 * Math.PI * internals.rotateDelta.x) / height);
this.rotateUp((2 * Math.PI * internals.rotateDelta.y) / height);
// Avoid division by 0.
if (height) {
// yes, height
this.rotateLeft((2 * Math.PI * internals.rotateDelta.x) / height);
this.rotateUp((2 * Math.PI * internals.rotateDelta.y) / height);
}
internals.rotateStart.copy(internals.rotateEnd);
},
dollyOut: function (dollyScale) {
Expand Down Expand Up @@ -248,9 +287,12 @@ function createControls() {
: // scale the zoom speed by a factor of 300
(1 / linearSquare(scope.camera.zoom)) * scope.zoomSpeed * 300;
targetDistance *= Math.tan((distanceScale * Math.PI) / 180.0);
// we use only height here so aspect ratio does not distort speed
this.panLeft((2 * deltaX * targetDistance) / height, scope.camera.matrix);
this.panUp((2 * deltaY * targetDistance) / height, scope.camera.matrix);
// Avoid division by 0.
if (height) {
// we use only height here so aspect ratio does not distort speed
this.panLeft((2 * deltaX * targetDistance) / height, scope.camera.matrix);
this.panUp((2 * deltaY * targetDistance) / height, scope.camera.matrix);
}
},
handleTouchMovePan: function (event) {
if (event.nativeEvent.touches.length === 1) {
Expand Down
12 changes: 9 additions & 3 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare const partialScope: {
rotateSpeed: number
enablePan: boolean
panSpeed: number
ignoreQuickPress: boolean
}
declare function createControls(): {
scope: {
Expand All @@ -40,6 +41,7 @@ declare function createControls(): {
rotateSpeed: number
enablePan: boolean
panSpeed: number
ignoreQuickPress: boolean
}
functions: {
update: () => void
Expand Down Expand Up @@ -84,12 +86,16 @@ declare function useControls(): readonly [
{
onLayout(event: react_native.LayoutChangeEvent): void
onStartShouldSetResponder(
event: react_native.GestureResponderEvent
event: react_native.GestureResponderEvent,
): boolean
onMoveShouldSetResponder(event: react_native.GestureResponderEvent): boolean
onResponderMove(event: react_native.GestureResponderEvent): void
onResponderRelease(): void
}
},
]

export { OrbitControlsChangeEvent, OrbitControlsProps, useControls as default }
export {
type OrbitControlsChangeEvent,
type OrbitControlsProps,
useControls as default,
}
60 changes: 51 additions & 9 deletions lib/index.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ var partialScope = {
rotateSpeed: 1.0,
enablePan: true,
panSpeed: 1.0,
ignoreQuickPress: false,
};
function createControls() {
var height = 0;
Expand All @@ -98,14 +99,49 @@ function createControls() {
};
var functions = {
shouldClaimTouch: function (event) {
// If there's 1 touch it may not be related to orbit-controls,
// therefore we delay "claiming" the touch.
// If there's 1 touch it may not be related to orbit controls,
// therefore we delay "claiming" the touch, as on older devices this stops the
// event propagation to prevent bubbling.
// This option is disabled by default because on newer devices (I tested on
// Android 8+ and iOS 15+) this behavior is (happily) inexistent (the
// propagation only stops if the code explicitly tells it to do so).
// See https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/issues/27
// Unfortunately, this feature may cause bugs in newer devices or browsers,
// where the first presses (quick or long) aren't detected.
// See https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/issues/30
// See https://github.com/TiagoCavalcante/r3f-native-orbitcontrols/issues/31
// Therefore it is **not** recommended to enable it if you are targeting newer
// devices.
// There are other options to fix this behavior on older devices:
// 1. Use the events `onTouchStart`, `onTouchMove`, `onTouchEnd`,
// `onTouchCancel` from @react-three/fiber's `Canvas`. I didn't choose this
// option because it seems to be slower than using the gesture responder
// system directly, and it would also make it harder to use these events
// in the `Canvas`.
// 2. Add a transparent `Plane` that covers the whole screen and use its
// touch events, which are exposed by @react-three/fiber. I didn't choose
// this option because it would hurt performance and just seems to be too
// hacky.
// 3. Use `View`'s `onTouchStart`, `onTouchMove`, etc. I think this would have
// the same behavior in older devices, but I still didn't test it. If you
// want me to test it, please just open an issue.
// Note that using @react-three/fiber's
// `useThree().gl.domElement.addEventListener` doesn't work, just look at the
// code of the function:
// https://github.com/pmndrs/react-three-fiber/blob/6c830bd793cfd15d980299f2582f8a70cc53e30c/packages/fiber/src/native/Canvas.tsx#L83-L84
// Ideally, this should be fixed by implementing something like an
// `addEventListener`-like in @react-three/fiber.
// I have suggested this feature here:
// https://github.com/pmndrs/react-three-fiber/issues/3173
if (!scope.ignoreQuickPress)
return true;
if (event.nativeEvent.touches.length === 1) {
var _a = event.nativeEvent.touches[0], x = _a.locationX, y = _a.locationY, t = _a.timestamp;
var dx = Math.abs(internals.moveStart.x - x);
var dy = Math.abs(internals.moveStart.y - y);
var dt = Math.pow(internals.moveStart.z - t, 2);
if (!internals.moveStart.length() || (dx * dt <= 1000 && dy * dt <= 1000)) {
if (!internals.moveStart.length() ||
(dx * dt <= 1000 && dy * dt <= 1000)) {
internals.moveStart.set(x, y, t);
return false;
}
Expand Down Expand Up @@ -198,9 +234,12 @@ function createControls() {
internals.rotateDelta
.subVectors(internals.rotateEnd, internals.rotateStart)
.multiplyScalar(scope.rotateSpeed);
// yes, height
this.rotateLeft((2 * Math.PI * internals.rotateDelta.x) / height);
this.rotateUp((2 * Math.PI * internals.rotateDelta.y) / height);
// Avoid division by 0.
if (height) {
// yes, height
this.rotateLeft((2 * Math.PI * internals.rotateDelta.x) / height);
this.rotateUp((2 * Math.PI * internals.rotateDelta.y) / height);
}
internals.rotateStart.copy(internals.rotateEnd);
},
dollyOut: function (dollyScale) {
Expand Down Expand Up @@ -246,9 +285,12 @@ function createControls() {
: // scale the zoom speed by a factor of 300
(1 / linearSquare(scope.camera.zoom)) * scope.zoomSpeed * 300;
targetDistance *= Math.tan((distanceScale * Math.PI) / 180.0);
// we use only height here so aspect ratio does not distort speed
this.panLeft((2 * deltaX * targetDistance) / height, scope.camera.matrix);
this.panUp((2 * deltaY * targetDistance) / height, scope.camera.matrix);
// Avoid division by 0.
if (height) {
// we use only height here so aspect ratio does not distort speed
this.panLeft((2 * deltaX * targetDistance) / height, scope.camera.matrix);
this.panUp((2 * deltaY * targetDistance) / height, scope.camera.matrix);
}
},
handleTouchMovePan: function (event) {
if (event.nativeEvent.touches.length === 1) {
Expand Down
29 changes: 15 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "r3f-native-orbitcontrols",
"version": "1.0.9",
"version": "1.0.10",
"description": "OrbitControls for React Three Fiber in React Native",
"main": "lib/index.cjs.js",
"module": "lib/index.esm.js",
Expand All @@ -11,7 +11,8 @@
},
"scripts": {
"build": "rollup -c",
"prepare": "husky install"
"prepare": "husky install",
"prepublishOnly": "npm run build"
},
"keywords": [
"r3f",
Expand All @@ -28,19 +29,19 @@
"three": ">=0.139.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-node-resolve": "^15.0.2",
"@types/react": "^18.0.35",
"@types/react-native": "^0.71.5",
"@types/three": "^0.150.1",
"husky": "^8.0.3",
"lint-staged": "^13.2.1",
"prettier": "^2.8.7",
"rollup": "^3.20.3",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/react": "^18.2.56",
"@types/react-native": "^0.72.8",
"@types/three": "^0.161.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"rollup": "^4.12.0",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-typescript2": "^0.34.1",
"typescript": "^5.0.4"
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-typescript2": "^0.36.0",
"typescript": "^5.3.3"
},
"lint-staged": {
"*.{ts,tsx,md}": "prettier --write --no-semi"
Expand Down
Loading

0 comments on commit 7468e51

Please sign in to comment.