33
44import { Euler , EventDispatcher , MathUtils , Quaternion , Vector3 } from "three" ;
55
6+ const isIOS =
7+ navigator . userAgent . match ( / i P h o n e | i P a d | i P o d / i) ||
8+ ( / M a c i n t o s h / i. test ( navigator . userAgent ) &&
9+ navigator . maxTouchPoints != null &&
10+ navigator . maxTouchPoints > 1 ) ; // for iPad Safari
11+
612const _zee = new Vector3 ( 0 , 0 , 1 ) ;
713const _euler = new Euler ( ) ;
814const _q0 = new Quaternion ( ) ;
@@ -11,6 +17,11 @@ const _q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 aro
1117const _changeEvent = { type : "change" } ;
1218
1319class DeviceOrientationControls extends EventDispatcher {
20+ /**
21+ * Create an instance of DeviceOrientationControls.
22+ * @param {Object } object - the object to attach the controls to
23+ * (usually your Three.js camera)
24+ */
1425 constructor ( object ) {
1526 super ( ) ;
1627
@@ -30,10 +41,11 @@ class DeviceOrientationControls extends EventDispatcher {
3041
3142 this . enabled = true ;
3243
33- this . deviceOrientation = { } ;
44+ this . deviceOrientation = null ;
3445 this . screenOrientation = 0 ;
3546
3647 this . alphaOffset = 0 ; // radians
48+ this . initialOffset = null ; // used in fix provided in issue #466, iOS related
3749
3850 this . TWO_PI = 2 * Math . PI ;
3951 this . HALF_PI = 0.5 * Math . PI ;
@@ -44,8 +56,25 @@ class DeviceOrientationControls extends EventDispatcher {
4456
4557 this . smoothingFactor = 1 ;
4658
47- const onDeviceOrientationChangeEvent = function ( event ) {
48- scope . deviceOrientation = event ;
59+ const onDeviceOrientationChangeEvent = function ( {
60+ alpha,
61+ beta,
62+ gamma,
63+ webkitCompassHeading,
64+ } ) {
65+ if ( isIOS ) {
66+ const ccwNorthHeading = 360 - webkitCompassHeading ;
67+ scope . alphaOffset = MathUtils . degToRad ( ccwNorthHeading - alpha ) ;
68+ scope . deviceOrientation = { alpha, beta, gamma, webkitCompassHeading } ;
69+ } else {
70+ if ( alpha < 0 ) alpha += 360 ;
71+ scope . deviceOrientation = { alpha, beta, gamma } ;
72+ }
73+ window . dispatchEvent (
74+ new CustomEvent ( "camera-rotation-change" , {
75+ detail : { cameraRotation : object . rotation } ,
76+ } ) ,
77+ ) ;
4978 } ;
5079
5180 const onScreenOrientationChangeEvent = function ( ) {
@@ -70,6 +99,10 @@ class DeviceOrientationControls extends EventDispatcher {
7099 quaternion . multiply ( _q0 . setFromAxisAngle ( _zee , - orient ) ) ; // adjust for screen orientation
71100 } ;
72101
102+ /**
103+ * Update the device orientation controls.
104+ * Should be called from your three.js rendering/animation function.
105+ */
73106 this . connect = function ( ) {
74107 onScreenOrientationChangeEvent ( ) ; // run once on load
75108
@@ -123,9 +156,11 @@ class DeviceOrientationControls extends EventDispatcher {
123156 ) ;
124157
125158 scope . enabled = false ;
159+ scope . initialOffset = false ;
160+ scope . deviceOrientation = null ;
126161 } ;
127162
128- this . update = function ( ) {
163+ this . update = function ( { theta = 0 } = { theta : 0 } ) {
129164 if ( scope . enabled === false ) return ;
130165
131166 const device = scope . deviceOrientation ;
@@ -143,45 +178,63 @@ class DeviceOrientationControls extends EventDispatcher {
143178 ? MathUtils . degToRad ( scope . screenOrientation )
144179 : 0 ; // O
145180
146- if ( this . smoothingFactor < 1 ) {
147- if ( this . lastOrientation ) {
148- const k = this . smoothingFactor ;
149- alpha = this . _getSmoothedAngle (
181+ if ( isIOS ) {
182+ const currentQuaternion = new Quaternion ( ) ;
183+ setObjectQuaternion ( currentQuaternion , alpha , beta , gamma , orient ) ;
184+ // Extract the Euler angles from the quaternion and add the heading angle to the Y-axis rotation of the Euler angles
185+ // (If we replace only the alpha value of the quaternion without using Euler angles, the camera will rotate unexpectedly. This is because a quaternion does not represent rotation values individually but rather through a combination of rotation axes and weights.)
186+ const currentEuler = new Euler ( ) . setFromQuaternion (
187+ currentQuaternion ,
188+ "YXZ" ,
189+ ) ;
190+ // Replace the current alpha value of the Euler angles and reset the quaternion
191+ currentEuler . y = MathUtils . degToRad (
192+ 360 - device . webkitCompassHeading ,
193+ ) ;
194+ currentQuaternion . setFromEuler ( currentEuler ) ;
195+ scope . object . quaternion . copy ( currentQuaternion ) ;
196+ } else {
197+ if ( this . smoothingFactor < 1 ) {
198+ if ( this . lastOrientation ) {
199+ const k = this . smoothingFactor ;
200+ alpha = this . _getSmoothedAngle (
201+ alpha ,
202+ this . lastOrientation . alpha ,
203+ k ,
204+ ) ;
205+ beta = this . _getSmoothedAngle (
206+ beta + Math . PI ,
207+ this . lastOrientation . beta ,
208+ k ,
209+ ) ;
210+ gamma = this . _getSmoothedAngle (
211+ gamma + this . HALF_PI ,
212+ this . lastOrientation . gamma ,
213+ k ,
214+ Math . PI ,
215+ ) ;
216+ } else {
217+ beta += Math . PI ;
218+ gamma += this . HALF_PI ;
219+ }
220+
221+ this . lastOrientation = {
150222 alpha,
151- this . lastOrientation . alpha ,
152- k ,
153- ) ;
154- beta = this . _getSmoothedAngle (
155- beta + Math . PI ,
156- this . lastOrientation . beta ,
157- k ,
158- ) ;
159- gamma = this . _getSmoothedAngle (
160- gamma + this . HALF_PI ,
161- this . lastOrientation . gamma ,
162- k ,
163- Math . PI ,
164- ) ;
165- } else {
166- beta += Math . PI ;
167- gamma += this . HALF_PI ;
223+ beta,
224+ gamma,
225+ } ;
168226 }
169-
170- this . lastOrientation = {
171- alpha : alpha ,
172- beta : beta ,
173- gamma : gamma ,
174- } ;
227+ setObjectQuaternion (
228+ scope . object . quaternion ,
229+ alpha + theta ,
230+ this . smoothingFactor < 1 ? beta - Math . PI : beta ,
231+ this . smoothingFactor < 1 ? gamma - this . HALF_PI : gamma ,
232+ orient ,
233+ ) ;
175234 }
176235
177- setObjectQuaternion (
178- scope . object . quaternion ,
179- alpha ,
180- this . smoothingFactor < 1 ? beta - Math . PI : beta ,
181- this . smoothingFactor < 1 ? gamma - this . HALF_PI : gamma ,
182- orient ,
183- ) ;
184-
236+ // NB - NOT present in IOS fixed version issue #466
237+ // Is it needed?
185238 if ( 8 * ( 1 - lastQuaternion . dot ( scope . object . quaternion ) ) > EPS ) {
186239 lastQuaternion . copy ( scope . object . quaternion ) ;
187240 scope . dispatchEvent ( _changeEvent ) ;
@@ -218,10 +271,29 @@ class DeviceOrientationControls extends EventDispatcher {
218271 return newangle ;
219272 } ;
220273
274+ // Provided in fix on issue #466 - iOS related
275+ this . updateAlphaOffset = function ( ) {
276+ scope . initialOffset = false ;
277+ } ;
278+
221279 this . dispose = function ( ) {
222280 scope . disconnect ( ) ;
223281 } ;
224282
283+ // provided with fix on issue #466
284+ this . getAlpha = function ( ) {
285+ const { deviceOrientation : device } = scope ;
286+ return device && device . alpha
287+ ? MathUtils . degToRad ( device . alpha ) + scope . alphaOffset
288+ : 0 ;
289+ } ;
290+
291+ // provided with fix on issue #466
292+ this . getBeta = function ( ) {
293+ const { deviceOrientation : device } = scope ;
294+ return device && device . beta ? MathUtils . degToRad ( device . beta ) : 0 ;
295+ } ;
296+
225297 this . connect ( ) ;
226298 }
227299}
0 commit comments