├── LICENSE
├── README.md
├── assets
└── textures
│ └── dot.png
├── index.html
├── js
├── controls
│ └── OrbitControls.js
├── main.js
└── three.r87.min.js
└── screenshots
├── Spherical Bias 1.png
├── Spherical bias 0.5.png
├── Spherical.png
├── Uniform.png
└── Vec3.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 David Lochhead
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Random Position On The Surface Of A Sphere
2 | Sometimes you just need to postion things on the surface of a sphere. Here are a few methods I've used over the years.
3 |
4 | Blog post coming soon.
5 |
6 | getCartesianPositions(100000, 50) Note the vertical clustering. 😬 
7 | getSphericalPositions(100000, 50) Note the clustering at the poles. 😠 
8 | getSphericalPositionsWithBias(100000, 50, 1) Clustering at the equator! 😢 
9 | getSphericalPositionsWithBias(100000, 50, 0.5) 🙂 
10 | getUniformPositions(10000, 50) 
11 |
--------------------------------------------------------------------------------
/assets/textures/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defmech/random-position-on-the-surface-of-a-sphere/fe5cd9cadb72b023b5799ba63153a3b7a9297b35/assets/textures/dot.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Random Position On The Surface Of A Sphere
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/js/controls/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 |
9 | // This set of controls performs orbiting, dollying (zooming), and panning.
10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
11 | //
12 | // Orbit - left mouse / touch: one finger move
13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
14 | // Pan - right mouse, or arrow keys / touch: three finger swipe
15 |
16 | THREE.OrbitControls = function ( object, domElement ) {
17 |
18 | this.object = object;
19 |
20 | this.domElement = ( domElement !== undefined ) ? domElement : document;
21 |
22 | // Set to false to disable this control
23 | this.enabled = true;
24 |
25 | // "target" sets the location of focus, where the object orbits around
26 | this.target = new THREE.Vector3();
27 |
28 | // How far you can dolly in and out ( PerspectiveCamera only )
29 | this.minDistance = 0;
30 | this.maxDistance = Infinity;
31 |
32 | // How far you can zoom in and out ( OrthographicCamera only )
33 | this.minZoom = 0;
34 | this.maxZoom = Infinity;
35 |
36 | // How far you can orbit vertically, upper and lower limits.
37 | // Range is 0 to Math.PI radians.
38 | this.minPolarAngle = 0; // radians
39 | this.maxPolarAngle = Math.PI; // radians
40 |
41 | // How far you can orbit horizontally, upper and lower limits.
42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
43 | this.minAzimuthAngle = - Infinity; // radians
44 | this.maxAzimuthAngle = Infinity; // radians
45 |
46 | // Set to true to enable damping (inertia)
47 | // If damping is enabled, you must call controls.update() in your animation loop
48 | this.enableDamping = false;
49 | this.dampingFactor = 0.25;
50 |
51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
52 | // Set to false to disable zooming
53 | this.enableZoom = true;
54 | this.zoomSpeed = 1.0;
55 |
56 | // Set to false to disable rotating
57 | this.enableRotate = true;
58 | this.rotateSpeed = 1.0;
59 |
60 | // Set to false to disable panning
61 | this.enablePan = true;
62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
63 |
64 | // Set to true to automatically rotate around the target
65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
66 | this.autoRotate = false;
67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
68 |
69 | // Set to false to disable use of the keys
70 | this.enableKeys = true;
71 |
72 | // The four arrow keys
73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
74 |
75 | // Mouse buttons
76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
77 |
78 | // for reset
79 | this.target0 = this.target.clone();
80 | this.position0 = this.object.position.clone();
81 | this.zoom0 = this.object.zoom;
82 |
83 | //
84 | // public methods
85 | //
86 |
87 | this.getPolarAngle = function () {
88 |
89 | return spherical.phi;
90 |
91 | };
92 |
93 | this.getAzimuthalAngle = function () {
94 |
95 | return spherical.theta;
96 |
97 | };
98 |
99 | this.saveState = function () {
100 |
101 | scope.target0.copy( scope.target );
102 | scope.position0.copy( scope.object.position );
103 | scope.zoom0 = scope.object.zoom;
104 |
105 | };
106 |
107 | this.reset = function () {
108 |
109 | scope.target.copy( scope.target0 );
110 | scope.object.position.copy( scope.position0 );
111 | scope.object.zoom = scope.zoom0;
112 |
113 | scope.object.updateProjectionMatrix();
114 | scope.dispatchEvent( changeEvent );
115 |
116 | scope.update();
117 |
118 | state = STATE.NONE;
119 |
120 | };
121 |
122 | // this method is exposed, but perhaps it would be better if we can make it private...
123 | this.update = function () {
124 |
125 | var offset = new THREE.Vector3();
126 |
127 | // so camera.up is the orbit axis
128 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
129 | var quatInverse = quat.clone().inverse();
130 |
131 | var lastPosition = new THREE.Vector3();
132 | var lastQuaternion = new THREE.Quaternion();
133 |
134 | return function update() {
135 |
136 | var position = scope.object.position;
137 |
138 | offset.copy( position ).sub( scope.target );
139 |
140 | // rotate offset to "y-axis-is-up" space
141 | offset.applyQuaternion( quat );
142 |
143 | // angle from z-axis around y-axis
144 | spherical.setFromVector3( offset );
145 |
146 | if ( scope.autoRotate && state === STATE.NONE ) {
147 |
148 | rotateLeft( getAutoRotationAngle() );
149 |
150 | }
151 |
152 | spherical.theta += sphericalDelta.theta;
153 | spherical.phi += sphericalDelta.phi;
154 |
155 | // restrict theta to be between desired limits
156 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
157 |
158 | // restrict phi to be between desired limits
159 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
160 |
161 | spherical.makeSafe();
162 |
163 |
164 | spherical.radius *= scale;
165 |
166 | // restrict radius to be between desired limits
167 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
168 |
169 | // move target to panned location
170 | scope.target.add( panOffset );
171 |
172 | offset.setFromSpherical( spherical );
173 |
174 | // rotate offset back to "camera-up-vector-is-up" space
175 | offset.applyQuaternion( quatInverse );
176 |
177 | position.copy( scope.target ).add( offset );
178 |
179 | scope.object.lookAt( scope.target );
180 |
181 | if ( scope.enableDamping === true ) {
182 |
183 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
184 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
185 |
186 | } else {
187 |
188 | sphericalDelta.set( 0, 0, 0 );
189 |
190 | }
191 |
192 | scale = 1;
193 | panOffset.set( 0, 0, 0 );
194 |
195 | // update condition is:
196 | // min(camera displacement, camera rotation in radians)^2 > EPS
197 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
198 |
199 | if ( zoomChanged ||
200 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
201 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
202 |
203 | scope.dispatchEvent( changeEvent );
204 |
205 | lastPosition.copy( scope.object.position );
206 | lastQuaternion.copy( scope.object.quaternion );
207 | zoomChanged = false;
208 |
209 | return true;
210 |
211 | }
212 |
213 | return false;
214 |
215 | };
216 |
217 | }();
218 |
219 | this.dispose = function () {
220 |
221 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
222 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
223 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
224 |
225 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
226 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
227 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
228 |
229 | document.removeEventListener( 'mousemove', onMouseMove, false );
230 | document.removeEventListener( 'mouseup', onMouseUp, false );
231 |
232 | window.removeEventListener( 'keydown', onKeyDown, false );
233 |
234 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
235 |
236 | };
237 |
238 | //
239 | // internals
240 | //
241 |
242 | var scope = this;
243 |
244 | var changeEvent = { type: 'change' };
245 | var startEvent = { type: 'start' };
246 | var endEvent = { type: 'end' };
247 |
248 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
249 |
250 | var state = STATE.NONE;
251 |
252 | var EPS = 0.000001;
253 |
254 | // current position in spherical coordinates
255 | var spherical = new THREE.Spherical();
256 | var sphericalDelta = new THREE.Spherical();
257 |
258 | var scale = 1;
259 | var panOffset = new THREE.Vector3();
260 | var zoomChanged = false;
261 |
262 | var rotateStart = new THREE.Vector2();
263 | var rotateEnd = new THREE.Vector2();
264 | var rotateDelta = new THREE.Vector2();
265 |
266 | var panStart = new THREE.Vector2();
267 | var panEnd = new THREE.Vector2();
268 | var panDelta = new THREE.Vector2();
269 |
270 | var dollyStart = new THREE.Vector2();
271 | var dollyEnd = new THREE.Vector2();
272 | var dollyDelta = new THREE.Vector2();
273 |
274 | function getAutoRotationAngle() {
275 |
276 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
277 |
278 | }
279 |
280 | function getZoomScale() {
281 |
282 | return Math.pow( 0.95, scope.zoomSpeed );
283 |
284 | }
285 |
286 | function rotateLeft( angle ) {
287 |
288 | sphericalDelta.theta -= angle;
289 |
290 | }
291 |
292 | function rotateUp( angle ) {
293 |
294 | sphericalDelta.phi -= angle;
295 |
296 | }
297 |
298 | var panLeft = function () {
299 |
300 | var v = new THREE.Vector3();
301 |
302 | return function panLeft( distance, objectMatrix ) {
303 |
304 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
305 | v.multiplyScalar( - distance );
306 |
307 | panOffset.add( v );
308 |
309 | };
310 |
311 | }();
312 |
313 | var panUp = function () {
314 |
315 | var v = new THREE.Vector3();
316 |
317 | return function panUp( distance, objectMatrix ) {
318 |
319 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
320 | v.multiplyScalar( distance );
321 |
322 | panOffset.add( v );
323 |
324 | };
325 |
326 | }();
327 |
328 | // deltaX and deltaY are in pixels; right and down are positive
329 | var pan = function () {
330 |
331 | var offset = new THREE.Vector3();
332 |
333 | return function pan( deltaX, deltaY ) {
334 |
335 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
336 |
337 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
338 |
339 | // perspective
340 | var position = scope.object.position;
341 | offset.copy( position ).sub( scope.target );
342 | var targetDistance = offset.length();
343 |
344 | // half of the fov is center to top of screen
345 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
346 |
347 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
348 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
349 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
350 |
351 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
352 |
353 | // orthographic
354 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
355 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
356 |
357 | } else {
358 |
359 | // camera neither orthographic nor perspective
360 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
361 | scope.enablePan = false;
362 |
363 | }
364 |
365 | };
366 |
367 | }();
368 |
369 | function dollyIn( dollyScale ) {
370 |
371 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
372 |
373 | scale /= dollyScale;
374 |
375 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
376 |
377 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
378 | scope.object.updateProjectionMatrix();
379 | zoomChanged = true;
380 |
381 | } else {
382 |
383 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
384 | scope.enableZoom = false;
385 |
386 | }
387 |
388 | }
389 |
390 | function dollyOut( dollyScale ) {
391 |
392 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
393 |
394 | scale *= dollyScale;
395 |
396 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
397 |
398 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
399 | scope.object.updateProjectionMatrix();
400 | zoomChanged = true;
401 |
402 | } else {
403 |
404 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
405 | scope.enableZoom = false;
406 |
407 | }
408 |
409 | }
410 |
411 | //
412 | // event callbacks - update the object state
413 | //
414 |
415 | function handleMouseDownRotate( event ) {
416 |
417 | //console.log( 'handleMouseDownRotate' );
418 |
419 | rotateStart.set( event.clientX, event.clientY );
420 |
421 | }
422 |
423 | function handleMouseDownDolly( event ) {
424 |
425 | //console.log( 'handleMouseDownDolly' );
426 |
427 | dollyStart.set( event.clientX, event.clientY );
428 |
429 | }
430 |
431 | function handleMouseDownPan( event ) {
432 |
433 | //console.log( 'handleMouseDownPan' );
434 |
435 | panStart.set( event.clientX, event.clientY );
436 |
437 | }
438 |
439 | function handleMouseMoveRotate( event ) {
440 |
441 | //console.log( 'handleMouseMoveRotate' );
442 |
443 | rotateEnd.set( event.clientX, event.clientY );
444 | rotateDelta.subVectors( rotateEnd, rotateStart );
445 |
446 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
447 |
448 | // rotating across whole screen goes 360 degrees around
449 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
450 |
451 | // rotating up and down along whole screen attempts to go 360, but limited to 180
452 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
453 |
454 | rotateStart.copy( rotateEnd );
455 |
456 | scope.update();
457 |
458 | }
459 |
460 | function handleMouseMoveDolly( event ) {
461 |
462 | //console.log( 'handleMouseMoveDolly' );
463 |
464 | dollyEnd.set( event.clientX, event.clientY );
465 |
466 | dollyDelta.subVectors( dollyEnd, dollyStart );
467 |
468 | if ( dollyDelta.y > 0 ) {
469 |
470 | dollyIn( getZoomScale() );
471 |
472 | } else if ( dollyDelta.y < 0 ) {
473 |
474 | dollyOut( getZoomScale() );
475 |
476 | }
477 |
478 | dollyStart.copy( dollyEnd );
479 |
480 | scope.update();
481 |
482 | }
483 |
484 | function handleMouseMovePan( event ) {
485 |
486 | //console.log( 'handleMouseMovePan' );
487 |
488 | panEnd.set( event.clientX, event.clientY );
489 |
490 | panDelta.subVectors( panEnd, panStart );
491 |
492 | pan( panDelta.x, panDelta.y );
493 |
494 | panStart.copy( panEnd );
495 |
496 | scope.update();
497 |
498 | }
499 |
500 | function handleMouseUp( event ) {
501 |
502 | // console.log( 'handleMouseUp' );
503 |
504 | }
505 |
506 | function handleMouseWheel( event ) {
507 |
508 | // console.log( 'handleMouseWheel' );
509 |
510 | if ( event.deltaY < 0 ) {
511 |
512 | dollyOut( getZoomScale() );
513 |
514 | } else if ( event.deltaY > 0 ) {
515 |
516 | dollyIn( getZoomScale() );
517 |
518 | }
519 |
520 | scope.update();
521 |
522 | }
523 |
524 | function handleKeyDown( event ) {
525 |
526 | //console.log( 'handleKeyDown' );
527 |
528 | switch ( event.keyCode ) {
529 |
530 | case scope.keys.UP:
531 | pan( 0, scope.keyPanSpeed );
532 | scope.update();
533 | break;
534 |
535 | case scope.keys.BOTTOM:
536 | pan( 0, - scope.keyPanSpeed );
537 | scope.update();
538 | break;
539 |
540 | case scope.keys.LEFT:
541 | pan( scope.keyPanSpeed, 0 );
542 | scope.update();
543 | break;
544 |
545 | case scope.keys.RIGHT:
546 | pan( - scope.keyPanSpeed, 0 );
547 | scope.update();
548 | break;
549 |
550 | }
551 |
552 | }
553 |
554 | function handleTouchStartRotate( event ) {
555 |
556 | //console.log( 'handleTouchStartRotate' );
557 |
558 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
559 |
560 | }
561 |
562 | function handleTouchStartDolly( event ) {
563 |
564 | //console.log( 'handleTouchStartDolly' );
565 |
566 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
567 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
568 |
569 | var distance = Math.sqrt( dx * dx + dy * dy );
570 |
571 | dollyStart.set( 0, distance );
572 |
573 | }
574 |
575 | function handleTouchStartPan( event ) {
576 |
577 | //console.log( 'handleTouchStartPan' );
578 |
579 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
580 |
581 | }
582 |
583 | function handleTouchMoveRotate( event ) {
584 |
585 | //console.log( 'handleTouchMoveRotate' );
586 |
587 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
588 | rotateDelta.subVectors( rotateEnd, rotateStart );
589 |
590 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
591 |
592 | // rotating across whole screen goes 360 degrees around
593 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
594 |
595 | // rotating up and down along whole screen attempts to go 360, but limited to 180
596 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
597 |
598 | rotateStart.copy( rotateEnd );
599 |
600 | scope.update();
601 |
602 | }
603 |
604 | function handleTouchMoveDolly( event ) {
605 |
606 | //console.log( 'handleTouchMoveDolly' );
607 |
608 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
609 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
610 |
611 | var distance = Math.sqrt( dx * dx + dy * dy );
612 |
613 | dollyEnd.set( 0, distance );
614 |
615 | dollyDelta.subVectors( dollyEnd, dollyStart );
616 |
617 | if ( dollyDelta.y > 0 ) {
618 |
619 | dollyOut( getZoomScale() );
620 |
621 | } else if ( dollyDelta.y < 0 ) {
622 |
623 | dollyIn( getZoomScale() );
624 |
625 | }
626 |
627 | dollyStart.copy( dollyEnd );
628 |
629 | scope.update();
630 |
631 | }
632 |
633 | function handleTouchMovePan( event ) {
634 |
635 | //console.log( 'handleTouchMovePan' );
636 |
637 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
638 |
639 | panDelta.subVectors( panEnd, panStart );
640 |
641 | pan( panDelta.x, panDelta.y );
642 |
643 | panStart.copy( panEnd );
644 |
645 | scope.update();
646 |
647 | }
648 |
649 | function handleTouchEnd( event ) {
650 |
651 | //console.log( 'handleTouchEnd' );
652 |
653 | }
654 |
655 | //
656 | // event handlers - FSM: listen for events and reset state
657 | //
658 |
659 | function onMouseDown( event ) {
660 |
661 | if ( scope.enabled === false ) return;
662 |
663 | event.preventDefault();
664 |
665 | switch ( event.button ) {
666 |
667 | case scope.mouseButtons.ORBIT:
668 |
669 | if ( scope.enableRotate === false ) return;
670 |
671 | handleMouseDownRotate( event );
672 |
673 | state = STATE.ROTATE;
674 |
675 | break;
676 |
677 | case scope.mouseButtons.ZOOM:
678 |
679 | if ( scope.enableZoom === false ) return;
680 |
681 | handleMouseDownDolly( event );
682 |
683 | state = STATE.DOLLY;
684 |
685 | break;
686 |
687 | case scope.mouseButtons.PAN:
688 |
689 | if ( scope.enablePan === false ) return;
690 |
691 | handleMouseDownPan( event );
692 |
693 | state = STATE.PAN;
694 |
695 | break;
696 |
697 | }
698 |
699 | if ( state !== STATE.NONE ) {
700 |
701 | document.addEventListener( 'mousemove', onMouseMove, false );
702 | document.addEventListener( 'mouseup', onMouseUp, false );
703 |
704 | scope.dispatchEvent( startEvent );
705 |
706 | }
707 |
708 | }
709 |
710 | function onMouseMove( event ) {
711 |
712 | if ( scope.enabled === false ) return;
713 |
714 | event.preventDefault();
715 |
716 | switch ( state ) {
717 |
718 | case STATE.ROTATE:
719 |
720 | if ( scope.enableRotate === false ) return;
721 |
722 | handleMouseMoveRotate( event );
723 |
724 | break;
725 |
726 | case STATE.DOLLY:
727 |
728 | if ( scope.enableZoom === false ) return;
729 |
730 | handleMouseMoveDolly( event );
731 |
732 | break;
733 |
734 | case STATE.PAN:
735 |
736 | if ( scope.enablePan === false ) return;
737 |
738 | handleMouseMovePan( event );
739 |
740 | break;
741 |
742 | }
743 |
744 | }
745 |
746 | function onMouseUp( event ) {
747 |
748 | if ( scope.enabled === false ) return;
749 |
750 | handleMouseUp( event );
751 |
752 | document.removeEventListener( 'mousemove', onMouseMove, false );
753 | document.removeEventListener( 'mouseup', onMouseUp, false );
754 |
755 | scope.dispatchEvent( endEvent );
756 |
757 | state = STATE.NONE;
758 |
759 | }
760 |
761 | function onMouseWheel( event ) {
762 |
763 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
764 |
765 | event.preventDefault();
766 | event.stopPropagation();
767 |
768 | handleMouseWheel( event );
769 |
770 | scope.dispatchEvent( startEvent ); // not sure why these are here...
771 | scope.dispatchEvent( endEvent );
772 |
773 | }
774 |
775 | function onKeyDown( event ) {
776 |
777 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
778 |
779 | handleKeyDown( event );
780 |
781 | }
782 |
783 | function onTouchStart( event ) {
784 |
785 | if ( scope.enabled === false ) return;
786 |
787 | switch ( event.touches.length ) {
788 |
789 | case 1: // one-fingered touch: rotate
790 |
791 | if ( scope.enableRotate === false ) return;
792 |
793 | handleTouchStartRotate( event );
794 |
795 | state = STATE.TOUCH_ROTATE;
796 |
797 | break;
798 |
799 | case 2: // two-fingered touch: dolly
800 |
801 | if ( scope.enableZoom === false ) return;
802 |
803 | handleTouchStartDolly( event );
804 |
805 | state = STATE.TOUCH_DOLLY;
806 |
807 | break;
808 |
809 | case 3: // three-fingered touch: pan
810 |
811 | if ( scope.enablePan === false ) return;
812 |
813 | handleTouchStartPan( event );
814 |
815 | state = STATE.TOUCH_PAN;
816 |
817 | break;
818 |
819 | default:
820 |
821 | state = STATE.NONE;
822 |
823 | }
824 |
825 | if ( state !== STATE.NONE ) {
826 |
827 | scope.dispatchEvent( startEvent );
828 |
829 | }
830 |
831 | }
832 |
833 | function onTouchMove( event ) {
834 |
835 | if ( scope.enabled === false ) return;
836 |
837 | event.preventDefault();
838 | event.stopPropagation();
839 |
840 | switch ( event.touches.length ) {
841 |
842 | case 1: // one-fingered touch: rotate
843 |
844 | if ( scope.enableRotate === false ) return;
845 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
846 |
847 | handleTouchMoveRotate( event );
848 |
849 | break;
850 |
851 | case 2: // two-fingered touch: dolly
852 |
853 | if ( scope.enableZoom === false ) return;
854 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
855 |
856 | handleTouchMoveDolly( event );
857 |
858 | break;
859 |
860 | case 3: // three-fingered touch: pan
861 |
862 | if ( scope.enablePan === false ) return;
863 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
864 |
865 | handleTouchMovePan( event );
866 |
867 | break;
868 |
869 | default:
870 |
871 | state = STATE.NONE;
872 |
873 | }
874 |
875 | }
876 |
877 | function onTouchEnd( event ) {
878 |
879 | if ( scope.enabled === false ) return;
880 |
881 | handleTouchEnd( event );
882 |
883 | scope.dispatchEvent( endEvent );
884 |
885 | state = STATE.NONE;
886 |
887 | }
888 |
889 | function onContextMenu( event ) {
890 |
891 | if ( scope.enabled === false ) return;
892 |
893 | event.preventDefault();
894 |
895 | }
896 |
897 | //
898 |
899 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
900 |
901 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
902 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
903 |
904 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
905 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
906 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
907 |
908 | window.addEventListener( 'keydown', onKeyDown, false );
909 |
910 | // force an update at start
911 |
912 | this.update();
913 |
914 | };
915 |
916 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
917 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
918 |
919 | Object.defineProperties( THREE.OrbitControls.prototype, {
920 |
921 | center: {
922 |
923 | get: function () {
924 |
925 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
926 | return this.target;
927 |
928 | }
929 |
930 | },
931 |
932 | // backward compatibility
933 |
934 | noZoom: {
935 |
936 | get: function () {
937 |
938 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
939 | return ! this.enableZoom;
940 |
941 | },
942 |
943 | set: function ( value ) {
944 |
945 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
946 | this.enableZoom = ! value;
947 |
948 | }
949 |
950 | },
951 |
952 | noRotate: {
953 |
954 | get: function () {
955 |
956 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
957 | return ! this.enableRotate;
958 |
959 | },
960 |
961 | set: function ( value ) {
962 |
963 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
964 | this.enableRotate = ! value;
965 |
966 | }
967 |
968 | },
969 |
970 | noPan: {
971 |
972 | get: function () {
973 |
974 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
975 | return ! this.enablePan;
976 |
977 | },
978 |
979 | set: function ( value ) {
980 |
981 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
982 | this.enablePan = ! value;
983 |
984 | }
985 |
986 | },
987 |
988 | noKeys: {
989 |
990 | get: function () {
991 |
992 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
993 | return ! this.enableKeys;
994 |
995 | },
996 |
997 | set: function ( value ) {
998 |
999 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
1000 | this.enableKeys = ! value;
1001 |
1002 | }
1003 |
1004 | },
1005 |
1006 | staticMoving: {
1007 |
1008 | get: function () {
1009 |
1010 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
1011 | return ! this.enableDamping;
1012 |
1013 | },
1014 |
1015 | set: function ( value ) {
1016 |
1017 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
1018 | this.enableDamping = ! value;
1019 |
1020 | }
1021 |
1022 | },
1023 |
1024 | dynamicDampingFactor: {
1025 |
1026 | get: function () {
1027 |
1028 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1029 | return this.dampingFactor;
1030 |
1031 | },
1032 |
1033 | set: function ( value ) {
1034 |
1035 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1036 | this.dampingFactor = value;
1037 |
1038 | }
1039 |
1040 | }
1041 |
1042 | } );
1043 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | // Set up the scene and renderer
2 | var scene = new THREE.Scene();
3 | var camera = new THREE.PerspectiveCamera(
4 | 75,
5 | window.innerWidth / window.innerHeight,
6 | 0.1,
7 | 1000
8 | );
9 |
10 | var renderer = new THREE.WebGLRenderer();
11 | renderer.setSize(window.innerWidth, window.innerHeight);
12 | document.body.appendChild(renderer.domElement);
13 |
14 | camera.position.z = 150;
15 |
16 | var controls = new THREE.OrbitControls(camera, renderer.domElement);
17 |
18 | // End of scene setup
19 |
20 | // These are our setup variables.
21 |
22 | var numberOfPoints = 100000;
23 | var radiusOfSphere = 50;
24 |
25 | /*
26 |
27 | Comment out alternate lines to see the different positioning in effect
28 |
29 | */
30 |
31 | var positionData = getCartesianPositions(numberOfPoints, radiusOfSphere);
32 | // var positionData = getSphericalPositions(numberOfPoints, radiusOfSphere);
33 | // var positionData = getSphericalPositionsWithBias(numberOfPoints,radiusOfSphere);
34 | // var positionData = getSphericalPositionsWithBias(numberOfPoints,radiusOfSphere, 1);
35 | // var positionData = getSphericalPositionsWithBias(numberOfPoints,radiusOfSphere, 0.5);
36 | // var positionData = getUniformPositions(numberOfPoints, radiusOfSphere);
37 |
38 | // Convert out positionData into a float32Array for handing to the buffer geometry
39 | const positions = new Float32Array(positionData.length * 3);
40 | positionData.forEach(function(vert, vertIndex) {
41 | vert.toArray(positions, vertIndex * 3);
42 | }, this);
43 |
44 | var geometry = new THREE.BufferGeometry();
45 | geometry.addAttribute("position", new THREE.BufferAttribute(positions, 3));
46 |
47 | var texture = new THREE.TextureLoader().load("./assets/textures/dot.png");
48 |
49 | var texture = new THREE.TextureLoader().load("./assets/textures/dot.png");
50 |
51 | var materialOptions = {
52 | color: 0x888888,
53 | map: texture,
54 | transparent: true,
55 | depthTest: false,
56 | opacity: 0.25,
57 | blending: THREE.AdditiveBlending,
58 | };
59 |
60 | var material = new THREE.PointsMaterial(materialOptions);
61 |
62 | const points = new THREE.Points(geometry, material);
63 | scene.add(points);
64 |
65 | function animate() {
66 | requestAnimationFrame(animate);
67 |
68 | // animation here - lets rotate our dots
69 | points.rotation.y += 0.005;
70 |
71 | controls.update();
72 |
73 | renderer.render(scene, camera);
74 | }
75 |
76 | animate();
77 |
78 | /*
79 | The following are the position generating methods
80 | */
81 |
82 | function getCartesianPositions(howMany, radius) {
83 | // Create and array to store our vector3 point data
84 | var vectors = [];
85 |
86 | // Create new points using random x,y and z properties then setting vector length to radius
87 |
88 | for (var i = 0; i < howMany; i += 1) {
89 | var vec3 = new THREE.Vector3();
90 |
91 | vec3.x = THREE.Math.randFloatSpread(1);
92 | vec3.y = THREE.Math.randFloatSpread(1);
93 | vec3.z = THREE.Math.randFloatSpread(1);
94 |
95 | vec3.setLength(radius);
96 |
97 | vectors.push(vec3);
98 | }
99 |
100 | return vectors;
101 | }
102 |
103 | function getSphericalPositions(howMany, radius) {
104 | // Create and array to store our vector3 point data
105 | var vectors = [];
106 |
107 | // Create a spherical object
108 | var spherical = new THREE.Spherical();
109 |
110 | // Set radius of spherical
111 | spherical.radius = radius;
112 |
113 | // Create new points using random phi and theta properties of the spherical object
114 | for (var i = 0; i < howMany; i += 1) {
115 | spherical.phi = THREE.Math.randFloat(0, Math.PI); // Phi is between 0 - PI
116 | spherical.theta = THREE.Math.randFloat(0, Math.PI * 2); // Phi is between 0 - 2 PI
117 |
118 | var vec3 = new THREE.Vector3().setFromSpherical(spherical);
119 |
120 | vectors.push(vec3);
121 | }
122 |
123 | return vectors;
124 | }
125 |
126 | function getSphericalPositionsWithBias(howMany, radius, bias) {
127 | var vectors = [];
128 |
129 | var spherical = new THREE.Spherical();
130 |
131 | spherical.radius = radius;
132 |
133 | for (var i = 0; i < howMany; i += 1) {
134 | spherical.phi = getRndBias(0, Math.PI, Math.PI / 2, bias); // Phi is between 0 - PI
135 | spherical.theta = THREE.Math.randFloat(0, Math.PI * 2); // Theta is between 0 - 2 PI
136 |
137 | var vec3 = new THREE.Vector3().setFromSpherical(spherical);
138 |
139 | vectors.push(vec3);
140 | }
141 |
142 | return vectors;
143 | }
144 |
145 | function getRndBias(min, max, bias, influence) {
146 | const rnd = Math.random() * (max - min) + min; // random in range
147 | const mix = Math.random() * influence; // random mixer
148 | return rnd * (1 - mix) + bias * mix; // mix full range and bias
149 | }
150 |
151 | // This method distributes the particles uniformly accross the surface of a sphere
152 |
153 | function getUniformPositions(howMany, radius) {
154 | var vectors = [];
155 |
156 | var inc = Math.PI * (3 - Math.sqrt(5));
157 |
158 | var x = 0;
159 | var y = 0;
160 | var z = 0;
161 | var r = 0;
162 | var phi = 0;
163 |
164 | for (var k = 0; k < howMany; k++) {
165 | var off = 2 / howMany;
166 | var vec3 = new THREE.Vector3();
167 |
168 | y = k * off - 1 + off / 2;
169 | r = Math.sqrt(1 - y * y);
170 |
171 | phi = k * inc;
172 |
173 | x = Math.cos(phi) * r;
174 |
175 | z = (0, Math.sin(phi) * r);
176 |
177 | x *= radius;
178 | y *= radius;
179 | z *= radius;
180 |
181 | vec3.x = x;
182 | vec3.y = y;
183 | vec3.z = z;
184 |
185 | vectors.push(vec3);
186 | }
187 |
188 | return vectors;
189 | }
190 |
--------------------------------------------------------------------------------
/screenshots/Spherical Bias 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defmech/random-position-on-the-surface-of-a-sphere/fe5cd9cadb72b023b5799ba63153a3b7a9297b35/screenshots/Spherical Bias 1.png
--------------------------------------------------------------------------------
/screenshots/Spherical bias 0.5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defmech/random-position-on-the-surface-of-a-sphere/fe5cd9cadb72b023b5799ba63153a3b7a9297b35/screenshots/Spherical bias 0.5.png
--------------------------------------------------------------------------------
/screenshots/Spherical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defmech/random-position-on-the-surface-of-a-sphere/fe5cd9cadb72b023b5799ba63153a3b7a9297b35/screenshots/Spherical.png
--------------------------------------------------------------------------------
/screenshots/Uniform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defmech/random-position-on-the-surface-of-a-sphere/fe5cd9cadb72b023b5799ba63153a3b7a9297b35/screenshots/Uniform.png
--------------------------------------------------------------------------------
/screenshots/Vec3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defmech/random-position-on-the-surface-of-a-sphere/fe5cd9cadb72b023b5799ba63153a3b7a9297b35/screenshots/Vec3.png
--------------------------------------------------------------------------------