├── src2
├── ManipulatorMode.js
├── state
│ ├── ScaleState.js
│ ├── PositionState.js
│ ├── ManipulatorState.js
│ └── RotationState.js
├── Manipulator3D.js
├── MouseEvents.js
├── ManipulatorActions.js
└── Tracer.js
├── test
├── _lib
│ ├── Starter.css
│ ├── FacedCube.js
│ ├── Util.js
│ ├── DynamicMesh.js
│ ├── Starter.js
│ └── DynLineMesh.js
├── test_no_attachment.html
├── test_dist.html
├── test.html
├── states.html
├── ManipulatorDebugger.js
├── new2.html
├── new.html
└── annotate_box.html
├── src3
├── Structs.js
├── lib
│ ├── EventDispatcher.js
│ ├── Func.js
│ ├── StateProxy.js
│ ├── MouseEvents.js
│ ├── transform.js
│ └── RayIntersection.js
├── mod
│ ├── Plane.js
│ ├── Translation.js
│ ├── Scale.js
│ └── Rotation.js
├── render
│ ├── FloorCube.js
│ └── Mesh3JS.js
├── DragAction.js
├── Manipulator3D copy.js
├── Actions.js
└── Manipulator3D.js
├── src
├── index.js
├── RayIntersection.js
├── ManipulatorMesh.js
└── Maths.js
├── annotatebox
├── Structs.js
├── lib
│ ├── EventDispatcher.js
│ ├── Func.js
│ ├── StateProxy.js
│ └── MouseEvents.js
├── mod
│ ├── Translation.js
│ ├── Rotation.js
│ └── Faces.js
├── DragAction.js
├── Actions.js
├── AnnotateBox.js
└── render
│ ├── Mesh3js.js
│ └── ScaleCube.js
├── notes.txt
├── LICENSE
├── package.json
├── .gitignore
├── vite.config.js
└── README.md
/src2/ManipulatorMode.js:
--------------------------------------------------------------------------------
1 | const ManipulatorMode = Object.freeze({
2 | None : -1,
3 | Translate : 0,
4 | Rotate : 1,
5 | Scale : 2,
6 | });
7 |
8 | export default ManipulatorMode;
--------------------------------------------------------------------------------
/test/_lib/Starter.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | background-color: #000;
4 | color: #fff;
5 | font-family: monospace;
6 | font-size: 12px;
7 | overscroll-behavior: none;
8 | }
9 | canvas{ display: block; }
--------------------------------------------------------------------------------
/src2/state/ScaleState.js:
--------------------------------------------------------------------------------
1 | export default class ScaleState{
2 | value = [1,1,1];
3 | set( v ){
4 | this.value[ 0 ] = v[ 0 ];
5 | this.value[ 1 ] = v[ 1 ];
6 | this.value[ 2 ] = v[ 2 ];
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src3/Structs.js:
--------------------------------------------------------------------------------
1 | const Modes = Object.freeze({
2 | None : -1,
3 | Translate : 0,
4 | Rotate : 1,
5 | Scale : 2,
6 | Plane : 3,
7 | });
8 |
9 | const Axes = Object.freeze({
10 | None : -1,
11 | X : 0,
12 | Y : 1,
13 | Z : 2,
14 | All : 3,
15 | })
16 |
17 | export { Modes, Axes };
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { ManipulatorData, ManipulatorMode } from './ManipulatorData.js';
2 | import { ManipulatorMesh } from './ManipulatorMesh.js';
3 | import { Manipulator3D } from './Manipulator3D.js';
4 |
5 | export {
6 | Manipulator3D,
7 | ManipulatorData,
8 | ManipulatorMode,
9 | ManipulatorMesh,
10 | };
--------------------------------------------------------------------------------
/annotatebox/Structs.js:
--------------------------------------------------------------------------------
1 | const Modes = Object.freeze({
2 | None : -1,
3 | Translate : 0,
4 | Rotate : 1,
5 | Scale : 2,
6 | });
7 |
8 | const Axes = Object.freeze({
9 | None : -1,
10 | X : 0,
11 | Y : 1,
12 | Z : 2,
13 | XN : 3,
14 | YN : 4,
15 | ZN : 5,
16 | })
17 |
18 | export { Modes, Axes };
--------------------------------------------------------------------------------
/src3/lib/EventDispatcher.js:
--------------------------------------------------------------------------------
1 | export default function EventDispatcher(){
2 | const evtTarget = new EventTarget();
3 | return {
4 | on( evtName, fn ){ evtTarget.addEventListener( evtName, fn ); return this; },
5 | off( evtName, fn ){ evtTarget.removeEventListener( evtName, fn ); return this; },
6 | once( evtName, fn ){ evtTarget.addEventListener( evtName, fn, { once:true } ); return this; },
7 | emit( evtName, data ){
8 | evtTarget.dispatchEvent( new CustomEvent( evtName, { detail:data, bubbles: false, cancelable:true, composed:false } ) );
9 | return this;
10 | },
11 | };
12 | }
--------------------------------------------------------------------------------
/annotatebox/lib/EventDispatcher.js:
--------------------------------------------------------------------------------
1 | export default function EventDispatcher(){
2 | const evtTarget = new EventTarget();
3 | return {
4 | on( evtName, fn ){ evtTarget.addEventListener( evtName, fn ); return this; },
5 | off( evtName, fn ){ evtTarget.removeEventListener( evtName, fn ); return this; },
6 | once( evtName, fn ){ evtTarget.addEventListener( evtName, fn, { once:true } ); return this; },
7 | emit( evtName, data ){
8 | evtTarget.dispatchEvent( new CustomEvent( evtName, { detail:data, bubbles: false, cancelable:true, composed:false } ) );
9 | return this;
10 | },
11 | };
12 | }
--------------------------------------------------------------------------------
/src3/lib/Func.js:
--------------------------------------------------------------------------------
1 | export function debounce( fn, delay ){
2 | let id = null;
3 | return ()=>{ //...args
4 | if( id ) clearTimeout( id );
5 | id = setTimeout( ()=>fn.apply( null, arguments ), delay ); //id = setTimeout( ()=>fn( ...args ), delay );
6 | };
7 | }
8 |
9 | export function memorize( fn ){
10 | const cache = {};
11 | return ( ...args )=>{
12 | const key = args.toString();
13 | if( key in cache ) return cache[ key ];
14 |
15 | const result = fn( ...args );
16 | cache[ key ] = result;
17 | return result;
18 | }
19 | }
20 |
21 | export function throttle( fn, delay ){
22 | let lastTime = 0;
23 | return ( ...args )=>{
24 | const now = new Date().getTime();
25 | if( now - lastTime < delay ) return;
26 | lastTime = now;
27 | fn( ...args );
28 | }
29 | }
--------------------------------------------------------------------------------
/annotatebox/lib/Func.js:
--------------------------------------------------------------------------------
1 | export function debounce( fn, delay ){
2 | let id = null;
3 | return ()=>{ //...args
4 | if( id ) clearTimeout( id );
5 | id = setTimeout( ()=>fn.apply( null, arguments ), delay ); //id = setTimeout( ()=>fn( ...args ), delay );
6 | };
7 | }
8 |
9 | export function memorize( fn ){
10 | const cache = {};
11 | return ( ...args )=>{
12 | const key = args.toString();
13 | if( key in cache ) return cache[ key ];
14 |
15 | const result = fn( ...args );
16 | cache[ key ] = result;
17 | return result;
18 | }
19 | }
20 |
21 | export function throttle( fn, delay ){
22 | let lastTime = 0;
23 | return ( ...args )=>{
24 | const now = new Date().getTime();
25 | if( now - lastTime < delay ) return;
26 | lastTime = now;
27 | fn( ...args );
28 | }
29 | }
--------------------------------------------------------------------------------
/notes.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://codesandbox.io/s/object-gizmo-controls-om2ff8?file=/src/pivot/AxisRotator.js:3237-3260
4 | Nice widget, Very interested in the pivot rotation
5 |
6 | const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current
7 | ray.copy(e.ray)
8 | ray.intersectPlane(plane, intersection)
9 | ray.direction.negate()
10 | ray.intersectPlane(plane, intersection)
11 | let angle = calculateAngle(clickPoint, intersection, origin, e1, e2)
12 | let degrees = toDegrees(angle)
13 |
14 | if (e.shiftKey) {
15 | degrees = Math.round(degrees / 10) * 10
16 | angle = toRadians(degrees)
17 | }
18 |
19 | divRef.current.innerText = degrees.toFixed(0) + 'º'
20 |
21 | rotMatrix.makeRotationAxis(normal, angle)
22 | posNew.copy(origin).applyMatrix4(rotMatrix).sub(origin).negate()
23 | rotMatrix.setPosition(posNew)
24 | onDrag(rotMatrix)
--------------------------------------------------------------------------------
/test/_lib/FacedCube.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | export default function facedCube( pos=null, scl=null ){
3 | const geo = new THREE.BoxGeometry( 1, 1, 1 );
4 | const mat = [
5 | //new THREE.MeshBasicMaterial( { color: 0x00ff00 } ), // Left
6 | new THREE.MeshBasicMaterial( { color: 0xeeeeee } ), // Left
7 | new THREE.MeshBasicMaterial( { color: 0x777777 } ), // Right
8 | // new THREE.MeshBasicMaterial( { color: 0x0000ff } ), // Top
9 | new THREE.MeshBasicMaterial( { color: 0xaaaaaa } ), // Top
10 | new THREE.MeshBasicMaterial( { color: 0x222222 } ), // Bottom
11 | // new THREE.MeshBasicMaterial( { color: 0xff0000 } ), // Forward
12 | new THREE.MeshBasicMaterial( { color: 0x999999 } ), // Forward
13 | new THREE.MeshBasicMaterial( { color: 0xffffff } ), // Back
14 | ];
15 |
16 | const mesh = new THREE.Mesh( geo, mat );
17 |
18 | if( pos ) mesh.position.fromArray( pos );
19 | if( scl != null ) mesh.scale.set( scl, scl, scl );
20 |
21 | return mesh;
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sketchpunk Labs
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "manipulator3d",
3 | "version" : "0.0.8",
4 | "author" : "Pedro Sousa ( Vor @ SketchPunk Labs )",
5 | "description" : "3D Manipulator Gizmo for Threejs",
6 | "keywords" : [ "threejs", "gizmo", "ui", "3d" ],
7 | "license" : "MIT",
8 | "homepage" : "https://github.com/sketchpunklabs/manipulator3d#readme",
9 | "repository" : { "type": "git", "url": "git+https://github.com/sketchpunklabs/manipulator3d.git" },
10 | "bugs" : { "url": "https://github.com/sketchpunklabs/manipulator3d/issues" },
11 | "files" : [ "dist" ],
12 | "main" : "./dist/manipulator3d.cjs.js",
13 | "module" : "./dist/manipulator3d.es.js",
14 | "exports": {
15 | ".": {
16 | "import" : "./dist/manipulator3d.es.js",
17 | "require" : "./dist/manipulator3d.cjs.js"
18 | }
19 | },
20 | "scripts": {
21 | "dev" : "vite",
22 | "build" : "vite build",
23 | "publish" : "npm publish --access public"
24 | },
25 |
26 | "peerDependencies": {
27 | "three" : "^0.138.2"
28 | },
29 |
30 | "devDependencies": {
31 | "vite": "^2.8.6"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src3/mod/Plane.js:
--------------------------------------------------------------------------------
1 | import { vec3_add, vec3_sub } from '../lib/Maths.js';
2 | import {
3 | // Ray,
4 | intersectPlane,
5 | intersectTri,
6 | // intersectSphere,
7 | //nearPoint,
8 | //NearSegmentResult,
9 | //nearSegment,
10 | } from '../../src/RayIntersection.js';
11 | import { vec3_copy, vec3_scaleAndAdd } from '../lib/Maths.js';
12 |
13 |
14 | export default class Plane{
15 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
16 | const a = [ 0, 0, 0 ];
17 | const b = [ 0, 0, 0 ];
18 | const c = [ 0, 0, 0 ];
19 | const x = 0.15 * state.cameraScale;
20 | const y = 0.65 * state.cameraScale;
21 | const origin = state.position;
22 |
23 | for( let i=0; i < 3; i++ ){
24 | let ii = ( i + 1 ) % 3;
25 | let iii = ( i + 2 ) % 3;
26 |
27 | vec3_scaleAndAdd( a, origin, axes[ ii ], x );
28 | vec3_scaleAndAdd( a, a, axes[ iii ], x );
29 |
30 | vec3_scaleAndAdd( b, a, axes[ ii ], y );
31 | vec3_scaleAndAdd( c, a, axes[ iii ], y );
32 |
33 | if( intersectTri( ray, b, a, c, hitPos, false ) ){
34 | return i;
35 | }
36 | }
37 |
38 | return -1;
39 | }
40 |
41 | static rayDrag( ray, origin, axes, iAxis, initHit ){
42 | const t = intersectPlane( ray, origin, axes[ iAxis ] );
43 | if( t === null ) return null;
44 |
45 | const offset = vec3_sub( [0,0,0], origin, initHit );
46 | const pos = ray.posAt( t );
47 |
48 | return vec3_add( pos, pos, offset );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/annotatebox/mod/Translation.js:
--------------------------------------------------------------------------------
1 | import { vec3_add, vec3_sub } from '../lib/Maths.js';
2 | import {
3 | // Ray,
4 | // intersectPlane,
5 | // intersectTri,
6 | // intersectSphere,
7 | // nearPoint,
8 | NearSegmentResult,
9 | nearSegment,
10 | } from '../lib/RayIntersection.js';
11 | import { vec3_copy, vec3_scaleAndAdd } from '../lib/Maths.js';
12 |
13 |
14 | export default class Translation{
15 | static minRayDistance = 0.08;
16 |
17 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
18 | const segResult = new NearSegmentResult();
19 | const a = [ 0, 0, 0 ];
20 | const b = [ 0, 0, 0 ];
21 | const p = state.position;
22 |
23 | let minDist = Infinity;
24 | let minAxis = -1;
25 |
26 | for( let i=0; i < 3; i++ ){
27 | vec3_scaleAndAdd( a, p, axes[i], state.axesLengths[i] );
28 | vec3_scaleAndAdd( b, a, axes[i], 1.0 );
29 |
30 | if( nearSegment( ray, a, b, segResult ) ){
31 | if( segResult.distance <= this.minRayDistance && segResult.distance < minDist ){
32 | minAxis = i;
33 | minDist = segResult.distance;
34 | vec3_copy( hitPos, segResult.segPosition );
35 | }
36 | }
37 | }
38 |
39 | return minAxis;
40 | }
41 |
42 | static rayDrag( ray, origin, axis, initHit ){
43 | const a = vec3_scaleAndAdd( [0,0,0], origin, axis, 1000 );
44 | const b = vec3_scaleAndAdd( [0,0,0], origin, axis, -1000 );
45 | const segResult = new NearSegmentResult();
46 |
47 | if( nearSegment( ray, a, b, segResult ) ){
48 | const offset = vec3_sub( [0,0,0], origin, initHit );
49 | vec3_add( offset, offset, segResult.segPosition );
50 | return offset;
51 | }
52 |
53 | return null;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src3/mod/Translation.js:
--------------------------------------------------------------------------------
1 | import { vec3_add, vec3_sub } from '../lib/Maths.js';
2 | import {
3 | // Ray,
4 | // intersectPlane,
5 | // intersectTri,
6 | // intersectSphere,
7 | // nearPoint,
8 | NearSegmentResult,
9 | nearSegment,
10 | } from '../../src/RayIntersection.js';
11 | import { vec3_copy, vec3_scaleAndAdd } from '../lib/Maths.js';
12 |
13 |
14 | export default class Translation{
15 | static minRayDistance = 0.08;
16 |
17 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
18 | const minRange = this.minRayDistance * state.cameraScale;
19 | const segResult = new NearSegmentResult();
20 | const a = [ 0, 0, 0 ];
21 | const b = [ 0, 0, 0 ];
22 | const s = state.axisLength * state.cameraScale;
23 | const p = state.position;
24 |
25 | let minDist = Infinity;
26 | let minAxis = -1;
27 |
28 | for( let i=0; i < 3; i++ ){
29 | vec3_scaleAndAdd( a, p, axes[i], -s );
30 | vec3_scaleAndAdd( b, p, axes[i], s );
31 |
32 | if( nearSegment( ray, a, b, segResult ) ){
33 | if( segResult.distance <= minRange && segResult.distance < minDist ){
34 | minAxis = i;
35 | minDist = segResult.distance;
36 | vec3_copy( hitPos, segResult.segPosition );
37 | }
38 | }
39 | }
40 |
41 | return minAxis;
42 | }
43 |
44 | static rayDrag( ray, origin, axis, initHit ){
45 | const a = vec3_scaleAndAdd( [0,0,0], origin, axis, 1000 );
46 | const b = vec3_scaleAndAdd( [0,0,0], origin, axis, -1000 );
47 | const segResult = new NearSegmentResult();
48 |
49 | if( nearSegment( ray, a, b, segResult ) ){
50 | const offset = vec3_sub( [0,0,0], origin, initHit );
51 | vec3_add( offset, offset, segResult.segPosition );
52 | return offset;
53 | }
54 |
55 | return null;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src3/lib/StateProxy.js:
--------------------------------------------------------------------------------
1 | export default class StateProxy{
2 | // #region MAIN
3 | static new( data={} ){ return new Proxy( data, new StateProxy( data ) ); }
4 |
5 | constructor( data ){
6 | this._data = data;
7 | this._events = new EventTarget();
8 | }
9 | // #endregion
10 |
11 | // #region METHODS
12 | update( struct, emitChange=false ){
13 | Object.assign( this._data, struct );
14 | if( emitChange ) this.emit( 'change', null );
15 | return this;
16 | }
17 | // #endregion
18 |
19 | // #region PROXY TRAPS
20 | get( target, prop, receiver ){
21 | // console.log( "GET", "target", target, "prop", prop, "rec", receiver );
22 | if( prop === '$' ) return this;
23 | return Reflect.get( target, prop, receiver ); //target[ prop ];
24 | }
25 |
26 | set( target, prop, value ){
27 | // console.log( "SET", "target", target, "prop", prop, "value", value, 'prev', Reflect.get( target, prop ) );
28 | if( prop === '$' ) return false;
29 |
30 | // Only update the state if the value is different
31 | if( Reflect.get( target, prop ) !== value ){
32 | Reflect.set( target, prop, value ); // Save data to Object
33 | this.emit( prop + 'Change', value ); // Emit event that property changed
34 | this.emit( 'change', { prop, value } ); // Emit event that any property changed
35 | }
36 | return true;
37 | }
38 | // #endregion
39 |
40 | // #region EVENTS
41 | on( evtName, fn ){ this._events.addEventListener( evtName, fn ); return this; }
42 | off( evtName, fn){ this._events.removeEventListener( evtName, fn ); return this; }
43 | once( evtName, fn ){ this._events.addEventListener( evtName, fn, { once:true } ); return this; }
44 | emit( evtName, data ){
45 | this._events.dispatchEvent( new CustomEvent( evtName, { detail:data, bubbles: false, cancelable:true, composed:false } ) );
46 | return this;
47 | }
48 | // #endregion
49 | }
--------------------------------------------------------------------------------
/annotatebox/lib/StateProxy.js:
--------------------------------------------------------------------------------
1 | export default class StateProxy{
2 | // #region MAIN
3 | static new( data={} ){ return new Proxy( data, new StateProxy( data ) ); }
4 |
5 | constructor( data ){
6 | this._data = data;
7 | this._events = new EventTarget();
8 | }
9 | // #endregion
10 |
11 | // #region METHODS
12 | update( struct, emitChange=false ){
13 | Object.assign( this._data, struct );
14 | if( emitChange ) this.emit( 'change', null );
15 | return this;
16 | }
17 | // #endregion
18 |
19 | // #region PROXY TRAPS
20 | get( target, prop, receiver ){
21 | // console.log( "GET", "target", target, "prop", prop, "rec", receiver );
22 | if( prop === '$' ) return this;
23 | return Reflect.get( target, prop, receiver ); //target[ prop ];
24 | }
25 |
26 | set( target, prop, value ){
27 | // console.log( "SET", "target", target, "prop", prop, "value", value, 'prev', Reflect.get( target, prop ) );
28 | if( prop === '$' ) return false;
29 |
30 | // Only update the state if the value is different
31 | if( Reflect.get( target, prop ) !== value ){
32 | Reflect.set( target, prop, value ); // Save data to Object
33 | this.emit( prop + 'Change', value ); // Emit event that property changed
34 | this.emit( 'change', { prop, value } ); // Emit event that any property changed
35 | }
36 | return true;
37 | }
38 | // #endregion
39 |
40 | // #region EVENTS
41 | on( evtName, fn ){ this._events.addEventListener( evtName, fn ); return this; }
42 | off( evtName, fn){ this._events.removeEventListener( evtName, fn ); return this; }
43 | once( evtName, fn ){ this._events.addEventListener( evtName, fn, { once:true } ); return this; }
44 | emit( evtName, data ){
45 | this._events.dispatchEvent( new CustomEvent( evtName, { detail:data, bubbles: false, cancelable:true, composed:false } ) );
46 | return this;
47 | }
48 | // #endregion
49 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 |
84 | # Gatsby files
85 | .cache/
86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
87 | # https://nextjs.org/blog/next-9-1#public-directory-support
88 | # public
89 |
90 | # vuepress build output
91 | .vuepress/dist
92 |
93 | # Serverless directories
94 | .serverless/
95 |
96 | # FuseBox cache
97 | .fusebox/
98 |
99 | # DynamoDB Local files
100 | .dynamodb/
101 |
102 | # TernJS port file
103 | .tern-port
104 |
--------------------------------------------------------------------------------
/src3/mod/Scale.js:
--------------------------------------------------------------------------------
1 | import { vec3_add, vec3_sub, vec3_dot, vec3_norm } from '../lib/Maths.js';
2 | import {
3 | // Ray,
4 | // intersectPlane,
5 | // intersectTri,
6 | // intersectSphere,
7 | nearPoint,
8 | NearSegmentResult,
9 | nearSegment,
10 | } from '../../src/RayIntersection.js';
11 | import { vec3_copy, vec3_scaleAndAdd, vec3_len } from '../lib/Maths.js';
12 |
13 |
14 | export default class Scale{
15 | static minRayDistance = 0.08;
16 |
17 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
18 | const minRange = this.minRayDistance * state.cameraScale;
19 | const a = [ 0, 0, 0 ];
20 | const b = [ 0, 0, 0 ];
21 | const s = state.pntRadius * state.cameraScale;
22 | const origin = state.position;
23 |
24 | if( nearPoint( ray, origin, minRange ) !== null ) return 3;
25 |
26 | for( let i=0; i < 3; i++ ){
27 | vec3_scaleAndAdd( a, origin, axes[i], -s );
28 | if( nearPoint( ray, a, minRange ) !== null ){
29 | vec3_copy( hitPos, a );
30 | return i;
31 | }
32 |
33 | vec3_scaleAndAdd( b, origin, axes[i], s );
34 | if( nearPoint( ray, b, minRange ) !== null ){
35 | vec3_copy( hitPos, b );
36 | return i;
37 | }
38 | }
39 |
40 | return -1;
41 | }
42 |
43 | static rayDrag( ray, origin, axes, iAxis, initHit ){
44 | if( iAxis === 3 ) return null;
45 |
46 | const a = vec3_scaleAndAdd( [0,0,0], origin, axes[ iAxis ], 1000 );
47 | const b = vec3_scaleAndAdd( [0,0,0], origin, axes[ iAxis ], -1000 );
48 | const result = new NearSegmentResult();
49 |
50 | if( nearSegment( ray, a, b, result ) ){
51 | const dir = vec3_sub( [0,0,0], result.segPosition, initHit );
52 | const len = vec3_len( dir );
53 | const sign = Math.sign( vec3_dot( dir, axes[ iAxis ] ) );
54 |
55 | const out = [0,0,0];
56 | out[ iAxis ] = len * sign;
57 |
58 | return out;
59 | }
60 |
61 | return null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "fs";
3 |
4 | const ignorePaths = [ ".git", "node_modules", "dist", "site" ];
5 |
6 | function getHtmlPaths( dirPath = __dirname, htmlPaths = {} ){
7 | const files = fs.readdirSync(dirPath);
8 |
9 | for( const file of files ){
10 | if( ignorePaths.includes( file ) ) continue;
11 |
12 | const absPath = path.join( dirPath, file );
13 |
14 | if( fs.statSync(absPath).isDirectory() ){
15 | htmlPaths = getHtmlPaths( absPath, htmlPaths );
16 |
17 | }else if( path.extname(file) === ".html" ){
18 | const relPath = path.relative( __dirname, absPath );
19 | htmlPaths[relPath] = absPath;
20 | }
21 | }
22 |
23 | return htmlPaths;
24 | }
25 |
26 | export default ( { command, mode } ) => {
27 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28 | if( mode === "site" || command === "serve" ){
29 |
30 | const repo = process.env.GITHUB_REPOSITORY;
31 | const base = ( repo )? `/${repo.split("/")[1]}/` : '/';
32 |
33 | return {
34 | base,
35 | build : {
36 | outDir : path.resolve( __dirname, "site" ),
37 | minify : false,
38 | rollupOptions : { input: getHtmlPaths() },
39 | },
40 | publicDir : path.join( __dirname, "test", "public" ),
41 | server : {
42 | port : 3012,
43 | open : '/test/test.html',
44 | },
45 | };
46 |
47 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48 | } else {
49 | return {
50 | build: {
51 | minify : false,
52 |
53 | lib : {
54 | entry : path.resolve( __dirname, "src/index.js" ),
55 | name : "Manipulator3D",
56 | formats : [ "es", "cjs" ],
57 | },
58 |
59 | rollupOptions : {
60 | external : [ "three", /^three\// ],
61 | }
62 | }
63 | };
64 | }
65 | };
--------------------------------------------------------------------------------
/test/test_no_attachment.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
62 |
--------------------------------------------------------------------------------
/test/_lib/Util.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | export default class Util{
4 | static facedCube( pos=null, scl=null, size=[1,1,1] ){
5 | const geo = new THREE.BoxGeometry( size[0], size[1], size[2] );
6 | const mat = [
7 | new THREE.MeshBasicMaterial( { color: 0x00ff00 } ), // Left
8 | new THREE.MeshBasicMaterial( { color: 0x777777 } ), // Right
9 | new THREE.MeshBasicMaterial( { color: 0x0000ff } ), // Top
10 | new THREE.MeshBasicMaterial( { color: 0x222222 } ), // Bottom
11 | new THREE.MeshBasicMaterial( { color: 0xff0000 } ), // Forward
12 | new THREE.MeshBasicMaterial( { color: 0xffffff } ), // Back
13 | ];
14 |
15 | const mesh = new THREE.Mesh( geo, mat );
16 |
17 | if( pos ) mesh.position.fromArray( pos );
18 | if( scl != null ) mesh.scale.set( scl, scl, scl );
19 |
20 | return mesh;
21 | }
22 |
23 | static mesh( verts, idx=null, norm=null, uv=null, mat=null, wireFrame=false ){
24 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25 | const bGeo = new THREE.BufferGeometry();
26 | bGeo.setAttribute( "position", new THREE.BufferAttribute( new Float32Array( verts ), 3 ) );
27 |
28 | if( idx && idx.length > 0 ) bGeo.setIndex( idx );
29 | if( norm && norm.length > 0 ) bGeo.setAttribute( "normal", new THREE.BufferAttribute( new Float32Array( norm ), 3 ) );
30 | if( uv && uv.length > 0 ) bGeo.setAttribute( "uv", new THREE.BufferAttribute( new Float32Array( uv ), 2 ) );
31 |
32 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33 | mat = mat || new THREE.MeshPhongMaterial( { color:0x009999 } ); // ,side:THREE.DoubleSide
34 | const mesh = new THREE.Mesh( bGeo, mat );
35 |
36 | if( wireFrame ){
37 | const mat = new THREE.LineBasicMaterial({ color:0xffffff, opacity:0.6, transparent:true });
38 | const wGeo = new THREE.WireframeGeometry( bGeo );
39 | const grp = new THREE.Group();
40 | grp.add( mesh );
41 | grp.add( new THREE.LineSegments( wGeo, mat ) )
42 | return grp;
43 | }else{
44 | return mesh;
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/test/test_dist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
61 |
--------------------------------------------------------------------------------
/test/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
62 |
--------------------------------------------------------------------------------
/src3/mod/Rotation.js:
--------------------------------------------------------------------------------
1 | import { vec3_add, vec3_angle, vec3_norm } from '../lib/Maths.js';
2 | import {
3 | // Ray,
4 | intersectPlane,
5 | // intersectTri,
6 | // intersectSphere,
7 | // nearPoint,
8 | //NearSegmentResult,
9 | //nearSegment,
10 | } from '../../src/RayIntersection.js';
11 |
12 | import {
13 | vec3_sub,
14 | vec3_len,
15 | vec3_dot,
16 | //vec3_scaleAndAdd,
17 | quat_rotateTo,
18 | } from '../lib/Maths.js';
19 | import { ZeroCurvatureEnding } from 'three';
20 | import { Axes } from '../Structs.js';
21 |
22 |
23 | export default class Rotation{
24 | static minRayDistance = 0.08;
25 |
26 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
27 | const minRange = this.minRayDistance * state.cameraScale;
28 | const origin = state.position;
29 | const radius = state.arcRadius * state.cameraScale;
30 | const hitDir = [0,0,0];
31 | let t;
32 | let dist;
33 |
34 | for( let i=0; i < 3; i++ ){
35 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 | // First test against the plane using the axis as the plane normal
37 | t = intersectPlane( ray, origin, axes[i] );
38 | if( t === null ) continue;
39 |
40 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 | // Next do a circle radius test of the hit point to plane origin
42 | ray.posAt( t, hitPos );
43 | dist = vec3_len( hitPos, origin );
44 |
45 | if( Math.abs( radius - dist ) <= minRange ){
46 |
47 | // Test if in the correct quadrant
48 | vec3_sub( hitDir, hitPos, origin );
49 | if( vec3_dot( hitDir, axes[ ( i + 1 ) % 3 ] ) >= 0 &&
50 | vec3_dot( hitDir, axes[ ( i + 2 ) % 3 ] ) >= 0 ){
51 | return i;
52 | }
53 |
54 | }
55 | }
56 |
57 | return -1;
58 | }
59 |
60 | static rayDrag( ray, origin, axes, iAxis, hitPos ){
61 | let t = intersectPlane( ray, origin, axes[ iAxis ] );
62 | if( t === null ) return null;
63 |
64 | const fromDir = vec3_sub( [0,0,0], hitPos, origin );
65 | vec3_norm( fromDir, fromDir );
66 |
67 | const toDir = vec3_sub( [0,0,0], ray.posAt( t ), origin );
68 | vec3_norm( toDir, toDir );
69 |
70 | return quat_rotateTo( [0,0,0,1], fromDir, toDir );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src2/state/PositionState.js:
--------------------------------------------------------------------------------
1 | import {
2 | vec3_add,
3 | // vec3_sub,
4 | // vec3_len,
5 | vec3_copy,
6 | // vec3_transformQuat,
7 | // vec3_norm,
8 | vec3_dot,
9 | // vec3_scale,
10 | // vec3_scaleAndAdd,
11 | // vec3_sqrLen,
12 | // quat_mul,
13 | // quat_normalize,
14 | // quat_copy,
15 | // quat_sqrLen,
16 | // quat_setAxisAngle,
17 | } from '../../src/Maths.js';
18 |
19 |
20 | import {
21 | // Ray,
22 | // intersectPlane,
23 | // intersectTri,
24 | // intersectSphere,
25 | // nearPoint,
26 | NearSegmentResult,
27 | nearSegment,
28 | } from '../../src/RayIntersection.js';
29 |
30 | export default class PositionState{
31 | // #region DATA
32 | value = [0,0,0];
33 |
34 | minRayDistance = 0.2;
35 | xEndPoint = [0,0,0];
36 | yEndPoint = [0,0,0];
37 | zEndPoint = [0,0,0];
38 |
39 | aryEndPoint = [
40 | this.xEndPoint,
41 | this.yEndPoint,
42 | this.zEndPoint,
43 | ];
44 | // #endregion
45 |
46 | set( v ){
47 | this.value[ 0 ] = v[ 0 ];
48 | this.value[ 1 ] = v[ 1 ];
49 | this.value[ 2 ] = v[ 2 ];
50 | }
51 |
52 | updateEndpoints( man ){
53 | vec3_add( this.xEndPoint, this.value, man.rotation.xAxisScl );
54 | vec3_add( this.yEndPoint, this.value, man.rotation.yAxisScl );
55 | vec3_add( this.zEndPoint, this.value, man.rotation.zAxisScl );
56 | }
57 |
58 | rayTest( ray, state, hitPos=[0,0,0] ){
59 | const minRange = state.cameraScale * this.minRayDistance;
60 | const segResult = new NearSegmentResult();
61 | let minDist = Infinity;
62 | let minAxis = -1;
63 |
64 | //Debug.pnt2.reset();
65 |
66 | for( let i=0; i < 3; i++ ){
67 | if( nearSegment( ray, this.value, this.aryEndPoint[i], segResult ) ){
68 | if( ( segResult.distance <= minRange ) && segResult.distance < minDist ){
69 | minAxis = i;
70 | minDist = segResult.distance;
71 | vec3_copy( hitPos, segResult.segPosition );
72 | }
73 | }
74 | }
75 |
76 | // if( minDist !== Infinity ){
77 | // // console.log( 'Axis', minAxis, 'at', minPos );
78 | // // Debug.pnt2.add( minPos, 0x00ff00, 4 )
79 | // return true;
80 | // }
81 |
82 | return minAxis;
83 | }
84 | }
--------------------------------------------------------------------------------
/test/states.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
68 |
--------------------------------------------------------------------------------
/annotatebox/DragAction.js:
--------------------------------------------------------------------------------
1 | import {
2 | // vec3_add,
3 | // vec3_sub,
4 | // vec3_len,
5 | // vec3_norm,
6 | // vec3_scale,
7 | vec3_inv_scale,
8 | vec3_transformQuat,
9 | // vec3_sqrLen,
10 | vec3_copy,
11 | quat_mul,
12 | quat_copy,
13 | vec3_add,
14 | vec3_scaleAndAdd,
15 | //quat_setAxisAngle,
16 | //quat_normalize,
17 | } from './lib/Maths.js';
18 |
19 | import Translation from './mod/Translation.js';
20 | import Rotation from './mod/Rotation.js';
21 | import Faces from './mod/Faces.js';
22 |
23 | import { Modes, Axes } from './Structs.js';
24 | import { vec3_scale } from './lib/Maths.js';
25 |
26 | export default function DragAction( ref, state, events ){
27 | // #region MAIN
28 | const initPosition = [0,0,0];
29 | const initRotation = [0,0,0,0];
30 | const initHitPos = [0,0,0];
31 | let axes = null;
32 | let axesLengths = null;
33 | let initMode = -1;
34 | let initAxis = -1;
35 | // #endregion
36 |
37 | // #region METHODS
38 | const start = ()=>{
39 | initMode = state.selectMode;
40 | initAxis = state.selectAxis;
41 | axesLengths = state.axesLengths.slice();
42 | axes = ref.getAxes();
43 | vec3_copy( initPosition, state.position );
44 | quat_copy( initRotation, state.rotation );
45 | vec3_copy( initHitPos, state.rayHitPos );
46 |
47 | state.isDragging = true;
48 | events.emit( 'dragStart' );
49 | };
50 |
51 | const stop = ()=>{
52 | state.isDragging = false;
53 | events.emit( 'dragEnd' );
54 | };
55 |
56 | const move = ( ray )=>{
57 | switch( initMode ){
58 | case Modes.Translate:{
59 | const pos = Translation.rayDrag( ray, initPosition, axes[ initAxis ], initHitPos );
60 | if( pos ){
61 | state.position = pos;
62 | return true;
63 | }
64 | break; }
65 |
66 | case Modes.Rotate:{
67 | const rot = Rotation.rayDrag( ray, initPosition, axes, initAxis, initHitPos );
68 | if( rot ){
69 | state.rotation = quat_mul( [0,0,0,1], rot, initRotation );
70 | return true;
71 | }
72 | break; }
73 |
74 | case Modes.Scale:{
75 | const len = Faces.rayDrag( ray, initPosition, axes, initAxis, initHitPos, state.debug );
76 | if( len ){
77 | const al = axesLengths.slice();
78 | al[ initAxis ] = len;
79 | state.axesLengths = al;
80 | return true;
81 | }
82 | break; }
83 | }
84 |
85 | return false;
86 | };
87 | // #endregion
88 |
89 | return { start, stop, move };
90 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 3D Manipulator for ThreeJs
2 | [](https://twitter.com/SketchpunkLabs)
3 | [](https://github.com/sponsors/sketchpunklabs)
4 | [](https://youtube.com/c/sketchpunklabs)
5 | [](https://www.patreon.com/sketchpunk)
6 | [](https://ko-fi.com/sketchpunk)
7 |
8 |
9 |
10 | https://www.npmjs.com/package/manipulator3d
11 |
12 | ### NPM Install ###
13 | ```
14 | npm install manipulator3d
15 | ```
16 |
17 | ### Development Setup ###
18 | ```
19 | git clone --depth=1 https://github.com/sketchpunklabs/manipulator3d
20 | cd manipulator3d
21 | npm install
22 | npm run dev
23 | ```
24 |
25 | ## Usage with attachment ###
26 | ```javascript
27 | import { Manipulator3D } from 'Manipulator3D';
28 |
29 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30 | const cube = facedCube( null, 0.5 );
31 | cube.position.set( 0, 1, 0 );
32 | scene.add( cube );
33 |
34 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 | const man = new Manipulator3D( scene, camera, renderer );
36 |
37 | // Hook into events to disable camera controller when user does a drag action
38 | man.on( 'dragstart', ()=>{ orbitControl.enabled = false; } );
39 | man.on( 'dragend', ()=>{ orbitControl.enabled = true; } );
40 |
41 | // Hook into events to see the changes happen
42 | // man.on( 'translate', e=>console.log( 'Translate', e.detail ) );
43 | // man.on( 'rotate', e=>console.log( 'Rotate( Quat )', e.detail ) );
44 | // man.on( 'scale', e=>console.log( 'Scale', e.detail ) );
45 |
46 | // Turn on Control
47 | man.setActive( true );
48 |
49 | // Attach object to manipulate
50 | man.attach( cube );
51 | ```
52 |
53 | ## Usage of translation without attachment ###
54 | ```javascript
55 | import { Manipulator3D } from 'Manipulator3D';
56 |
57 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58 | const man = new Manipulator3D( scene, camera, renderer );
59 |
60 | // Hook into events to disable camera controller when user does a drag action
61 | man.on( 'dragstart', ()=>{ orbitControl.enabled = false; } );
62 | man.on( 'dragend', ()=>{ orbitControl.enabled = true; } );
63 |
64 | // Hook into events to see the changes happen
65 | man.on( 'translate', e=>console.log( 'Translate', e.detail ) );
66 |
67 | man.setActive( true ); // Turn on Control
68 | man.useRotate( false ); // Turn off Rotation
69 | man.useScale( false ); // Turn off Scale
70 | ```
--------------------------------------------------------------------------------
/src3/lib/MouseEvents.js:
--------------------------------------------------------------------------------
1 | import { Ray } from '../../src/RayIntersection.js';
2 |
3 |
4 | export default function MouseEvents( camera, renderer, actions, state, autoLoad=false ){
5 | // #region MAIN
6 | const oRay = new Ray();
7 | const canvas = renderer.domElement;
8 |
9 | /**
10 | * Special case when there is an onClick Handler on the canvas, it
11 | * get triggered on mouse up, but if user did a drag action the click
12 | * will trigger at the end. This can cause issues if using click as a way
13 | * to select new attachments.
14 | */
15 | let stopClick = false;
16 | // #endregion
17 |
18 | // #region EVENT HANDLERS
19 | const onClick = e =>{ if( stopClick ){ e.stopImmediatePropagation(); stopClick = false; } };
20 | const onPointerUp = e =>{ if( actions.rayUp() ) canvas.releasePointerCapture( e.pointerId ); };
21 | const onPointerMove = e =>{
22 | updateRay( e );
23 |
24 | if( !state.isDragging ){
25 | actions.rayHover( oRay );
26 | }else{
27 | actions.rayMove( oRay );
28 | canvas.setPointerCapture( e.pointerId ); // Keep receiving events
29 | e.preventDefault();
30 | e.stopPropagation();
31 | }
32 | };
33 |
34 | const onPointerDown = e =>{
35 | updateRay( e );
36 | if( actions.rayDown( oRay ) ){
37 | e.preventDefault();
38 | e.stopPropagation();
39 | stopClick = true;
40 | }
41 | };
42 | // #endregion
43 |
44 | // #region HELPERS
45 | const updateRay = e =>{
46 | const rect = canvas.getBoundingClientRect();
47 | const x = e.clientX - rect.x; // canvas x position
48 | const y = e.clientY - rect.y; // canvas y position
49 | const camProj = camera.projectionMatrix.toArray(); // Need Projection Matrix
50 | const camWorld = camera.matrixWorld.toArray(); // World Space Transform of Camera
51 | oRay.fromScreenProjection( x, y, rect.width, rect.height, camProj, camWorld );
52 | };
53 | // #endregion
54 |
55 | // #region METHODS
56 | const loadListeners = ()=>{
57 | canvas.addEventListener( 'click', onClick );
58 | canvas.addEventListener( 'pointermove', onPointerMove );
59 | canvas.addEventListener( 'pointerdown', onPointerDown );
60 | canvas.addEventListener( 'pointerup', onPointerUp );
61 | return this;
62 | };
63 |
64 | const unloadListeners = ()=>{
65 | canvas.removeEventListener( 'click', onClick );
66 | canvas.removeEventListener( 'pointermove', onPointerMove );
67 | canvas.removeEventListener( 'pointerdown', onPointerDown );
68 | canvas.removeEventListener( 'pointerup', onPointerUp );
69 | return this;
70 | };
71 | // #endregion
72 |
73 | if( autoLoad ) loadListeners();
74 | return { loadListeners, unloadListeners };
75 | }
--------------------------------------------------------------------------------
/annotatebox/lib/MouseEvents.js:
--------------------------------------------------------------------------------
1 | import { Ray } from '../../src/RayIntersection.js';
2 |
3 |
4 | export default function MouseEvents( camera, renderer, actions, state, autoLoad=false ){
5 | // #region MAIN
6 | const oRay = new Ray();
7 | const canvas = renderer.domElement;
8 |
9 | /**
10 | * Special case when there is an onClick Handler on the canvas, it
11 | * get triggered on mouse up, but if user did a drag action the click
12 | * will trigger at the end. This can cause issues if using click as a way
13 | * to select new attachments.
14 | */
15 | let stopClick = false;
16 | // #endregion
17 |
18 | // #region EVENT HANDLERS
19 | const onClick = e =>{ if( stopClick ){ e.stopImmediatePropagation(); stopClick = false; } };
20 | const onPointerUp = e =>{ if( actions.rayUp() ) canvas.releasePointerCapture( e.pointerId ); };
21 | const onPointerMove = e =>{
22 | updateRay( e );
23 |
24 | if( !state.isDragging ){
25 | actions.rayHover( oRay );
26 | }else{
27 | actions.rayMove( oRay );
28 | canvas.setPointerCapture( e.pointerId ); // Keep receiving events
29 | e.preventDefault();
30 | e.stopPropagation();
31 | }
32 | };
33 |
34 | const onPointerDown = e =>{
35 | updateRay( e );
36 | if( actions.rayDown( oRay ) ){
37 | e.preventDefault();
38 | e.stopPropagation();
39 | stopClick = true;
40 | }
41 | };
42 | // #endregion
43 |
44 | // #region HELPERS
45 | const updateRay = e =>{
46 | const rect = canvas.getBoundingClientRect();
47 | const x = e.clientX - rect.x; // canvas x position
48 | const y = e.clientY - rect.y; // canvas y position
49 | const camProj = camera.projectionMatrix.toArray(); // Need Projection Matrix
50 | const camWorld = camera.matrixWorld.toArray(); // World Space Transform of Camera
51 | oRay.fromScreenProjection( x, y, rect.width, rect.height, camProj, camWorld );
52 | };
53 | // #endregion
54 |
55 | // #region METHODS
56 | const loadListeners = ()=>{
57 | canvas.addEventListener( 'click', onClick );
58 | canvas.addEventListener( 'pointermove', onPointerMove );
59 | canvas.addEventListener( 'pointerdown', onPointerDown );
60 | canvas.addEventListener( 'pointerup', onPointerUp );
61 | return this;
62 | };
63 |
64 | const unloadListeners = ()=>{
65 | canvas.removeEventListener( 'click', onClick );
66 | canvas.removeEventListener( 'pointermove', onPointerMove );
67 | canvas.removeEventListener( 'pointerdown', onPointerDown );
68 | canvas.removeEventListener( 'pointerup', onPointerUp );
69 | return this;
70 | };
71 | // #endregion
72 |
73 | if( autoLoad ) loadListeners();
74 | return { loadListeners, unloadListeners };
75 | }
--------------------------------------------------------------------------------
/annotatebox/mod/Rotation.js:
--------------------------------------------------------------------------------
1 | import { vec3_add, vec3_angle, vec3_norm } from '../lib/Maths.js';
2 | import {
3 | // Ray,
4 | intersectPlane,
5 | // intersectTri,
6 | // intersectSphere,
7 | // nearPoint,
8 | //NearSegmentResult,
9 | //nearSegment,
10 | } from '../lib/RayIntersection.js';
11 |
12 |
13 | import {
14 | vec3_sub,
15 | vec3_len,
16 | vec3_dot,
17 | vec3_scaleAndAdd,
18 | quat_rotateTo,
19 | } from '../lib/Maths.js';
20 | import { ZeroCurvatureEnding } from 'three';
21 | import { Axes } from '../Structs.js';
22 |
23 | export default class Rotation{
24 | static minRayDistance = 0.1;
25 | static minAngle = 11 * Math.PI / 180;
26 |
27 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
28 | //const minRange = this.minRayDistance * state.cameraScale;
29 | const origin = state.position;
30 | const hitDir = [0,0,0];
31 | const toHit = [0,0,0];
32 | let t;
33 | let ii;
34 | let dist;
35 | let radius;
36 |
37 | // let debug = state.debug;
38 | // debug.pnt.reset();
39 | // debug.ln.reset();
40 |
41 | for( let i=0; i < 3; i++ ){
42 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43 | // First test against the plane using the axis as the plane normal
44 | t = intersectPlane( ray, origin, axes[i] );
45 | if( t === null ) continue;
46 |
47 | ii = (i + 1) % 3; // Need the next axis over to perform tests
48 |
49 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50 | // Next do a circle radius test of the hit point to plane origin
51 | ray.posAt( t, hitPos );
52 | radius = state.axesLengths[ ii ] + state.axisLen;
53 | dist = vec3_len( hitPos, origin );
54 |
55 | //debug.pnt.add( vec3_scaleAndAdd( [0,0,0], origin, axes[ii], radius ), 0xffff00, 13 );
56 | //debug.pnt.add( hitPos, 0x00ff00, 7 );
57 |
58 |
59 | if( Math.abs( radius - dist ) <= this.minRayDistance ){
60 | //debug.pnt.add( hitPos, 0xffffff, 13 );
61 | // Direction to hit position
62 | vec3_sub( toHit, hitPos, origin );
63 | vec3_norm( toHit, toHit );
64 |
65 | if( vec3_angle( axes[ii], toHit ) <= this.minAngle ){
66 |
67 | //debug.ln.add( origin, vec3_scaleAndAdd( [0,0,0], origin, toHit, 4 ), 0xffffff );
68 | //debug.ln.add( origin, vec3_scaleAndAdd( [0,0,0], origin, axes[ii], 4 ), 0xffff00 );
69 |
70 | //console.log( i, vec3_angle( axes[ii], toHit ) * 180 / Math.PI );
71 |
72 | return i;
73 |
74 | }
75 | }
76 |
77 | }
78 |
79 | return -1;
80 | }
81 |
82 | static rayDrag( ray, origin, axes, iAxis, hitPos ){
83 | let t = intersectPlane( ray, origin, axes[ iAxis ] );
84 | if( t === null ) return null;
85 |
86 | const fromDir = vec3_sub( [0,0,0], hitPos, origin );
87 | vec3_norm( fromDir, fromDir );
88 |
89 | const toDir = vec3_sub( [0,0,0], ray.posAt( t ), origin );
90 | vec3_norm( toDir, toDir );
91 |
92 | return quat_rotateTo( [0,0,0,1], fromDir, toDir );
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src2/Manipulator3D.js:
--------------------------------------------------------------------------------
1 | import ManipulatorState from './state/ManipulatorState.js';
2 | import ManipulatorActions from './ManipulatorActions.js';
3 | import MouseEvents from './MouseEvents.js';
4 | import Tracer from './Tracer.js';
5 |
6 |
7 | export default class Manipulator3D{
8 | // #region MAIN
9 | state = new ManipulatorState();
10 | actions = new ManipulatorActions( this );
11 | trace = new Tracer();
12 | camera = null;
13 | mouseEvents = null;
14 |
15 | constructor( camera, renderer ){
16 | this.camera = camera;
17 | this.mouseEvents = new MouseEvents( this, renderer );
18 | }
19 | // #endregion
20 |
21 | // #region DRAGGING
22 | startDrag(){
23 | this.state.isDragging = true;
24 | this.trace.prepare( this );
25 | this.state.doCache();
26 |
27 |
28 | //console.log( 'startDrag' );
29 |
30 | // // Save initial state of attached object
31 | // if( this.attachedObject ){
32 | // vec3_copy( this._initDragPosition, this.attachedObject.position.toArray() );
33 | // vec3_copy( this._initDragScale, this.attachedObject.scale.toArray() );
34 | // quat_copy( this._initDragQuaternion, this.attachedObject.quaternion.toArray() );
35 | // }else{
36 | // // Continue using 'current' values in no attachment use of the control.
37 | // vec3_copy( this._initDragPosition, this._currentPosition );
38 | // vec3_copy( this._initDragScale, this._currentScale );
39 | // quat_copy( this._initDragQuaternion, this._currentQuaternion );
40 | // }
41 |
42 | // // Offset has prevent the snapping effect of translation
43 | // vec3_sub( this._intersectOffset, this.data.position, this.data.intersectPos );
44 |
45 | // // When dealing with small objects, better to hide the gizmo during scale & rotation
46 | // // But leave the trace line visible as its really the only ui the user needs during dragging
47 | // if( this.data.activeMode !== ManipulatorMode.Translate ){
48 | // this.mesh.hideGizmo();
49 | // }
50 |
51 | // this.data.hasUpdated = true; // Need mesh to update
52 |
53 | this.emit( 'dragstart' );
54 | }
55 |
56 | endDrag(){
57 | this.state.isDragging = false;
58 | //console.log( 'endDrag' );
59 |
60 | // // onTranslate updates position, need to recalculate at the end of dragging
61 | // // for intersection tests to be accurate.
62 | // this.data.calcAxesPosition();
63 |
64 | // // When doing dragging away from ui, the hover event won't trigger to undo
65 | // // visual states, so call method at the end of the dragging to tidy things up.
66 | // this.data.resetState();
67 | // this.mesh.showGizmo();
68 |
69 | this.emit( 'dragend' );
70 | }
71 | // #endregion
72 |
73 | // #region OUTER EVENTS{
74 | on( evtName, fn ){ document.body.addEventListener( evtName, fn ); return this; }
75 | off( evtName, fn ){ document.body.removeEventListener( evtName, fn ); return this; }
76 | emit( evtName, detail=null ){
77 | document.body.dispatchEvent( new CustomEvent( evtName, { detail, bubbles:true, cancelable:true, composed:false } ) );
78 | }
79 | // #endregion
80 | }
--------------------------------------------------------------------------------
/src2/MouseEvents.js:
--------------------------------------------------------------------------------
1 | import {
2 | Ray,
3 | // intersectPlane,
4 | // intersectTri,
5 | // intersectSphere,
6 | // nearPoint,
7 | // NearSegmentResult,
8 | // nearSegment,
9 | } from '../src/RayIntersection.js';
10 |
11 | export default class MouseEvents{
12 | // #region MAIN
13 | constructor( man, renderer ){
14 | this.ray = new Ray();
15 | this.canvas = renderer.domElement;
16 | this.man = man;
17 | this.loadListeners();
18 |
19 | /**
20 | * Special case when there is an onClick Handler on the canvas, it
21 | * get triggered on mouse up, but if user did a drag action the click
22 | * will trigger at the end. This can cause issues if using click as a way
23 | * to select new attachments.
24 | */
25 | this.stopClick = false;
26 | }
27 | // #endregion
28 |
29 | // #region METHODS
30 | loadListeners(){
31 | this.canvas.addEventListener( 'click', this.onClick );
32 | this.canvas.addEventListener( 'pointermove', this.onPointerMove );
33 | this.canvas.addEventListener( 'pointerdown', this.onPointerDown );
34 | this.canvas.addEventListener( 'pointerup', this.onPointerUp );
35 | return this;
36 | }
37 |
38 | unloadListeners(){
39 | this.canvas.removeEventListener( 'click', this.onClick );
40 | this.canvas.removeEventListener( 'pointermove', this.onPointerMove );
41 | this.canvas.removeEventListener( 'pointerdown', this.onPointerDown );
42 | this.canvas.removeEventListener( 'pointerup', this.onPointerUp );
43 | }
44 |
45 | updateRay( e ){
46 | const rect = this.canvas.getBoundingClientRect();
47 | const x = e.clientX - rect.x; // canvas x position
48 | const y = e.clientY - rect.y; // canvas y position
49 | const camProj = this.man.camera.projectionMatrix.toArray(); // Need Projection Matrix
50 | const camWorld = this.man.camera.matrixWorld.toArray(); // World Space Transform of Camera
51 | this.ray.fromScreenProjection( x, y, rect.width, rect.height, camProj, camWorld );
52 | }
53 | // #endregion
54 |
55 | // #region HANDLERS
56 | onClick = ( e )=>{
57 | if( this.stopClick ){
58 | e.stopImmediatePropagation();
59 | this.stopClick = false;
60 | }
61 | };
62 |
63 | onPointerMove = ( e )=>{
64 | this.updateRay( e );
65 |
66 | if( !this.man.state.isDragging ){
67 | this.man.actions.rayHover( this.ray );
68 | }else{
69 | this.man.actions.rayMove( this.ray );
70 | this.canvas.setPointerCapture( e.pointerId ); // Keep receiving events
71 | e.preventDefault();
72 | e.stopPropagation();
73 | }
74 | };
75 |
76 | onPointerDown = ( e )=>{
77 | this.updateRay( e );
78 |
79 | if( this.man.actions.rayDown( this.ray ) ){
80 | e.preventDefault();
81 | e.stopPropagation();
82 | this.stopClick = true;
83 | }
84 | };
85 |
86 | onPointerUp = ( e )=>{
87 | if( this.man.actions.rayUp() ){
88 | this.canvas.releasePointerCapture( e.pointerId );
89 | }
90 | };
91 | // #endregion
92 | }
93 |
--------------------------------------------------------------------------------
/test/_lib/DynamicMesh.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | class DynamicMesh{
4 | mesh = null;
5 |
6 | vertices = [];
7 | indices = [];
8 | normals = [];
9 |
10 | _vertCount = 0;
11 | _normCount = 0;
12 | _idxCount = 0;
13 |
14 | constructor( mat=null ){
15 | this.mesh = new THREE.Mesh();
16 | this.mesh.material = mat || new THREE.MeshPhongMaterial( {color:0x00ffff, side:THREE.DoubleSide } );
17 | }
18 |
19 | rebuild( nGeo=null ){
20 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 | if( nGeo ){
22 | if( nGeo.vertices ) this.vertices = nGeo.vertices;
23 | if( nGeo.indices ) this.indices = nGeo.indices;
24 | if( nGeo.normals ) this.normals = nGeo.normals;
25 | }
26 |
27 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28 | // Geometry Buffers can't not be resized in ThreeJS
29 | // Note : Buffers themselves in WebGL can, just a limitation of the Framework.
30 | // Because of this, will need to recreate the Geometry Object if size is larger.
31 | if( this.vertices.length > this._vertCount ||
32 | this.indices.length > this._idxCount ||
33 | this.normals.length > this._normCount ){
34 |
35 | this.mesh.geometry.dispose();
36 | this.mesh.geometry = null;
37 | this._mkGeo();
38 |
39 | return this;
40 | }
41 |
42 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43 | let geo = this.mesh.geometry;
44 |
45 | geo.attributes.position.array.set( this.vertices );
46 | geo.attributes.position.needsUpdate = true;
47 |
48 | if( this.normals.length > 0 ){
49 | geo.attributes.normal.array.set( this.normals );
50 | geo.attributes.normal.needsUpdate = true;
51 | }
52 |
53 | if( this.indices.length > 0 ){
54 | geo.index.array.set( this.indices );
55 | geo.index.needsUpdate = true;
56 | geo.setDrawRange( 0, this.indices.length );
57 | }else geo.setDrawRange( 0, this.vertices.length / 3 );
58 |
59 | geo.computeBoundingBox();
60 | geo.computeBoundingSphere();
61 |
62 | return this;
63 | }
64 |
65 | reset(){
66 | this.vertices.length = 0;
67 | this.indices.length = 0;
68 | this.normals.length = 0;
69 | return this;
70 | }
71 |
72 | _mkGeo(){
73 | //----------------------------------
74 | // Define Geometry Object
75 | const bGeo = new THREE.BufferGeometry();
76 | bGeo.setAttribute( "position", new THREE.BufferAttribute( new Float32Array( this.vertices ), 3 ) );
77 |
78 | if( this.normals.length > 0 ) bGeo.setAttribute( "normal", new THREE.BufferAttribute( new Float32Array( this.normals ), 3 ) );
79 | if( this.indices.length > 0 ){
80 | // ThreeJS doesn't seem to like type arrays when setting indices, If its a
81 | // typeArray, convert it to a javascript array.
82 | // TODO: Look up how to setup indices using Buffers instead.
83 | if( this.indices.byteLength ) bGeo.setIndex( Array.from( this.indices ) );
84 | else bGeo.setIndex( this.indices );
85 | }
86 |
87 | this.mesh.geometry = bGeo;
88 |
89 | //----------------------------------
90 | if( this.vertices.length > this._vertCount ) this._vertCount = this.vertices.length;
91 | if( this.indices.length > this._idxCount ) this._idxCount = this.indices.length;
92 | if( this.normals.length > this._normCount ) this._normCount = this.normals.length;
93 | }
94 | }
95 |
96 | export default DynamicMesh;
--------------------------------------------------------------------------------
/annotatebox/Actions.js:
--------------------------------------------------------------------------------
1 | // #region IMPORTS
2 | // import Translation from './mod/Translation.js';
3 | // import Rotation from './mod/Rotation.js';
4 | // import Scale from './mod/Scale.js';
5 | import Faces from './mod/Faces.js';
6 | import Translation from './mod/Translation.js';
7 | import Rotation from './mod/Rotation.js';
8 |
9 | // import DragAction from './DragAction.js';
10 | import { Modes, Axes } from './Structs.js';
11 |
12 | import { intersectQuad } from './lib/RayIntersection.js';
13 |
14 | import DragAction from './DragAction.js';
15 |
16 | import {
17 | vec3_add_batch,
18 | // vec3_add,
19 | // vec3_sub,
20 | // vec3_len,
21 | // vec3_norm,
22 | // vec3_scale,
23 | vec3_transformQuat,
24 | vec3_sqrLen,
25 | // vec3_copy,
26 | // quat_mul,
27 | // quat_copy,
28 | // quat_setAxisAngle,
29 | // quat_normalize,
30 | } from './lib/Maths.js';
31 | // #endregion
32 |
33 | export default function Actions( ref, state, events ){
34 | // #region MAIN
35 | const dragging = DragAction( ref, state, events );
36 | let pnts = [
37 | [0,0,0], [0,0,0], [0,0,0], [0,0,0], // Bottom : Start back left, front left, front right, back right
38 | [0,0,0], [0,0,0], [0,0,0], [0,0,0], // Top
39 | ];
40 | // #endregion
41 |
42 | // #region HELPERS
43 | const rayIntersect = ray =>{
44 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | // Get the current state axis from rotation
46 | const axes = ref.getAxes();
47 | const hit = [Infinity,Infinity,Infinity];
48 |
49 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50 | const rotAxis = Rotation.rayTest( ray, state, axes, hit );
51 | //const rotDist = ( rotAxis === -1 )? Infinity : vec3_sqrLen( ray.posStart, rotHit );
52 | if( rotAxis !== -1 ){
53 | state.selectAxis = rotAxis;
54 | state.selectMode = Modes.Rotate;
55 | state.rayHitPos = hit;
56 | return true;
57 | }
58 |
59 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60 | const posAxis = Translation.rayTest( ray, state, axes, hit );
61 | if( posAxis !== -1 ){
62 | state.selectAxis = posAxis;
63 | state.selectMode = Modes.Translate;
64 | state.rayHitPos = hit;
65 | return true;
66 | }
67 |
68 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | const faceAxis = Faces.rayTest( ray, state, axes, hit );
70 | if( faceAxis !== -1 ){
71 | state.selectAxis = faceAxis;
72 | state.selectMode = Modes.Scale;
73 | state.rayHitPos = hit;
74 | return true;
75 | }
76 |
77 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
78 | state.selectAxis = Axes.None;
79 | state.selectMode = Modes.Translate;
80 | return false;
81 | };
82 | // #endregion
83 |
84 | // #region EXPORT
85 | return {
86 | rayDown( ray ){
87 | if( state.isActive && rayIntersect( ray ) ){
88 | dragging.start();
89 | return true;
90 | }
91 | return false;
92 | },
93 |
94 | rayUp( ray ){
95 | if( state.isDragging ) dragging.stop ();
96 | },
97 |
98 | rayMove( ray ){
99 | if( !state.isActive || !state.isDragging ) return false;
100 | return dragging.move( ray );
101 | },
102 |
103 | rayHover( ray ){
104 | return ( state.isActive && !state.isDragging )? rayIntersect( ray ) : false;
105 | },
106 | };
107 | // #endregion
108 | }
109 |
--------------------------------------------------------------------------------
/src3/render/FloorCube.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | // Create a cube with its origin at the bottom left back corner
4 | export default class FloorCube{
5 | static mesh( mat=null, pos=null, scl=null ){
6 | const bGeo = this.geo();
7 | const mesh = new THREE.Mesh( bGeo, mat || new THREE.MeshPhongMaterial( { color:0x009999 } ) );
8 | if( pos ) mesh.position.fromArray( pos );
9 | if( scl != null ) mesh.scale.set( scl, scl, scl );
10 |
11 | return mesh;
12 | }
13 |
14 | static geo( width=1, height=1, depth=1 ){
15 | const geo = this.get( width, height, depth );
16 | const bGeo = new THREE.BufferGeometry();
17 | bGeo.setIndex( new THREE.BufferAttribute( geo.indices, 1 ) );
18 | bGeo.setAttribute( 'position', new THREE.BufferAttribute( geo.vertices, 3 ) );
19 | bGeo.setAttribute( 'normal', new THREE.BufferAttribute( geo.normals, 3 ) );
20 | bGeo.setAttribute( 'uv', new THREE.BufferAttribute( geo.texcoord, 2 ) );
21 | return bGeo;
22 | }
23 |
24 | static get( width=1, height=1, depth=1 ){
25 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26 | const x1 = width * 0.5,
27 | y1 = height, // * 0.5,
28 | z1 = depth * 0.5,
29 | x0 = -x1,
30 | y0 = 0, //-y1,
31 | z0 = -z1;
32 |
33 | // Starting bottom left corner, then working counter clockwise to create the front face.
34 | // Backface is the first face but in reverse (3,2,1,0)
35 | // keep each quad face built the same way to make index and uv easier to assign
36 | const vert = [
37 | x0, y1, z1, //0 Front
38 | x0, y0, z1, //1
39 | x1, y0, z1, //2
40 | x1, y1, z1, //3
41 |
42 | x1, y1, z0, //4 Back
43 | x1, y0, z0, //5
44 | x0, y0, z0, //6
45 | x0, y1, z0, //7
46 |
47 | x1, y1, z1, //3 Right
48 | x1, y0, z1, //2
49 | x1, y0, z0, //5
50 | x1, y1, z0, //4
51 |
52 | x0, y0, z1, //1 Bottom
53 | x0, y0, z0, //6
54 | x1, y0, z0, //5
55 | x1, y0, z1, //2
56 |
57 | x0, y1, z0, //7 Left
58 | x0, y0, z0, //6
59 | x0, y0, z1, //1
60 | x0, y1, z1, //0
61 |
62 | x0, y1, z0, //7 Top
63 | x0, y1, z1, //0
64 | x1, y1, z1, //3
65 | x1, y1, z0, //4
66 | ];
67 |
68 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | //Build the index of each quad [0,1,2, 2,3,0]
70 | let i;
71 | const idx = [];
72 | for( i=0; i < vert.length / 3; i+=2) idx.push( i, i+1, ( Math.floor( i / 4 ) * 4 ) + ( ( i + 2 ) % 4 ) );
73 |
74 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75 | //Build UV data for each vertex
76 | const uv = [];
77 | for( i=0; i < 6; i++) uv.push( 0,0, 0,1, 1,1, 1,0 );
78 |
79 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
80 |
81 | return {
82 | vertices : new Float32Array( vert ),
83 | indices : new Uint16Array( idx ),
84 | texcoord : new Float32Array( uv ),
85 | normals : new Float32Array( [ // Left/Right have their xNormal flipped to render correctly in 3JS, Why does normals need to be mirrored on X?
86 | 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, //Front
87 | 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, //Back
88 | 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, //Left
89 | 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, //Bottom
90 | -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, //Right
91 | 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 //Top
92 | ] ),
93 | };
94 | }
95 | }
--------------------------------------------------------------------------------
/src2/state/ManipulatorState.js:
--------------------------------------------------------------------------------
1 | import PositionState from './PositionState.js';
2 | import RotationState from './RotationState.js';
3 | import ScaleState from './ScaleState.js';
4 |
5 |
6 | import {
7 | // vec3_add,
8 | vec3_sub,
9 | vec3_len,
10 | vec3_copy,
11 | // vec3_transformQuat,
12 | vec3_norm,
13 | vec3_dot,
14 | vec3_scale,
15 | // vec3_scaleAndAdd,
16 | vec3_sqrLen,
17 | // quat_mul,
18 | // quat_normalize,
19 | quat_copy,
20 | quat_sqrLen,
21 | // quat_setAxisAngle,
22 | } from '../../src/Maths.js';
23 |
24 |
25 | import ManipulatorMode from '../ManipulatorMode.js';
26 |
27 | export default class ManipulatorState{
28 | // #region MAIN
29 | // Main Transformation State
30 | position = new PositionState();
31 | rotation = new RotationState();
32 | scale = new ScaleState();
33 |
34 | // Scale data based on camera distance
35 | cameraRot = [0,0,0,1];
36 | cameraPos = [0,0,0];
37 | cameraFactor = 10;
38 | cameraScale = 1;
39 | cameraVecScale = [1,1,1];
40 | cameraFlipDMin = -0.02;
41 |
42 | // settings
43 | isActive = true;
44 | isDragging = false;
45 | rotateStep = 2 * Math.PI / 180; // How many radians per step
46 |
47 | // misc
48 | radius = 1.5; // Distance each axis should stick out from origin
49 | mode = ManipulatorMode.None;
50 | iAxis = -1;
51 | contactPos = [0,0,0];
52 |
53 | // cache
54 | _initRotation = [0,0,0,0];
55 | _initAxis = [0,0,0];
56 |
57 | constructor(){}
58 | // #endregion
59 |
60 | // #region CAMERA
61 | updateFromCamera( pos, rot ){
62 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63 | // Only update if the camera has changed since last update
64 | if(
65 | Math.abs( vec3_sqrLen( pos, this.cameraPos ) ) <= 0.000001 &&
66 | Math.abs( quat_sqrLen( rot, this.cameraRot ) ) <= 0.000001
67 | ) return;
68 |
69 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70 | vec3_copy( this.cameraPos, pos ); // Save camera state
71 | quat_copy( this.cameraRot, rot );
72 | this.updateCameraScale(); // Update cameral scaling
73 | this.updateData(); // Update other bits of data
74 | return this;
75 | }
76 |
77 | updateCameraScale(){
78 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79 | // Adjust the scale to keep the gizmo as the same size no matter how far the camera goes
80 | const eyeDir = vec3_sub( [0,0,0], this.cameraPos, this.position.value );
81 | const eyeLen = vec3_len( eyeDir );
82 | this.cameraScale = eyeLen / this.cameraFactor;
83 |
84 | vec3_norm( eyeDir, eyeDir ); // Normalize for DOT Checks
85 | vec3_scale( this.cameraVecScale, [1,1,1], this.cameraScale );
86 |
87 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88 | // Flip viewing to the opposite side
89 | if( vec3_dot( eyeDir, [1,0,0] ) < this.cameraFlipDMin ) this.cameraVecScale[0] = -this.cameraVecScale[0];
90 | if( vec3_dot( eyeDir, [0,1,0] ) < this.cameraFlipDMin ) this.cameraVecScale[1] = -this.cameraVecScale[1];
91 | if( vec3_dot( eyeDir, [0,0,1] ) < this.cameraFlipDMin ) this.cameraVecScale[2] = -this.cameraVecScale[2];
92 | }
93 | // #endregion
94 |
95 | // #region METHODS
96 | updateData(){
97 | this.rotation.updateScale( this ); // Update scaled axis
98 | this.position.updateEndpoints( this ); // Update axis end points
99 | }
100 |
101 | doCache(){
102 | quat_copy( this._initRotation, this.rotation.value );
103 | vec3_copy( this._initAxis, this.rotation.aryAxis[ this.iAxis ] );
104 | }
105 | // #endregion
106 | }
--------------------------------------------------------------------------------
/test/ManipulatorDebugger.js:
--------------------------------------------------------------------------------
1 | import { Group } from 'three';
2 | import DynLineMesh from './_lib/DynLineMesh.js';
3 | import ShapePointsMesh from './_lib/ShapePointsMesh.js';
4 |
5 | export default class ManipulatorDebugger extends Group{
6 | meshAxis = new DynLineMesh();
7 | meshPoints = new ShapePointsMesh();
8 |
9 | constructor(){
10 | super();
11 | this.add( this.meshAxis );
12 | this.add( this.meshPoints );
13 | }
14 |
15 | update( data, forceUpdate=false ){
16 | if( !data.hasUpdated && !data.hasHit && !forceUpdate ) return;
17 |
18 | const a = [0,0,0];
19 | const b = [0,0,0];
20 | const pntSize = 6;
21 | const pntShape = 2;
22 | this.meshAxis.reset();
23 | this.meshPoints.reset();
24 |
25 | // Axes Lines
26 | this.meshAxis.add( data.position, data.axes[0].endPos, 0xffff00 );
27 | this.meshAxis.add( data.position, data.axes[1].endPos, 0x00ffff );
28 | this.meshAxis.add( data.position, data.axes[2].endPos, 0x00ff00 );
29 |
30 | // Mind Point lines ( Planes )
31 | this.meshAxis.add( data.axes[0].midPos, data.axes[1].midPos, 0xffff00, 0x00ffff );
32 | this.meshAxis.add( data.axes[1].midPos, data.axes[2].midPos, 0x00ffff, 0x00ff00 );
33 | this.meshAxis.add( data.axes[2].midPos, data.axes[0].midPos, 0x00ff00, 0xffff00 );
34 |
35 | // Draw End Points
36 | this.meshPoints.add( data.axes[0].endPos, 0xffff00, pntSize * Math.abs( data.scale[0] ), pntShape );
37 | this.meshPoints.add( data.axes[1].endPos, 0x00ffff, pntSize * Math.abs( data.scale[1] ), pntShape );
38 | this.meshPoints.add( data.axes[2].endPos, 0x00ff00, pntSize * Math.abs( data.scale[2] ), pntShape );
39 |
40 | // Draw Mid Points
41 | this.meshPoints.add( data.axes[0].midPos, 0xffff00, 3 * Math.abs( data.scale[0] ), 1 );
42 | this.meshPoints.add( data.axes[1].midPos, 0x00ffff, 3 * Math.abs( data.scale[1] ), 1 );
43 | this.meshPoints.add( data.axes[2].midPos, 0x00ff00, 3 * Math.abs( data.scale[2] ), 1 );
44 |
45 | // Draw Scl Points
46 | this.meshPoints.add( data.axes[0].sclPos, 0xffff00, 5 * Math.abs( data.scale[0] ), 0 );
47 | this.meshPoints.add( data.axes[1].sclPos, 0x00ffff, 5 * Math.abs( data.scale[1] ), 0 );
48 | this.meshPoints.add( data.axes[2].sclPos, 0x00ff00, 5 * Math.abs( data.scale[2] ), 0 );
49 |
50 | // Draw axes guides
51 | // let ax;
52 | // for( let i=0; i < 3; i++ ){
53 | // ax = data.axes[ i ];
54 | // if( ax.isActive ){
55 | // this.meshAxis.add( ax.tracePoints.a, ax.tracePoints.b, 0x909090 );
56 | // }
57 | // }
58 |
59 | // TraceLine
60 | if( data.traceLine.isActive ){
61 | this.meshAxis.add( data.traceLine.a, data.traceLine.b, 0x909090 );
62 | //this.meshPoints.add( data.hitPos, 0xffffff, 5 * Math.abs( data.scale[2] ), 7 );
63 | this.meshPoints.add( data.traceLine.origin, 0xffffff, 5 * Math.abs( data.scale[2] ), 7 );
64 | //console.log( 'x', data.traceLine.hit );
65 | this.meshPoints.add( data.traceLine.hitPos, 0xffffff, 5 * Math.abs( data.scale[2] ), 1 );
66 | ///Debug.pnt.add( data.traceLine.hit, 0xffffff, 10 );
67 | }
68 |
69 | // Draw Rotation Arcs
70 | const xSign = Math.sign( data.scale[0] );
71 | const ySign = Math.sign( data.scale[1] );
72 | const zSign = Math.sign( data.scale[2] );
73 |
74 | this.meshAxis.arc( data.position, data.axes[0].dir, data.axes[2].dir, data.scale[0] * data.arcRadius, 6, Math.PI*0.5*xSign*zSign, 0, 0x00ffff );
75 | this.meshAxis.arc( data.position, data.axes[1].dir, data.axes[2].dir, data.scale[1] * data.arcRadius, 6, Math.PI*0.5*zSign*ySign, 0, 0xffff00 );
76 | this.meshAxis.arc( data.position, data.axes[1].dir, data.axes[0].dir, data.scale[1] * data.arcRadius, 6, Math.PI*0.5*xSign*ySign, 0, 0x00ff00 );
77 |
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src3/DragAction.js:
--------------------------------------------------------------------------------
1 | import {
2 | // vec3_add,
3 | // vec3_sub,
4 | // vec3_len,
5 | // vec3_norm,
6 | // vec3_scale,
7 | vec3_inv_scale,
8 | vec3_transformQuat,
9 | // vec3_sqrLen,
10 | vec3_copy,
11 | quat_mul,
12 | quat_copy,
13 | vec3_add,
14 | vec3_scaleAndAdd,
15 | //quat_setAxisAngle,
16 | //quat_normalize,
17 | } from './lib/Maths.js';
18 |
19 | import Translation from './mod/Translation.js';
20 | import Rotation from './mod/Rotation.js';
21 | import Plane from './mod/Plane.js';
22 | import Scale from './mod/Scale.js';
23 |
24 | import { Modes, Axes } from './Structs.js';
25 | import { vec3_scale } from '../src/Maths.js';
26 |
27 | export default function DragAction( state, events ){
28 | // #region MAIN
29 | const initPosition = [0,0,0];
30 | const initRotation = [0,0,0,0];
31 | const initScale = [0,0,0];
32 | const initHitPos = [0,0,0];
33 | const axes = [
34 | [1,0,0],
35 | [0,1,0],
36 | [0,0,1],
37 | ];
38 |
39 | let initMode = -1;
40 | let initAxis = -1;
41 | // #endregion
42 |
43 | // #region METHODS
44 | const start = ()=>{
45 | if( state.target ){
46 | vec3_copy( initPosition, state.target.position.toArray() );
47 | vec3_copy( initScale, state.target.scale.toArray() );
48 | quat_copy( initRotation, state.target.quaternion.toArray() );
49 | }else{
50 | vec3_copy( initPosition, state.position );
51 | vec3_copy( initScale, state.scale );
52 | quat_copy( initRotation, state.rotation );
53 | }
54 |
55 | vec3_copy( initHitPos, state.rayHitPos );
56 | vec3_transformQuat( axes[0], [1,0,0], initRotation );
57 | vec3_transformQuat( axes[1], [0,1,0], initRotation );
58 | vec3_transformQuat( axes[2], [0,0,1], initRotation );
59 |
60 | initMode = state.selectMode;
61 | initAxis = state.selectAxis;
62 |
63 | state.isDragging = true;
64 | events.emit( 'dragStart' );
65 | };
66 |
67 | const stop = ()=>{
68 | state.isDragging = false;
69 | events.emit( 'dragEnd' );
70 | };
71 |
72 | const move = ( ray )=>{
73 | switch( initMode ){
74 | case Modes.Translate:{
75 | const pos = Translation.rayDrag( ray, initPosition, axes[ initAxis ], initHitPos );
76 | if( pos ){
77 | state.position = pos;
78 | return true;
79 | }
80 | break; }
81 |
82 | case Modes.Plane:{
83 | const pos = Plane.rayDrag( ray, initPosition, axes, initAxis, initHitPos );
84 | if( pos ){
85 | state.position = pos;
86 | return true;
87 | }
88 | break; }
89 |
90 | case Modes.Rotate:{
91 | const rot = Rotation.rayDrag( ray, initPosition, axes, initAxis, initHitPos );
92 | if( rot ){
93 | state.rotation = quat_mul( [0,0,0,1], rot, initRotation );
94 | return true;
95 | }
96 | break; }
97 |
98 | case Modes.Scale:{
99 | const scl = Scale.rayDrag( ray, initPosition, axes, initAxis, initHitPos );
100 | if( scl ){
101 |
102 | state.position = vec3_scaleAndAdd( [0,0,0], initPosition, axes[ initAxis ], scl[ initAxis ] * 0.5 );
103 |
104 |
105 | //vec3_inv_scale( scl, scl, state.scaleIncStep );
106 | vec3_add( scl, initScale, scl );
107 | state.scale = scl;
108 |
109 |
110 | return true;
111 | }
112 | break; }
113 | }
114 |
115 | return false;
116 | };
117 | // #endregion
118 |
119 | return { start, stop, move };
120 | }
--------------------------------------------------------------------------------
/test/new2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
113 |
--------------------------------------------------------------------------------
/src2/ManipulatorActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | // vec3_add,
3 | // vec3_sub,
4 | // vec3_len,
5 | vec3_copy,
6 | // vec3_transformQuat,
7 | // vec3_norm,
8 | // vec3_dot,
9 | // vec3_scale,
10 | // vec3_scaleAndAdd,
11 | vec3_sqrLen,
12 | quat_mul,
13 | quat_normalize,
14 | // quat_copy,
15 | // quat_sqrLen,
16 | quat_setAxisAngle,
17 | } from '../src/Maths.js';
18 |
19 | import ManipulatorMode from './ManipulatorMode.js';
20 |
21 | export default class ManipulatorActions{
22 | constructor( man ){
23 | this.man = man;
24 | }
25 |
26 | rayIntersect( ray ){
27 | const s = this.man.state;
28 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29 | // Test Axis
30 | const posHit = [Infinity,Infinity,Infinity];
31 | const posAxis = s.position.rayTest( ray, s, posHit );
32 | const posDist = vec3_sqrLen( ray.posStart, posHit );
33 |
34 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 | // Test Rotation Arcs
36 | const rotHit = [Infinity,Infinity,Infinity];
37 | const rotAxis = s.rotation.rayTest( ray, s, rotHit );
38 | const rotDist = vec3_sqrLen( ray.posStart, rotHit );
39 |
40 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 | if( posAxis === -1 && rotAxis === -1 ){
42 | s.mode = ManipulatorMode.None;
43 | s.iAxis = -1;
44 | return false;
45 | }
46 |
47 | if( posDist < rotDist ){
48 | //console.log( 'POSITION', posAxis );
49 | s.mode = ManipulatorMode.Translate;
50 | s.iAxis = posAxis;
51 | vec3_copy( s.contactPos, posHit );
52 | }else{
53 | //console.log( 'ROTATION', rotAxis );
54 | s.mode = ManipulatorMode.Rotate;
55 | s.iAxis = rotAxis;
56 | vec3_copy( s.contactPos, rotHit );
57 | }
58 |
59 | return true;
60 | }
61 |
62 | rayHover( ray ){
63 | return ( this.man.state.isActive && !this.man.state.isDragging && this.rayIntersect( ray ) );
64 | }
65 |
66 | rayDown( ray ){
67 | if( this.man.state.isActive && this.rayIntersect( ray ) ){
68 | this.man.startDrag();
69 | return true;
70 | }
71 |
72 | return false;
73 | }
74 |
75 | rayMove( ray ){
76 | if( this.man.state.isActive && !this.man.state.isDragging ) return false;
77 |
78 | const s = this.man.state;
79 | const dist = this.man.trace.rayIntersect( ray );
80 |
81 | switch( s.mode ){
82 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
83 | case ManipulatorMode.Translate:{
84 | //const pos = vec3_sub( [0,0,0], this.man.trace.hitPos, this.man.trace.offset );
85 | s.position.set( this.man.trace.pos );
86 | s.updateData();
87 | break; }
88 |
89 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90 | case ManipulatorMode.Rotate:{
91 | const distStep = 0.1; // Distance to travel to count as 1 step
92 | const steps = dist / distStep;
93 | const rad = s.rotateStep * steps;
94 | const q = quat_setAxisAngle( [0,0,0,1], s._initAxis, rad );
95 |
96 | quat_mul( q, q, s._initRotation );
97 | quat_normalize( q, q );
98 |
99 | s.rotation.set( q );
100 | s.rotation.updateAxis();
101 | s.rotation.updateScale( s ); // TODO This is dumb
102 | s.updateData();
103 | break; }
104 |
105 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106 | case ManipulatorMode.Scale: {
107 |
108 | break; }
109 | }
110 |
111 | return true;
112 | }
113 |
114 | rayUp(){
115 | if( this.man.state.isDragging ){
116 | this.man.endDrag();
117 | return true;
118 | }
119 | return false;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/annotatebox/mod/Faces.js:
--------------------------------------------------------------------------------
1 | import { vec3_dot } from '../../src/Maths.js';
2 | import { vec3_add_batch, vec3_len, vec3_sub, vec3_scale, vec3_scaleAndAdd } from '../lib/Maths.js';
3 | import {
4 | // Ray,
5 | intersectQuad,
6 | //intersectSphere,
7 | //nearPoint,
8 | NearSegmentResult,
9 | nearSegment,
10 | } from '../lib/RayIntersection.js';
11 |
12 |
13 | export default class Faces{
14 | static minLen = 0.15;
15 |
16 | static pnts = [
17 | [0,0,0], [0,0,0], [0,0,0], [0,0,0], // Bottom : Start back left, front left, front right, back right
18 | [0,0,0], [0,0,0], [0,0,0], [0,0,0], // Top
19 | ];
20 |
21 | static faces = [
22 | [ this.pnts[6], this.pnts[2], this.pnts[3], this.pnts[7] ], // Right Face +x
23 | [ this.pnts[4], this.pnts[5], this.pnts[6], this.pnts[7] ], // Top Face +y
24 | [ this.pnts[1], this.pnts[2], this.pnts[6], this.pnts[5] ], // Front face +z
25 | [ this.pnts[4], this.pnts[0], this.pnts[1], this.pnts[5] ], // Left Face -x
26 | [ this.pnts[1], this.pnts[0], this.pnts[3], this.pnts[2] ], // Bot Face -y
27 | [ this.pnts[7], this.pnts[3], this.pnts[0], this.pnts[4] ], // Back Face -z
28 | ];
29 |
30 | static rayTest( ray, state, axes, hitPos=[0,0,0] ){
31 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 | const pnts = this.pnts;
33 | const ax = new Array( 6 );
34 | for( let i=0; i < 6; i++ ){
35 | ax[ i ] = vec3_scale( [0,0,0], axes[i], state.axesLengths[ i ] );
36 | }
37 |
38 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | vec3_add_batch( pnts[0], ax[3], ax[4], ax[5], state.position ); // Bottom Face
40 | vec3_add_batch( pnts[1], ax[3], ax[4], ax[2], state.position );
41 | vec3_add_batch( pnts[2], ax[0], ax[4], ax[2], state.position );
42 | vec3_add_batch( pnts[3], ax[0], ax[4], ax[5], state.position );
43 |
44 | vec3_add_batch( pnts[4], ax[3], ax[1], ax[5], state.position ); // Top Face
45 | vec3_add_batch( pnts[5], ax[3], ax[1], ax[2], state.position );
46 | vec3_add_batch( pnts[6], ax[0], ax[1], ax[2], state.position );
47 | vec3_add_batch( pnts[7], ax[0], ax[1], ax[5], state.position );
48 |
49 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50 | let f;
51 | for( let i=0; i < 6; i++ ){
52 | f = this.faces[ i ];
53 | if( intersectQuad( ray, f[0], f[1], f[2], f[3], hitPos, true ) !== null ){
54 | return i;
55 | }
56 | }
57 |
58 | return -1;
59 | }
60 |
61 | static rayDrag( ray, origin, axes, iAxis, initHit, debug ){
62 |
63 |
64 | // if( iAxis === 3 ) return null;
65 |
66 | // debug.ln.reset();
67 | // debug.pnt.reset();
68 |
69 | const a = vec3_scaleAndAdd( [0,0,0], origin, axes[ iAxis ], 1000 );
70 | const b = vec3_scaleAndAdd( [0,0,0], origin, axes[ iAxis ], -1000 );
71 |
72 | // debug.ln.add( a, b, 0x00ff00 );
73 |
74 | const result = new NearSegmentResult();
75 |
76 | if( nearSegment( ray, a, b, result ) ){
77 |
78 | //debug.pnt.add( result.segPosition, 0x00ff00, 10 );
79 |
80 |
81 | // const dir = vec3_sub( [0,0,0], result.segPosition, initHit );
82 | //const len = vec3_len( result.segPosition, origin );
83 | const dir = vec3_sub( [0,0,0], result.segPosition, origin );
84 |
85 | if( vec3_dot( dir, axes[ iAxis ] ) >= 0 ){
86 | const len = vec3_len( dir );
87 | if( len >= this.minLen ){
88 | //console.log( len );
89 | return len;
90 | }
91 | }
92 |
93 |
94 |
95 | //const len = vec3_len( dir );
96 | // const sign = Math.sign( vec3_dot( dir, axes[ iAxis ] ) );
97 |
98 | // const out = [0,0,0];
99 | // out[ iAxis ] = len * sign;
100 |
101 | //return out;
102 | }
103 |
104 | return null;
105 | }
106 | }
--------------------------------------------------------------------------------
/annotatebox/AnnotateBox.js:
--------------------------------------------------------------------------------
1 | // #region IMPORT
2 | import StateProxy from './lib/StateProxy.js';
3 | import EventDispatcher from './lib/EventDispatcher.js';
4 | import MouseEvents from './lib/MouseEvents.js';
5 | import Actions from './Actions.js';
6 | import { Modes, Axes } from './Structs.js';
7 | import { debounce } from './lib/Func.js';
8 |
9 | import Mesh3js from './render/Mesh3js.js';
10 |
11 | import {
12 | // vec3_add,
13 | vec3_add_batch,
14 | // vec3_sub,
15 | // vec3_len,
16 | // vec3_norm,
17 | // vec3_scale,
18 | vec3_transformQuat,
19 | vec3_negate,
20 | // vec3_sqrLen,
21 | // vec3_copy,
22 | // quat_mul,
23 | // quat_copy,
24 | // quat_setAxisAngle,
25 | // quat_normalize,
26 | } from './lib/Maths.js';
27 |
28 | // #endregion
29 |
30 | export default function AnnotateBox( oCamera, oRenderer, debug ){
31 | // #region MAIN
32 | const self = {};
33 | const state = StateProxy.new({
34 | position : [0,0,0],
35 | rotation : [0,0,0,1],
36 | axesLengths : [1,1,1,1,1,1], // xp, yp, zp, xn, yn, zn
37 |
38 | isActive : true,
39 | isDragging : false,
40 |
41 | selectMode : Modes.None,
42 | selectAxis : Axes.None,
43 | rayHitPos : null,
44 |
45 | axisLen : 1, // How long the axis line should be on the UI
46 | debug : debug,
47 | });
48 |
49 | const events = EventDispatcher();
50 | const actions = Actions( self, state, events );
51 | const mouse = MouseEvents( oCamera, oRenderer, actions, state, true );
52 | const mesh = new Mesh3js();
53 | // #endregion
54 |
55 | // #region METHODS
56 | const getAxes = ()=>{
57 | const x = vec3_transformQuat( [0,0,0], [1,0,0], state.rotation );
58 | const y = vec3_transformQuat( [0,0,0], [0,1,0], state.rotation );
59 | const z = vec3_transformQuat( [0,0,0], [0,0,1], state.rotation );
60 | return [
61 | x, y, z,
62 | vec3_negate( [0,0,0], x ),
63 | vec3_negate( [0,0,0], y ),
64 | vec3_negate( [0,0,0], z ),
65 | ];
66 | };
67 |
68 | const render = ()=>mesh.render( self );
69 | const renderDelay = debounce( render, 5 );
70 | // const getPoints = ()=>{
71 | // const axes = getAxes();
72 | // return [
73 | // vec3_add_batch( [0,0,0], axes[3], axes[4], axes[5], state.position ),
74 | // vec3_add_batch( [0,0,0], axes[3], axes[4], axes[2], state.position ),
75 | // vec3_add_batch( [0,0,0], axes[0], axes[4], axes[2], state.position ),
76 | // vec3_add_batch( [0,0,0], axes[0], axes[4], axes[5], state.position ),
77 |
78 | // vec3_add_batch( [0,0,0], axes[3], axes[1], axes[5], state.position ),
79 | // vec3_add_batch( [0,0,0], axes[3], axes[1], axes[2], state.position ),
80 | // vec3_add_batch( [0,0,0], axes[0], axes[1], axes[2], state.position ),
81 | // vec3_add_batch( [0,0,0], axes[0], axes[1], axes[5], state.position ),
82 | // ];
83 | // };
84 | // #endregion
85 |
86 | // #region EVENTS
87 | state.$.on( 'change', e=>{
88 | // console.log( 'chg', e.detail );
89 | switch( e.detail.prop ){
90 | case 'position' :
91 | case 'rotation' :
92 | case 'selectMode' :
93 | case 'selectAxis' : renderDelay(); break;
94 | case 'axesLengths' : render(); break;
95 | }
96 | });
97 | // #endregion
98 |
99 | // #region Export
100 | self.state = state;
101 | self.getAxes = getAxes;
102 | self.mesh = mesh;
103 | self.events = events;
104 |
105 | self.getPosition = ()=>state.position.slice(),
106 | self.setPosition = v=>{ state.position = [ v[0], v[1], v[2] ]; return self; },
107 | self.getRotation = ()=>state.rotation.slice(),
108 | self.setRotation = v=>{ state.rotation = [ v[0], v[1], v[2], v[3] ]; return self; },
109 |
110 | render();
111 | return self;
112 | // #endregion
113 | }
--------------------------------------------------------------------------------
/src2/state/RotationState.js:
--------------------------------------------------------------------------------
1 | import {
2 | vec3_add,
3 | vec3_sub,
4 | vec3_len,
5 | vec3_copy,
6 | vec3_transformQuat,
7 | // vec3_norm,
8 | vec3_dot,
9 | vec3_scale,
10 | // vec3_scaleAndAdd,
11 | vec3_sqrLen,
12 | // quat_mul,
13 | // quat_normalize,
14 | // quat_copy,
15 | // quat_sqrLen,
16 | quat_setAxisAngle,
17 | } from '../../src/Maths.js';
18 |
19 | import {
20 | // Ray,
21 | intersectPlane,
22 | // intersectTri,
23 | // intersectSphere,
24 | // nearPoint,
25 | // NearSegmentResult,
26 | // nearSegment,
27 | } from '../../src/RayIntersection.js';
28 |
29 | export default class RotationState{
30 | minRayDistance = 0.2;
31 |
32 | value = [0,0,0,1];
33 | xAxis = [1,0,0]; // Actual Axis of rotation
34 | yAxis = [0,1,0];
35 | zAxis = [0,0,1];
36 |
37 | xAxisScl = [1,0,0]; // Scaled to Camera Distance & Radius
38 | yAxisScl = [0,1,0];
39 | zAxisScl = [0,0,1];
40 |
41 | xAxisSclDir = [1,0,0]; // Normalized, no radius applied
42 | yAxisSclDir = [0,1,0];
43 | zAxisSclDir = [0,0,1];
44 |
45 | aryAxisSclDir = [ this.xAxisSclDir, this.yAxisSclDir, this.zAxisSclDir ];
46 | aryAxis = [ this.xAxis, this.yAxis, this.zAxis ];
47 |
48 | set( v ){
49 | this.value[ 0 ] = v[ 0 ];
50 | this.value[ 1 ] = v[ 1 ];
51 | this.value[ 2 ] = v[ 2 ];
52 | this.value[ 3 ] = v[ 3 ];
53 | }
54 |
55 | setAxisAngle( axis, rad ){
56 | quat_setAxisAngle( this.value, axis, rad );
57 | this.updateAxis();
58 | }
59 |
60 | updateAxis(){
61 | vec3_transformQuat( this.xAxis, [1,0,0], this.value );
62 | vec3_transformQuat( this.yAxis, [0,1,0], this.value );
63 | vec3_transformQuat( this.zAxis, [0,0,1], this.value );
64 | }
65 |
66 | // Update acaled axis directions based on the camera
67 | updateScale( man ){
68 | vec3_scale( this.xAxisSclDir, this.xAxis, man.cameraVecScale[ 0 ] );
69 | vec3_scale( this.yAxisSclDir, this.yAxis, man.cameraVecScale[ 1 ] );
70 | vec3_scale( this.zAxisSclDir, this.zAxis, man.cameraVecScale[ 2 ] );
71 |
72 | vec3_scale( this.xAxisScl, this.xAxisSclDir, man.radius );
73 | vec3_scale( this.yAxisScl, this.yAxisSclDir, man.radius );
74 | vec3_scale( this.zAxisScl, this.zAxisSclDir, man.radius );
75 | }
76 |
77 | rayTest( ray, state, out=[0,0,0] ){
78 | const minRange = state.cameraScale * this.minRayDistance;
79 | const radius = state.cameraScale * state.radius;
80 | const pos = state.position.value;
81 | const hitDir = [0,0,0];
82 | const hitPos = [0,0,0];
83 |
84 | let t;
85 | let dist;
86 |
87 | for( let i=0; i < 3; i++ ){
88 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89 | // First test against the plane using the axis as the plane normal
90 | t = intersectPlane( ray, pos, this.aryAxisSclDir[i] );
91 | if( t === null ) continue;
92 |
93 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94 | // Next do a circle radius test of the hit point to plane origin
95 | ray.posAt( t, hitPos );
96 | dist = vec3_len( pos, hitPos );
97 |
98 | if( Math.abs( dist - radius ) <= minRange ){
99 | // Inside circle, Check if in the positive side of
100 | // the hemisphere using the next axis direction
101 | vec3_sub( hitDir, hitPos, pos );
102 |
103 | if( vec3_dot( hitDir, this.aryAxisSclDir[ ( i + 1 ) % 3 ] ) >= 0 ){
104 |
105 | // Do the other hemisphere check with the remaining axis
106 | if( vec3_dot( hitDir, this.aryAxisSclDir[ ( i + 2 ) % 3 ] ) >= 0 ){
107 | // Debug.pnt2.add( hitPos, 0x00ff00, 5 );
108 | // Debug.pnt2._updateGeometry();
109 | vec3_copy( out, hitPos );
110 | return i;
111 | }
112 |
113 | }
114 | }
115 | }
116 |
117 | return -1;
118 | }
119 |
120 | }
--------------------------------------------------------------------------------
/src3/Manipulator3D copy.js:
--------------------------------------------------------------------------------
1 | // import ManipulatorState from './state/ManipulatorState.js';
2 | // import ManipulatorActions from './ManipulatorActions.js';
3 | // import MouseEvents from './MouseEvents.js';
4 | // import Tracer from './Tracer.js';
5 |
6 | import StateProxy from './lib/StateProxy.js';
7 | import MouseEvents from './lib/MouseEvents.js';
8 |
9 | // https://github.com/pmndrs/drei/blob/master/src/core/pivotControls/AxisRotator.tsx
10 |
11 | export default class Manipulator3D{
12 | // #region MAIN
13 | constructor( camera, renderer, loadMesh=true ){
14 | this.target = null;
15 | this.camera = camera;
16 | this.mouseEvents = new MouseEvents( this, renderer, false );
17 |
18 | this.state = StateProxy.new({
19 | position : [0,0,0],
20 | rotation : [0,0,0,1],
21 | scale : [1,1,1],
22 | isActive : false,
23 | isDragging : false,
24 | });
25 |
26 | this.settings = {
27 | };
28 | }
29 | // #endregion
30 |
31 | // #region METHODS
32 | attach( obj ){ this.target = obj; return this; }
33 | detach(){ this.target = null; return this; }
34 | // #endregion
35 |
36 | // #region GETTERS / SETTERS
37 | // #endregion
38 |
39 | // #region CAMERA
40 | updateFromCamera( pos, rot ){
41 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42 | // Only update if the camera has changed since last update
43 | if(
44 | Math.abs( vec3_sqrLen( pos, this.cameraPos ) ) <= 0.000001 &&
45 | Math.abs( quat_sqrLen( rot, this.cameraRot ) ) <= 0.000001
46 | ) return;
47 |
48 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49 | vec3_copy( this.cameraPos, pos ); // Save camera state
50 | quat_copy( this.cameraRot, rot );
51 | this.updateCameraScale(); // Update cameral scaling
52 | this.updateData(); // Update other bits of data
53 | return this;
54 | }
55 |
56 | updateCameraScale(){
57 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58 | // Adjust the scale to keep the gizmo as the same size no matter how far the camera goes
59 | const eyeDir = vec3_sub( [0,0,0], this.cameraPos, this.position.value );
60 | const eyeLen = vec3_len( eyeDir );
61 | this.cameraScale = eyeLen / this.cameraFactor;
62 |
63 | vec3_norm( eyeDir, eyeDir ); // Normalize for DOT Checks
64 | vec3_scale( this.cameraVecScale, [1,1,1], this.cameraScale );
65 |
66 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67 | // Flip viewing to the opposite side
68 | if( vec3_dot( eyeDir, [1,0,0] ) < this.cameraFlipDMin ) this.cameraVecScale[0] = -this.cameraVecScale[0];
69 | if( vec3_dot( eyeDir, [0,1,0] ) < this.cameraFlipDMin ) this.cameraVecScale[1] = -this.cameraVecScale[1];
70 | if( vec3_dot( eyeDir, [0,0,1] ) < this.cameraFlipDMin ) this.cameraVecScale[2] = -this.cameraVecScale[2];
71 | }
72 | // #endregion
73 |
74 | // #region DRAGGING
75 | startDrag(){
76 | this.state.isDragging = true;
77 | // this.trace.prepare( this );
78 | // this.state.doCache();
79 |
80 | // this.emit( 'dragstart' );
81 | }
82 |
83 | endDrag(){
84 | this.state.isDragging = false;
85 | //console.log( 'endDrag' );
86 |
87 | // // onTranslate updates position, need to recalculate at the end of dragging
88 | // // for intersection tests to be accurate.
89 | // this.data.calcAxesPosition();
90 |
91 | // // When doing dragging away from ui, the hover event won't trigger to undo
92 | // // visual states, so call method at the end of the dragging to tidy things up.
93 | // this.data.resetState();
94 | // this.mesh.showGizmo();
95 |
96 | this.emit( 'dragend' );
97 | }
98 | // #endregion
99 |
100 | // #region OUTER EVENTS
101 | // TODO : Maybe use EventTarget instead of body
102 | on( evtName, fn ){ document.body.addEventListener( evtName, fn ); return this; }
103 | off( evtName, fn ){ document.body.removeEventListener( evtName, fn ); return this; }
104 | emit( evtName, detail=null ){
105 | document.body.dispatchEvent( new CustomEvent( evtName, { detail, bubbles:true, cancelable:true, composed:false } ) );
106 | }
107 | // #endregion
108 | }
--------------------------------------------------------------------------------
/src3/Actions.js:
--------------------------------------------------------------------------------
1 | // #region IMPORTS
2 | import Translation from './mod/Translation.js';
3 | import Rotation from './mod/Rotation.js';
4 | import Scale from './mod/Scale.js';
5 | import Plane from './mod/Plane.js';
6 |
7 | import DragAction from './DragAction.js';
8 | import { Modes, Axes } from './Structs.js';
9 |
10 | import {
11 | // vec3_add,
12 | // vec3_sub,
13 | // vec3_len,
14 | // vec3_norm,
15 | // vec3_scale,
16 | vec3_transformQuat,
17 | vec3_sqrLen,
18 | // vec3_copy,
19 | // quat_mul,
20 | // quat_copy,
21 | // quat_setAxisAngle,
22 | // quat_normalize,
23 | } from './lib/Maths.js';
24 | // #endregion
25 |
26 | export default function Actions( state, events ){
27 | // #region MAIN
28 | const dragging = DragAction( state, events );
29 | const axes = [
30 | [1,0,0],
31 | [0,1,0],
32 | [0,0,1],
33 | ];
34 | // #endregion
35 |
36 | // #region HELPERS
37 | const rayIntersect = ray => {
38 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | // Get the current state axis from rotation
40 | vec3_transformQuat( axes[0], [1,0,0], state.rotation );
41 | vec3_transformQuat( axes[1], [0,1,0], state.rotation );
42 | vec3_transformQuat( axes[2], [0,0,1], state.rotation );
43 |
44 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | // Test Translation Segments
46 | const posHit = [Infinity,Infinity,Infinity];
47 | const posAxis = Translation.rayTest( ray, state, axes, posHit );
48 | const posDist = ( posAxis === -1 )? Infinity : vec3_sqrLen( ray.posStart, posHit );
49 |
50 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 | // Test Rotation Arcs
52 | const rotHit = [Infinity,Infinity,Infinity];
53 | const rotAxis = Rotation.rayTest( ray, state, axes, rotHit );
54 | const rotDist = ( rotAxis === -1 )? Infinity : vec3_sqrLen( ray.posStart, rotHit );
55 |
56 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 | // Test Scale Points
58 | const sclHit = [Infinity,Infinity,Infinity];
59 | const sclAxis = Scale.rayTest( ray, state, axes, sclHit );
60 |
61 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62 | const plnHit = [Infinity,Infinity,Infinity];
63 | const plnAxis = Plane.rayTest( ray, state, axes, plnHit );
64 | const plnDist = ( plnAxis === -1 )? Infinity : vec3_sqrLen( ray.posStart, plnHit );
65 |
66 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67 | //console.log( 'Position', posAxis, posDist, 'Rotation', rotAxis, rotDist );
68 |
69 | if( posAxis === Axes.None && rotAxis === Axes.None && sclAxis === Axes.None && plnAxis === Axes.None ){
70 | state.selectAxis = Axes.None;
71 | state.selectMode = Modes.None;
72 | return false;
73 | }
74 |
75 | if( sclAxis !== -1 ){
76 | state.selectAxis = sclAxis;
77 | state.selectMode = Modes.Scale;
78 | state.rayHitPos = sclHit;
79 |
80 | }else if( posAxis !== -1 && posDist < rotDist && posDist < plnDist ){
81 | state.selectAxis = posAxis;
82 | state.selectMode = Modes.Translate;
83 | state.rayHitPos = posHit;
84 |
85 | }else if( rotAxis !== -1 && rotDist < plnDist ){
86 | state.selectAxis = rotAxis;
87 | state.selectMode = Modes.Rotate;
88 | state.rayHitPos = rotHit;
89 |
90 | }else if( plnAxis !== -1 ){
91 | state.selectAxis = plnAxis;
92 | state.selectMode = Modes.Plane;
93 | state.rayHitPos = plnHit;
94 | }
95 |
96 | return true;
97 | };
98 | // #endregion
99 |
100 | // #region EXPORT
101 | return {
102 | rayDown( ray ){
103 | if( state.isActive && rayIntersect( ray ) ){
104 | dragging.start();
105 | return true;
106 | }
107 | return false;
108 | },
109 |
110 | rayUp( ray ){
111 | if( state.isDragging ) dragging.stop ();
112 | },
113 |
114 | rayMove( ray ){
115 | if( !state.isActive || !state.isDragging ) return false;
116 | return dragging.move( ray );
117 | },
118 |
119 | rayHover( ray ){
120 | return ( state.isActive && !state.isDragging )? rayIntersect( ray ) : false;
121 | },
122 | };
123 | // #endregion
124 | }
125 |
--------------------------------------------------------------------------------
/src3/Manipulator3D.js:
--------------------------------------------------------------------------------
1 | // #region IMPORTS
2 | import { Vector3, Quaternion } from 'three';
3 |
4 | import StateProxy from './lib/StateProxy.js';
5 | import MouseEvents from './lib/MouseEvents.js';
6 | import EventDispatcher from './lib/EventDispatcher.js';
7 | import Mesh3JS from './render/Mesh3JS.js';
8 | import { Modes, Axes } from './Structs.js';
9 | import Actions from './Actions.js';
10 | import { debounce } from './lib/Func.js';
11 |
12 | import {
13 | // vec3_add,
14 | vec3_sub,
15 | vec3_len,
16 | // vec3_norm,
17 | // vec3_scale,
18 | // vec3_transformQuat,
19 | // vec3_sqrLen,
20 | // vec3_copy,
21 | // quat_mul,
22 | // quat_copy,
23 | // quat_setAxisAngle,
24 | // quat_normalize,
25 | } from './lib/Maths.js';
26 | // #endregion
27 |
28 | let Debug;
29 | export default function Manipulator3D( oCamera, oRenderer, debug ){
30 | // #region MAIN
31 | Debug = debug;
32 | const state = StateProxy.new({
33 | position : [0,0,0],
34 | rotation : [0,0,0,1],
35 | scale : [1,1,1],
36 | isActive : true,
37 | isDragging : false,
38 | target : null,
39 |
40 | rayHitPos : [0,0,0],
41 |
42 | selectMode : Modes.None,
43 | selectAxis : Axes.None,
44 |
45 | usePosition : true,
46 | useRotation : true,
47 | useScale : true,
48 |
49 | cameraFactor : 9,
50 | cameraScale : 1,
51 |
52 | axisLength : 0.85,
53 | arcRadius : 1.08,
54 | pntRadius : 1,
55 | scaleIncStep : 1.0,
56 | });
57 |
58 | const events = EventDispatcher();
59 | const actions = Actions( state, events );
60 | const mouse = MouseEvents( oCamera, oRenderer, actions, state, true );
61 | const mesh = new Mesh3JS( state );
62 | // #endregion
63 |
64 | // #region FUNCS
65 | const cameraScaleUpdate = ()=>{
66 | const scl = computeCameraScale( oCamera, state );
67 | mesh.scale.set( scl, scl, scl );
68 | }
69 |
70 | const render = debounce( ()=>{ mesh.render( state ); }, 5 );
71 | const update = ()=>{ cameraScaleUpdate(); };
72 | // #endregion
73 |
74 | // #region EVENTS
75 | state.$.on( 'change', e=>{
76 | // console.log( 'chg', e.detail );
77 | switch( e.detail.prop ){
78 | case 'rotation' :
79 | case 'position' :
80 | case 'selectMode' : render(); break;
81 | }
82 | })
83 |
84 | .on( 'positionChange', ()=>{
85 | if( state.target ) state.target.position.fromArray( state.position );
86 | events.emit( 'transform', { position: state.position.slice( 0 ) } );
87 | })
88 |
89 | .on( 'rotationChange', ()=>{
90 | if( state.target ) state.target.quaternion.fromArray( state.rotation );
91 | events.emit( 'transform', { rotation: state.rotation.slice( 0 ) } );
92 | })
93 |
94 | .on( 'scaleChange', ()=>{
95 | if( state.target ) state.target.scale.fromArray( state.scale );
96 | events.emit( 'transform', { scale: state.scale.slice( 0 ) } );
97 | })
98 | ;
99 | // #endregion
100 |
101 | // #region EXPORT
102 | const self = {
103 | events,
104 | state,
105 | update,
106 | mesh,
107 |
108 | setRotation : v=>{ state.rotation = [ v[0], v[1], v[2], v[3] ]; return self; },
109 | setPosition : v=>{ state.position = [ v[0], v[1], v[2] ]; return self; },
110 | attach : o=>{
111 | state.target = o;
112 | state.position = o.position.toArray();
113 | state.rotation = o.quaternion.toArray();
114 | state.scale = o.scale.toArray();
115 | return self;
116 | },
117 | };
118 | return self;
119 | // #endregion
120 | };
121 |
122 | // #region HELPERS
123 | function computeCameraScale( camera, state ){
124 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125 | // Need world space info incase camera is attached to something
126 | const wPos = new Vector3();
127 | //const wRot = new Quaternion();
128 | camera.getWorldPosition( wPos );
129 | //camera.getWorldQuaternion( wRot );
130 |
131 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132 | const eyeDir = vec3_sub( [0,0,0], wPos.toArray(), state.position );
133 | const eyeLen = vec3_len( eyeDir );
134 | const cameraScale = eyeLen / state.cameraFactor;
135 |
136 | //state.$.update( { cameraScale } );
137 | state.cameraScale = cameraScale;
138 | return cameraScale;
139 | }
140 | // #endregion
141 |
--------------------------------------------------------------------------------
/test/new.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
147 |
--------------------------------------------------------------------------------
/annotatebox/render/Mesh3js.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import FloorCube from '../../src3/render/FloorCube.js';
3 | import ScaleCube from './ScaleCube.js';
4 | import ShapePointsMesh from '../../test/_lib/ShapePointsMesh.js';
5 |
6 | import { Modes, Axes } from '../Structs.js';
7 |
8 | import {
9 | // vec3_add,
10 | // vec3_add_batch,
11 | // vec3_sub,
12 | // vec3_len,
13 | // vec3_norm,
14 | vec3_scale,
15 | vec3_scaleAndAdd,
16 | // vec3_transformQuat,
17 | // vec3_negate,
18 | // vec3_sqrLen,
19 | // vec3_copy,
20 | // quat_mul,
21 | // quat_copy,
22 | // quat_setAxisAngle,
23 | // quat_normalize,
24 | } from '../lib/Maths.js';
25 |
26 | function newMesh( geo, color, pos=null, rot=null, order=0 ){
27 | const mat = new THREE.MeshBasicMaterial( {
28 | depthTest : false,
29 | depthWrite : false,
30 | fog : false,
31 | toneMapped : false,
32 | transparent : true,
33 | side : THREE.DoubleSide,
34 | opacity : 0.9,
35 | color : color,
36 | } );
37 |
38 | const mesh = new THREE.Mesh( geo, mat );
39 | mesh.renderOrder = order;
40 | if( pos ) mesh.position.fromArray( pos );
41 | if( rot ) mesh.rotation.fromArray( rot );
42 | return mesh;
43 | }
44 |
45 |
46 | export default class Mesh3js extends THREE.Group{
47 | axisColors = [ 0x81D773, 0x6DA9EA, 0xF7716A ];
48 | axes = [ [1,0,0], [0,1,0], [0,0,1] ];
49 | axisLines = [];
50 | axisArcs = [];
51 |
52 | dmesh = new ScaleCube();
53 | pnt = new ShapePointsMesh();//.useDepth( true );
54 |
55 | constructor(){
56 | super();
57 | this.add( this.pnt );
58 | this.add( this.dmesh.mesh );
59 |
60 |
61 | const PIH = Math.PI * 0.5;
62 | const PIY = Math.PI * 0.375;
63 | const PIQ = Math.PI * 0.25;
64 | const PIX = Math.PI * 0.125;
65 | const PI = Math.PI;
66 |
67 | const geoBox = FloorCube.geo( 0.1, 1.0, 0.1 );
68 | const xpBox = newMesh( geoBox, this.axisColors[0], null, [0,0,-PIH] );
69 | const ypBox = newMesh( geoBox, this.axisColors[1], null );
70 | const zpBox = newMesh( geoBox, this.axisColors[2], null, [PIH,0,0] );
71 | this.add( xpBox, ypBox, zpBox );
72 | this.axisLines.push( xpBox, ypBox, zpBox );
73 |
74 | const geoArc = new THREE.TorusGeometry( 1, 0.1, 3, 10, PIQ );
75 | const zArc = newMesh( geoArc, this.axisColors[2], [1,0,0], [0,0,-PIX] );
76 | const yArc = newMesh( geoArc, this.axisColors[1], [0,0,1], [PIH,0,PIY] );
77 | const xArc = newMesh( geoArc, this.axisColors[0], [0,1,0], [0,PIH,PIY] );
78 | this.add( xArc, yArc, zArc );
79 | this.axisArcs.push( xArc, yArc, zArc );
80 | }
81 |
82 | render( abox ){
83 | this.pnt.reset();
84 |
85 | const tmp = [0,0,0];
86 | const pos = abox.getPosition();
87 | const rot = abox.getRotation();
88 | const lens = abox.state.axesLengths;
89 | const selAxis = abox.state.selectAxis;
90 | const selMode = abox.state.selectMode;
91 |
92 | //const axes = abox.getAxes();
93 | //const pnts = abox.getPoints();
94 |
95 | this.position.fromArray( pos );
96 | this.quaternion.fromArray( rot );
97 |
98 | this.dmesh.setAxesLengths( lens );
99 | this.dmesh.selectFace( (( selMode === Modes.Scale )? selAxis : -1) );
100 |
101 | for( let i=0; i < 3; i++ ){
102 | vec3_scale( tmp, this.axes[ i ], lens[ i ] );
103 | this.axisLines[ i ].position.fromArray( tmp );
104 | this.axisLines[ i ].material.color.setHex( ( selMode === Modes.Translate && i === selAxis)? 0xffffff : this.axisColors[i] );
105 |
106 | const ii = ( i+1 ) % 3;
107 | vec3_scale( tmp, this.axes[ii], lens[ii] );
108 | this.axisArcs[ i ].position.fromArray( tmp );
109 | this.axisArcs[ i ].material.color.setHex( ( selMode === Modes.Rotate && i === selAxis)? 0xffffff : this.axisColors[i] );
110 | }
111 |
112 |
113 |
114 | // const offset = 0.4;
115 | // const tmp = [0,0,0];
116 | this.pnt.add( [0,0,0], 0xffff00, 5 );
117 | // this.pnt.add( vec3_scale( tmp, [1,0,0], lens[ 0 ] + offset), 0xffffff, 8 );
118 | // this.pnt.add( vec3_scale( tmp, [-1,0,0], lens[ 3 ] + offset), 0xffffff, 8 );
119 | // this.pnt.add( vec3_scale( tmp, [0,1,0], lens[ 1 ] + offset), 0xffffff, 8 );
120 | // this.pnt.add( vec3_scale( tmp, [0,-1,0], lens[ 4 ] + offset), 0xffffff, 8 );
121 | // this.pnt.add( vec3_scale( tmp, [0,0,1], lens[ 1 ] + offset), 0xffffff, 8 );
122 | // this.pnt.add( vec3_scale( tmp, [0,0,-1], lens[ 4 ] + offset), 0xffffff, 8 );
123 | }
124 | }
--------------------------------------------------------------------------------
/src2/Tracer.js:
--------------------------------------------------------------------------------
1 | import {
2 | // vec3_add,
3 | vec3_sub,
4 | vec3_len,
5 | vec3_copy,
6 | // vec3_transformQuat,
7 | vec3_norm,
8 | vec3_dot,
9 | vec3_scale,
10 | vec3_scaleAndAdd,
11 | // vec3_sqrLen,
12 | // quat_mul,
13 | // quat_normalize,
14 | // quat_copy,
15 | // quat_sqrLen,
16 | // quat_setAxisAngle,
17 | } from '../src/Maths.js';
18 |
19 | import {
20 | // Ray,
21 | // intersectPlane,
22 | // intersectTri,
23 | // intersectSphere,
24 | // nearPoint,
25 | NearSegmentResult,
26 | nearSegment,
27 | } from '../src/RayIntersection.js';
28 |
29 | import ManipulatorMode from './ManipulatorMode.js';
30 |
31 | export default class Tracer{
32 | // #region MAIN
33 | initPos = [0,0,0]; // Inital Contact Position that started the drag
34 | offset = [0,0,0]; // Offset vector from contact position and gizmo's origin
35 | startPos = [0,0,0]; // Start Position of the trace line
36 | endPos = [0,0,0]; // End Position of the trace line
37 | dir = [0,0,0]; // Direction of trace line
38 | hitPos = [0,0,0]; // Intersection position of mouse ray & trace segment
39 | pos = [0,0,0]; // Final position with offset applied
40 | distance = 0; // Signed distance traveled
41 | // #endregion
42 |
43 | rayIntersect( ray ){
44 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45 | // Trace mouse movement onto trace line
46 | const segResult = new NearSegmentResult();
47 | nearSegment( ray, this.startPos, this.endPos, segResult );
48 | vec3_copy( this.hitPos, segResult.segPosition );
49 |
50 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 | // Distance Traveled
52 | const dir = vec3_sub( [0,0,0], this.hitPos, this.initPos );
53 | const sign = Math.sign( vec3_dot( this.dir, dir ) );
54 | this.distance = vec3_len( dir ) * sign;
55 |
56 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 | // Final Position
58 | vec3_sub( this.pos, this.hitPos, this.offset );
59 |
60 | // Debug.pnt2.reset()
61 | // .add( segResult.segPosition, 0x707070, 8 )
62 | // .add( this.initPos, 0x00ff00, 4 );
63 |
64 | return this.distance;
65 | // console.log( dist );
66 | }
67 |
68 | prepare( man ){
69 | const s = man.state;
70 | vec3_copy( this.initPos, s.contactPos );
71 | vec3_sub( this.offset, this.initPos, s.position.value );
72 |
73 | switch( s.mode ){
74 | // ----------------------------------------------
75 | case ManipulatorMode.Translate:{
76 | vec3_copy( this.dir, s.rotation.aryAxisSclDir[ s.iAxis ] );
77 | vec3_scaleAndAdd( this.startPos, this.initPos, s.rotation.aryAxisSclDir[ s.iAxis ], 1000 );
78 | vec3_scaleAndAdd( this.endPos, this.initPos, s.rotation.aryAxisSclDir[ s.iAxis ], -1000 );
79 | break; }
80 |
81 | // ----------------------------------------------
82 | case ManipulatorMode.Rotate:{
83 | const i = (s.iAxis + 1) % 3;
84 | const ii = (s.iAxis + 2) % 3;
85 | const a = s.position.aryEndPoint[ i ];
86 | const b = s.position.aryEndPoint[ ii ];
87 |
88 | const sn =
89 | Math.sign( s.cameraVecScale[ i ] ) *
90 | Math.sign( s.cameraVecScale[ ii ] );
91 |
92 | vec3_sub( this.dir, b, a );
93 | vec3_norm( this.dir, this.dir );
94 | vec3_scale( this.dir, this.dir, sn );
95 |
96 | vec3_scaleAndAdd( this.startPos, this.initPos, this.dir, 1000 );
97 | vec3_scaleAndAdd( this.endPos, this.initPos, this.dir, -1000 );
98 | break; }
99 |
100 | // ----------------------------------------------
101 | case ManipulatorMode.Scale: break;
102 | }
103 |
104 | // Debug.ln2.reset();
105 | // Debug.ln2.add( this.startPos, this.endPos, 0x707070 );
106 |
107 |
108 | // console.log( s.mode, s.axis )
109 |
110 | //main.state.rotation.aryAxisSclDir[];
111 |
112 | // this.traceLine.isActive = true;
113 | // vec3_copy( this.traceLine.origin, pos );
114 | // vec3_copy( this.traceLine.hitPos, pos );
115 |
116 | // if( axis == -1 ){
117 | // vec3_transformQuat( this.traceLine.dir, [1,0,0], this.lastCamRot );
118 | // }else{
119 | // vec3_copy( this.traceLine.dir, this.axes[axis].dir );
120 | // }
121 |
122 | // vec3_scaleAndAdd( this.traceLine.a, pos, this.traceLine.dir, -1000 );
123 | // vec3_scaleAndAdd( this.traceLine.b, pos, this.traceLine.dir, 1000 );
124 |
125 |
126 | //Debug.pnt2.add( man.state.contactPos, 0x00ff00, 5 );
127 | }
128 | }
--------------------------------------------------------------------------------
/test/_lib/Starter.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
3 |
4 | // #region STARTUP
5 | const mod_path = import.meta.url.substring( 0, import.meta.url.lastIndexOf("/") + 1 );
6 | const css_path = mod_path + "Starter.css";
7 |
8 | (function(){
9 | let link = document.createElement( "link" );
10 | link.rel = "stylesheet";
11 | link.type = "text/css";
12 | link.media = "all";
13 | link.href = css_path;
14 | document.getElementsByTagName( "head" )[0].appendChild( link );
15 | })();
16 | // #endregion /////////////////////////////////////////////////////////////////////////
17 |
18 |
19 | // Boiler Plate Starter for ThreeJS
20 | class Starter{
21 | // #region MAIN
22 | scene = null;
23 | camera = null;
24 | clock = null;
25 | renderer = null;
26 | orbit = null;
27 | render_bind = this.render.bind( this );
28 | onRender = null;
29 | deltaTime = 0;
30 | elapsedTime = 0;
31 |
32 | constructor( config={} ){ // { webgl2:true, grid:true, container:null }
33 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34 | // MAIN
35 | this.scene = new THREE.Scene();
36 | this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.01, 2000 );
37 | this.camera.position.set( 0, 10, 20 );
38 |
39 | this.clock = new THREE.Clock();
40 |
41 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42 | // LIGHTING
43 | let light = new THREE.DirectionalLight( 0xffffff, 0.8 );
44 | light.position.set( 4, 10, 4 );
45 |
46 | this.scene.add( light );
47 | this.scene.add( new THREE.AmbientLight( 0x404040 ) );
48 |
49 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50 | // RENDERER
51 | let options = { antialias:true, alpha:true };
52 |
53 | // THREE.JS can't handle loading into WebGL2 on its own
54 | // Need to create canvas & get the proper context, pass those 2 into 3js
55 | if( config.webgl2 ){
56 | let canvas = document.createElement( "canvas" );
57 | options.canvas = canvas;
58 | options.context = canvas.getContext( "webgl2" );
59 | }
60 |
61 | this.renderer = new THREE.WebGLRenderer( options );
62 | this.renderer.setPixelRatio( window.devicePixelRatio );
63 | this.renderer.setClearColor( 0x3a3a3a, 1 );
64 |
65 | //---------------------------------
66 | // where to add the cnavas object, in a container or in the body.
67 | if( config.container ) config.container.appendChild( this.renderer.domElement );
68 | else document.body.appendChild( this.renderer.domElement );
69 |
70 | //---------------------------------
71 | // Have the canvas set as full screen or fill its container's space
72 | if( config.fullscreen != false ){
73 | this.setSize( window.innerWidth, window.innerHeight );
74 | }else{
75 | // Take the size of the parent element.
76 | const box = this.renderer.domElement.parentNode.getBoundingClientRect();
77 | this.setSize( box.width , box.height );
78 | }
79 |
80 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81 | // MISC
82 | this.orbit = new OrbitControls( this.camera, this.renderer.domElement );
83 | if( config.grid ) this.scene.add( new THREE.GridHelper( 20, 20, 0x0c610c, 0x444444 ) );
84 |
85 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86 | window.addEventListener( 'resize', this.onResize.bind( this ) );
87 | }
88 |
89 | render(){
90 | requestAnimationFrame( this.render_bind );
91 |
92 | this.deltaTime = this.clock.getDelta();
93 | this.elapsedTime = this.clock.elapsedTime;
94 |
95 | if( this.onRender ) this.onRender( this.deltaTime, this.elapsedTime );
96 | this.renderer.render( this.scene, this.camera );
97 | }
98 | // #endregion ////////////////////////////////////////////////////////////////////////////////////////
99 |
100 | // #region METHODS
101 | add( o ){ this.scene.add( o ); return this; }
102 | remove( o ){ this.scene.remove( o ); return this; }
103 |
104 | setCamera( lon, lat, radius, target ){
105 | let phi = ( 90 - lat ) * Math.PI / 180,
106 | theta = ( lon + 180 ) * Math.PI / 180;
107 |
108 | this.camera.position.set(
109 | -(radius * Math.sin( phi ) * Math.sin(theta)),
110 | radius * Math.cos( phi ),
111 | -(radius * Math.sin( phi ) * Math.cos(theta))
112 | );
113 |
114 | if( target ) this.orbit.target.fromArray( target );
115 |
116 | this.orbit.update();
117 | return this;
118 | }
119 |
120 | setSize( w, h ){
121 | this.renderer.setSize( w , h );
122 |
123 | // When changing the canvas size, need to update the Projection Aspect Ratio to render correctly.
124 | this.camera.aspect = w / h;
125 | this.camera.updateProjectionMatrix();
126 | }
127 | // #endregion ////////////////////////////////////////////////////////////////////////////////////////
128 |
129 | // #region EVENTS
130 | onResize( e ){
131 | this.setSize( window.innerWidth, window.innerHeight );
132 | }
133 | //#endregion
134 |
135 | }
136 |
137 | export default Starter;
138 | export { THREE };
--------------------------------------------------------------------------------
/src3/render/Mesh3JS.js:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | Group, Line, Mesh, DoubleSide,
4 | MeshBasicMaterial, LineBasicMaterial,
5 | BufferGeometry, Float32BufferAttribute,
6 | SphereGeometry, TorusGeometry, CylinderGeometry, BoxGeometry, PlaneGeometry, CircleGeometry,
7 | } from 'three';
8 |
9 | import FloorCube from './FloorCube.js';
10 |
11 | function newMesh( geo, color, pos=null, rot=null, order=0 ){
12 | const mat = new MeshBasicMaterial( {
13 | depthTest : false,
14 | depthWrite : false,
15 | fog : false,
16 | toneMapped : false,
17 | transparent : true,
18 | side : DoubleSide,
19 | opacity : 0.9,
20 | color : color,
21 | } );
22 |
23 | const mesh = new Mesh( geo, mat );
24 | mesh.renderOrder = order;
25 | if( pos ) mesh.position.fromArray( pos );
26 | if( rot ) mesh.rotation.fromArray( rot );
27 | return mesh;
28 | }
29 |
30 | export default class Mesh3JS extends Group{
31 | axisColors = [ 0x81D773, 0x6DA9EA, 0xF7716A ];
32 | axisPLines = [];
33 | axisNLines = [];
34 | axisArc = [];
35 | axisPoints = [];
36 | axisPlanes = [];
37 | //onUpdate = null;
38 |
39 | constructor( state ){
40 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 | super();
42 | //this.onUpdate = updateCallback;
43 |
44 | const PIH = Math.PI * 0.5;
45 | const PI = Math.PI;
46 | const arcRadius = 0.8;
47 | const arcThickness = 0.025;
48 | const planeSize = 0.25;
49 | const boxSize = 0.04;
50 | const boxLen = state.axisLength; //0.85;
51 |
52 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | // GEOMETRY
54 | const geoSphere = new SphereGeometry( 0.08, 8, 8 );
55 | const geoArc = new TorusGeometry( arcRadius, arcThickness, 3, 30, PIH );
56 | //const geoPlane = new PlaneGeometry( planeSize, planeSize );
57 | //const geoPlane = new CircleGeometry( planeSize, 6 );
58 | const geoBox = FloorCube.geo( boxSize, boxLen, boxSize );
59 |
60 | const geoTri = new BufferGeometry();
61 | geoTri.setAttribute( 'position', new Float32BufferAttribute( [ 0.15,0.15,0, 0.8,0.15,0, 0.15,0.8,0 ], 3 ) );
62 |
63 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64 | // Translation
65 | const p = 0.0;
66 | const xpBox = newMesh( geoBox, this.axisColors[0], [p,0,0], [0,0,-PIH] );
67 | const ypBox = newMesh( geoBox, this.axisColors[1], [0,p,0] );
68 | const zpBox = newMesh( geoBox, this.axisColors[2], [0,0,p], [PIH,0,0] );
69 | this.add( xpBox, ypBox, zpBox );
70 | this.axisPLines.push( xpBox, ypBox, zpBox );
71 |
72 | const xnBox = newMesh( geoBox, this.axisColors[0], [-p,0,0], [0,0,PIH] );
73 | const ynBox = newMesh( geoBox, this.axisColors[1], [0,-p,0], [0,0,PI] );
74 | const znBox = newMesh( geoBox, this.axisColors[2], [0,0,-p], [-PIH,0,0] );
75 | this.add( xnBox, ynBox, znBox );
76 | this.axisNLines.push( xnBox, ynBox, znBox );
77 |
78 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79 | // Rotation
80 | const r = 0.2;
81 | const zArc = newMesh( geoArc, this.axisColors[2], [r,r,0], [0,0,0] );
82 | const xArc = newMesh( geoArc, this.axisColors[0], [0,r,r], [0,-PIH,0] );
83 | const yArc = newMesh( geoArc, this.axisColors[1], [r,0,r], [PIH,0,0] );
84 |
85 | this.add( xArc, yArc, zArc );
86 | this.axisArc.push( xArc, yArc, zArc );
87 |
88 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89 | // Plane
90 | // const pp = planeSize + 0.15;
91 | // const xPln = newMesh( geoPlane, this.axisColors[0], [0,pp,pp], [0,PIH,0] );
92 | // const yPln = newMesh( geoPlane, this.axisColors[1], [pp,0,pp], [PIH,0,0] );
93 | // const zPln = newMesh( geoPlane, this.axisColors[2], [pp,pp,0] );
94 |
95 | const xPln = newMesh( geoTri, this.axisColors[0], [0,0,0], [0,-PIH,0] );
96 | const yPln = newMesh( geoTri, this.axisColors[1], [0,0,0], [PIH,0,0] );
97 | const zPln = newMesh( geoTri, this.axisColors[2], [0,0,0], [0,0,0] );
98 | this.add( xPln, yPln, zPln );
99 | this.axisPlanes.push( xPln, yPln, zPln );
100 |
101 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102 | // Scale
103 | const s = 1;
104 | const xpScl = newMesh( geoSphere, this.axisColors[0], [s,0,0] );
105 | const xnScl = newMesh( geoSphere, this.axisColors[0], [-s,0,0] );
106 | const ypScl = newMesh( geoSphere, this.axisColors[1], [0,s,0] );
107 | const ynScl = newMesh( geoSphere, this.axisColors[1], [0,-s,0] );
108 | const zpScl = newMesh( geoSphere, this.axisColors[2], [0,0,s] );
109 | const znScl = newMesh( geoSphere, this.axisColors[2], [0,0,-s] );
110 | const mScl = newMesh( geoSphere, 0xffff00, null, null, 1 );
111 |
112 | this.add( mScl, xpScl, xnScl, ypScl, ynScl, zpScl, znScl );
113 | this.axisPoints.push( xpScl, ypScl, zpScl, xnScl, ynScl, znScl, mScl );
114 | }
115 |
116 | render( state ){
117 | const mode = state.selectMode;
118 | const axis = state.selectAxis;
119 |
120 | let c;
121 | this.quaternion.fromArray( state.rotation );
122 | this.position.fromArray( state.position );
123 |
124 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125 | // LINES
126 | for( let i=0; i < 3; i++ ){
127 | this.axisNLines[ i ].material.color.setHex( ( mode === 0 && i === axis )? 0xffffff : this.axisColors[i] );
128 | this.axisPLines[ i ].material.color.setHex( ( mode === 0 && i === axis )? 0xffffff : this.axisColors[i] );
129 | }
130 |
131 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132 | // ARCS
133 | for( let i=0; i < 3; i++ ){
134 | this.axisArc[ i ].material.color.setHex( ( mode === 1 && i === axis)? 0xffffff : this.axisColors[i] );
135 | }
136 |
137 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138 | // PLANES
139 | for( let i=0; i < 3; i++ ){
140 | this.axisPlanes[ i ].material.color.setHex( ( mode === 3 && i === axis)? 0xffffff : this.axisColors[i] );
141 | }
142 |
143 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144 | // POINTS
145 | for( let i=0; i < 3; i++ ){
146 | c = ( mode === 2 && i === axis )? 0xffffff : this.axisColors[ i ];
147 | this.axisPoints[ i ].material.color.setHex( c );
148 | this.axisPoints[ i+3 ].material.color.setHex( c );
149 | }
150 |
151 | c = ( mode === 2 && axis === 3 )? 0xffffff : 0xffff00;
152 | this.axisPoints[ 6 ].material.color.setHex( c );
153 | }
154 |
155 | /* CAN USE THIS OVERRIDE TO REPLACE UPDATE FUNCTION, IT WILL BE CALLED ON EVERY FRAME*/
156 | // updateMatrixWorld(){
157 | // super.updateMatrixWorld( this );
158 | // if( this.onUpdate ) this.onUpdate();
159 | // }
160 | }
--------------------------------------------------------------------------------
/src3/lib/transform.js:
--------------------------------------------------------------------------------
1 | // https://gabormakesgames.com/blog_transforms_transforms.html
2 | // https://gabormakesgames.com/blog_transforms_transform_world.html
3 |
4 | export default class transform{
5 | // #region MAIN
6 | static new(){ return { pos:[0,0,0], rot:[0,0,0,1], scl:[1,1,1] }; }
7 |
8 | static clone( t ){ return { pos:t.pos.slice(), rot:t.rot.slice(), scl:t.scl.slice() }; }
9 |
10 | static reset( t ){
11 | t.pos[0] = 0; t.pos[1] = 0; t.pos[2] = 0;
12 | t.scl[0] = 1; t.scl[1] = 1; t.scl[2] = 1;
13 | t.rot[0] = 0; t.rot[1] = 0; t.rot[2] = 0; t.rot[3] = 1;
14 | }
15 |
16 | static copy( t, out ){
17 | out.pos[ 0 ] = t.pos[ 0 ];
18 | out.pos[ 1 ] = t.pos[ 1 ];
19 | out.pos[ 2 ] = t.pos[ 2 ];
20 |
21 | out.scl[ 0 ] = t.scl[ 0 ];
22 | out.scl[ 1 ] = t.scl[ 1 ];
23 | out.scl[ 2 ] = t.scl[ 2 ];
24 |
25 | out.rot[ 0 ] = t.rot[ 0 ];
26 | out.rot[ 1 ] = t.rot[ 1 ];
27 | out.rot[ 2 ] = t.rot[ 2 ];
28 | out.rot[ 3 ] = t.rot[ 3 ];
29 |
30 | return out;
31 | }
32 | // #endregion
33 |
34 | // #region OPS
35 |
36 | // fromInvert( t: Transform ) : this{
37 | // // Invert Rotation
38 | // this.rot.fromInvert( t.rot );
39 |
40 | // // Invert Scale
41 | // this.scl.fromInvert( t.scl );
42 |
43 | // // Invert Position : rotInv * ( invScl * -Pos )
44 | // this.pos
45 | // .fromNegate( t.pos )
46 | // .mul( this.scl )
47 | // .transformQuat( this.rot );
48 |
49 | // return this;
50 | // }
51 |
52 | // Transform LocalInverse(Transform t) {
53 | // Quaternion invRotation = Inverse(t.rotation);
54 |
55 | // Vector3 invScale = Vector3(0, 0, 0);
56 | // if (t.scale.x != 0) { // Do epsilon comparison here
57 | // invScale.x = 1.0 / t.scale.x
58 | // }
59 | // if (t.scale.y != 0) { // Do epsilon comparison here
60 | // invScale.y = 1.0 / t.scale.y
61 | // }
62 | // if (t.scale.z != 0) { // Do epsilon comparison here
63 | // invScale.z = 1.0 / t.scale.z
64 | // }
65 |
66 | // Vector3 invTranslation = invRotation * (invScale * (-1 * t.translation));
67 |
68 | // Transform result;
69 | // result.position = invTranslation;
70 | // result.rotation = invRotation;
71 | // result.scale = invScale;
72 |
73 | // return result;
74 | // }
75 |
76 |
77 | /*
78 | Matrix ToMatrix(Transform transform) {
79 | // First, extract the rotation basis of the transform
80 | Vector x = Vector(1, 0, 0) * transform.rotation; // Vec3 * Quat (right vector)
81 | Vector y = Vector(0, 1, 0) * transform.rotation; // Vec3 * Quat (up vector)
82 | Vector z = Vector(0, 0, 1) * transform.rotation; // Vec3 * Quat (forward vector)
83 |
84 | // Next, scale the basis vectors
85 | x = x * transform.scale.x; // Vector * float
86 | y = y * transform.scale.y; // Vector * float
87 | z = z * transform.scale.z; // Vector * float
88 |
89 | // Extract the position of the transform
90 | Vector t = transform.position;
91 |
92 | // Create matrix
93 | return Matrix(
94 | x.x, x.y, x.z, 0, // X basis (& Scale)
95 | y.x, y.y, y.z, 0, // Y basis (& scale)
96 | z.x, z.y, z.z, 0, // Z basis (& scale)
97 | t.x, t.y, t.z, 1 // Position
98 | );
99 | }
100 | */
101 |
102 | // fromMul( tp: Transform, tc: Transform ) : this{
103 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
104 | // // POSITION - parent.position + ( parent.rotation * ( parent.scale * child.position ) )
105 | // const v = new Vec3().fromMul( tp.scl, tc.pos ).transformQuat( tp.rot ); // parent.scale * child.position;
106 | // this.pos.fromAdd( tp.pos, v );
107 |
108 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109 | // // SCALE - parent.scale * child.scale
110 | // this.scl.fromMul( tp.scl, tc.scl );
111 |
112 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
113 | // // ROTATION - parent.rotation * child.rotation
114 | // this.rot.fromMul( tp.rot, tc.rot );
115 |
116 | // return this;
117 | // }
118 |
119 | // mul( cr: TVec4 | Transform, cp ?: TVec3, cs ?: TVec3 ) : this{
120 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121 | // // If just passing in Tranform Object
122 | // if( cr instanceof Transform ){
123 | // cp = cr.pos;
124 | // cs = cr.scl;
125 | // cr = cr.rot;
126 | // }
127 |
128 | // if( cr && cp ){
129 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130 | // // POSITION - parent.position + ( parent.rotation * ( parent.scale * child.position ) )
131 | // this.pos.add( new Vec3().fromMul( this.scl, cp ).transformQuat( this.rot ) );
132 |
133 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134 | // // SCALE - parent.scale * child.scale
135 | // if( cs ) this.scl.mul( cs );
136 |
137 | // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138 | // // ROTATION - parent.rotation * child.rotation
139 | // this.rot.mul( cr );
140 | // }
141 |
142 | // return this;
143 | // }
144 |
145 | // addPos( cp: TVec3, ignoreScl=false ) : this{
146 | // //POSITION - parent.position + ( parent.rotation * ( parent.scale * child.position ) )
147 | // if( ignoreScl ) this.pos.add( new Vec3().fromQuat( this.rot, cp ) );
148 | // else this.pos.add( new Vec3().fromMul( cp, this.scl ).transformQuat( this.rot ) );
149 | // return this;
150 | // }
151 |
152 | // #endregion
153 | }
154 |
155 | // // #region TRANSFORMATION
156 | // transformVec3( v: TVec3, out ?: TVec3 ) : TVec3{
157 | // // GLSL - vecQuatRotation(model.rotation, a_position.xyz * model.scale) + model.position;
158 | // // return (out || v)
159 | // // .fromMul( v, this.scl )
160 | // // .transformQuat( this.rot )
161 | // // .add( this.pos );
162 |
163 | // out = out || v;
164 | // vec3.mul( v, this.scl, out );
165 | // vec3.transformQuat( out, this.rot );
166 | // vec3.add( out, this.pos );
167 | // return out;
168 | // }
169 | // // #endregion
170 |
171 | // Vector3 InverseTransformPoint(Transform t, Vector3 point) {
172 | // // Recursive function, apply inverse of parent transform first
173 | // if (t.parent != NULL) {
174 | // point = InverseTransformPoint(t.parent, point)
175 | // }
176 |
177 | // // First, apply the inverse translation of the transform
178 | // point = point - t.position;
179 |
180 | // // Next, apply the inverse rotation of the transform
181 | // Quaternion invRot = Inverse(t.rotation);
182 | // point = point * invRot;
183 |
184 | // // Finally, apply the inverse scale
185 | // point = point / t.scale; // Component wise vector division
186 |
187 | // return point
188 | // }
189 |
190 | /*
191 | World Space Position to Local Space.
192 | V .copy( gBWorld.eye_lid_upper_mid_l.pos ) // World Space Postion
193 | .add( [0, -0.05 * t, 0 ] ) // Change it
194 | .sub( gBWorld.eye_l.pos ) // Subtract from Parent's WS Position
195 | .div( gBWorld.eye_l.scl ) // Div by Parent's WS Scale
196 | .transform_quat( gBWorld.eye_l.rot_inv ); // Rotate by Parent's WS Inverse Rotation
197 | */
--------------------------------------------------------------------------------
/test/annotate_box.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
228 |
--------------------------------------------------------------------------------
/src/RayIntersection.js:
--------------------------------------------------------------------------------
1 | // #region IMPORT
2 | import {
3 | vec3_add,
4 | vec3_sub,
5 | vec3_mul,
6 | vec3_norm,
7 | vec3_copy,
8 | vec3_scale,
9 | vec3_dot,
10 | vec3_cross,
11 | vec3_sqrLen,
12 | vec3_lerp,
13 | vec3_len,
14 | vec4_transformMat4,
15 | mat4_invert,
16 | mat4_mul,
17 | } from './Maths.js';
18 | // #endregion
19 |
20 | export class Ray{
21 | posStart = [0,0,0]; // Origin
22 | posEnd = [0,0,0];
23 | direction = [0,0,0]; // Direction from Start to End
24 | vecLength = [0,0,0]; // Vector Length between start to end
25 |
26 | // #region GETTERS / SETTERS
27 | /** Get position of the ray from T Scale of VecLen */
28 | posAt( t, out){
29 | // RayVecLen * t + RayOrigin
30 | // also works lerp( RayOrigin, RayEnd, t )
31 | out = out || [0,0,0];
32 | out[ 0 ] = this.vecLength[ 0 ] * t + this.posStart[ 0 ];
33 | out[ 1 ] = this.vecLength[ 1 ] * t + this.posStart[ 1 ];
34 | out[ 2 ] = this.vecLength[ 2 ] * t + this.posStart[ 2 ];
35 | return out;
36 | }
37 |
38 | /** Get position of the ray from distance from origin */
39 | directionAt( len, out ){
40 | out = out || [0,0,0];
41 | out[ 0 ] = this.direction[ 0 ] * len + this.posStart[ 0 ];
42 | out[ 1 ] = this.direction[ 1 ] * len + this.posStart[ 1 ];
43 | out[ 2 ] = this.direction[ 2 ] * len + this.posStart[ 2 ];
44 | return out;
45 | }
46 |
47 | fromCaster( caster ){
48 | vec3_copy( this.posStart, caster.ray.origin.toArray() );
49 | vec3_copy( this.direction, caster.ray.direction.toArray() );
50 |
51 | const len = ( caster.far == Infinity )? 1000 : caster.far;
52 | vec3_scale( this.vecLength, this.direction, len );
53 | vec3_add( this.posEnd, this.posStart, this.vecLength );
54 | }
55 |
56 | fromScreenProjection( x, y, w, h, projMatrix, camMatrix ){
57 | // http://antongerdelan.net/opengl/raycasting.html
58 | // Normalize Device Coordinate
59 | const nx = x / w * 2 - 1;
60 | const ny = 1 - y / h * 2;
61 |
62 | // inverseWorldMatrix = invert( ProjectionMatrix * ViewMatrix ) OR
63 | // inverseWorldMatrix = localMatrix * invert( ProjectionMatrix )
64 | const invMatrix = [ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 ];
65 | mat4_invert( invMatrix, projMatrix )
66 | mat4_mul( invMatrix, camMatrix, invMatrix );
67 |
68 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | // https://stackoverflow.com/questions/20140711/picking-in-3d-with-ray-tracing-using-ninevehgl-or-opengl-i-phone/20143963#20143963
70 | // Clip Cords would be [nx,ny,-1,1];
71 | const clipNear = [ nx, ny, -1, 1 ];
72 | const clipFar = [ nx, ny, 1, 1 ];
73 |
74 | // using 4d Homogeneous Clip Coordinates
75 | vec4_transformMat4( clipNear, clipNear, invMatrix );
76 | vec4_transformMat4( clipFar, clipFar, invMatrix );
77 |
78 | // Normalize by using W component
79 | for( let i=0; i < 3; i++){
80 | clipNear[ i ] /= clipNear[ 3 ];
81 | clipFar [ i ] /= clipFar [ 3 ];
82 | }
83 |
84 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85 | vec3_copy( this.posStart, clipNear ); // Starting Point of the Ray
86 | vec3_copy( this.posEnd, clipFar ); // The absolute end of the ray
87 | vec3_sub( this.vecLength, clipFar, clipNear ); // Vector Length
88 | vec3_norm( this.direction, this.vecLength ); // Normalized Vector Length
89 | return this;
90 | }
91 | // #endregion
92 | }
93 |
94 | export function intersectSphere( ray, origin, radius ){
95 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
96 | const radiusSq = radius * radius;
97 | const rayToCenter = vec3_sub( [0,0,0], origin, ray.posStart );
98 | const tProj = vec3_dot( rayToCenter, ray.direction ); // Project the length to the center onto the Ray
99 |
100 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
101 | // Get length of projection point to center and check if its within the sphere
102 | // Opposite^2 = hyptenuse^2 - adjacent^2
103 | const oppLenSq = vec3_sqrLen( rayToCenter ) - ( tProj * tProj );
104 | return !( oppLenSq > radiusSq );
105 | }
106 |
107 | /** T returned is scale to vector length, not direction */
108 | export function intersectPlane( ray, planePos, planeNorm ){
109 | // ((planePos - rayOrigin) dot planeNorm) / ( rayVecLen dot planeNorm )
110 | // pos = t * rayVecLen + rayOrigin;
111 | const denom = vec3_dot( ray.vecLength, planeNorm ); // Dot product of ray Length and plane normal
112 | if( denom <= 0.000001 && denom >= -0.000001 ) return null; // abs(denom) < epsilon, using && instead to not perform absolute.
113 |
114 | const t = vec3_dot( vec3_sub( [0,0,0], planePos, ray.posStart ), planeNorm ) / denom;
115 | return ( t >= 0 )? t : null;
116 | }
117 |
118 | export function intersectTri( ray, v0, v1, v2, out, cullFace=true ){
119 | const v0v1 = vec3_sub( [0,0,0], v1, v0 );
120 | const v0v2 = vec3_sub( [0,0,0], v2, v0 );
121 | const pvec = vec3_cross( [0,0,0], ray.direction, v0v2 );
122 | const det = vec3_dot( v0v1, pvec );
123 |
124 | if( cullFace && det < 0.000001 ) return false;
125 |
126 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
127 | const idet = 1 / det;
128 | const tvec = vec3_sub( [0,0,0], ray.posStart, v0 );
129 | const u = vec3_dot( tvec, pvec ) * idet;
130 |
131 | if( u < 0 || u > 1 ) return false;
132 |
133 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134 | const qvec = vec3_cross( [0,0,0], tvec, v0v1, );
135 | const v = vec3_dot( ray.direction, qvec ) * idet;
136 |
137 | if( v < 0 || u+v > 1 ) return false;
138 |
139 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
140 | if( out ){
141 | const len = vec3_dot( v0v2, qvec ) * idet;
142 | ray.directionAt( len, out );
143 | }
144 |
145 | return true;
146 | }
147 |
148 | export function nearPoint( ray, p, distLimit=0.1 ){
149 | /* closest_point_to_line3D
150 | let dx = bx - ax,
151 | dy = by - ay,
152 | dz = bz - az,
153 | t = ( (px-ax)*dx + (py-ay)*dy + (pz-az)*dz ) / ( dx*dx + dy*dy + dz*dz ) ; */
154 | const v = vec3_sub( [0,0,0], p, ray.posStart );
155 | vec3_mul( v, v, ray.vecLength );
156 |
157 | const t = ( v[0] + v[1] + v[2] ) / vec3_sqrLen( ray.vecLength );
158 |
159 | if( t < 0 || t > 1 ) return null; // Over / Under shoots the Ray Segment
160 | const lenSqr = vec3_sqrLen( ray.posAt( t, v ), p ); // Distance from point to nearest point on ray.
161 |
162 | return ( lenSqr <= (distLimit*distLimit) )? t : null;
163 | }
164 |
165 | export class NearSegmentResult{
166 | segPosition = [0,0,0];
167 | rayPosition = [0,0,0];
168 | distanceSq = 0;
169 | distance = 0;
170 | }
171 |
172 | /** Returns [ T of Segment, T of RayLen ] */
173 | export function nearSegment( ray, p0, p1, results=null){
174 | // http://geomalgorithms.com/a07-_distance.html
175 | const u = vec3_sub( [0,0,0], p1, p0 ),
176 | v = ray.vecLength,
177 | w = vec3_sub( [0,0,0], p0, ray.posStart ),
178 | a = vec3_dot( u, u ), // always >= 0
179 | b = vec3_dot( u, v ),
180 | c = vec3_dot( v, v ), // always >= 0
181 | d = vec3_dot( u, w ),
182 | e = vec3_dot( v, w ),
183 | D = a * c - b * b; // always >= 0
184 |
185 | let tU = 0, // T Of Segment
186 | tV = 0; // T Of Ray
187 |
188 | // Compute the line parameters of the two closest points
189 | if( D < 0.000001 ){ // the lines are almost parallel
190 | tU = 0.0;
191 | tV = ( b > c ? d/b : e/c ); // use the largest denominator
192 | }else{
193 | tU = ( b*e - c*d ) / D;
194 | tV = ( a*e - b*d ) / D;
195 | }
196 |
197 | if( tU < 0 || tU > 1 || tV < 0 || tV > 1 ) return false;
198 |
199 | // Segment Position : u.scale( tU ).add( p0 )
200 | // Ray Position : v.scale( tV ).add( this.origin ) ];
201 | if( results ){
202 | vec3_lerp( results.rayPosition, ray.posStart, ray.posEnd, tV );
203 | vec3_lerp( results.segPosition, p0, p1, tU );
204 | results.distance = vec3_len( results.segPosition, results.rayPosition );
205 | }
206 |
207 | return true;
208 | }
--------------------------------------------------------------------------------
/src3/lib/RayIntersection.js:
--------------------------------------------------------------------------------
1 | // #region IMPORT
2 | import {
3 | vec3_add,
4 | vec3_sub,
5 | vec3_mul,
6 | vec3_norm,
7 | vec3_copy,
8 | vec3_scale,
9 | vec3_dot,
10 | vec3_cross,
11 | vec3_sqrLen,
12 | vec3_lerp,
13 | vec3_len,
14 | vec4_transformMat4,
15 | mat4_invert,
16 | mat4_mul,
17 | } from './Maths.js';
18 | // #endregion
19 |
20 | export class Ray{
21 | posStart = [0,0,0]; // Origin
22 | posEnd = [0,0,0];
23 | direction = [0,0,0]; // Direction from Start to End
24 | vecLength = [0,0,0]; // Vector Length between start to end
25 |
26 | // #region GETTERS / SETTERS
27 | /** Get position of the ray from T Scale of VecLen */
28 | posAt( t, out){
29 | // RayVecLen * t + RayOrigin
30 | // also works lerp( RayOrigin, RayEnd, t )
31 | out = out || [0,0,0];
32 | out[ 0 ] = this.vecLength[ 0 ] * t + this.posStart[ 0 ];
33 | out[ 1 ] = this.vecLength[ 1 ] * t + this.posStart[ 1 ];
34 | out[ 2 ] = this.vecLength[ 2 ] * t + this.posStart[ 2 ];
35 | return out;
36 | }
37 |
38 | /** Get position of the ray from distance from origin */
39 | directionAt( len, out ){
40 | out = out || [0,0,0];
41 | out[ 0 ] = this.direction[ 0 ] * len + this.posStart[ 0 ];
42 | out[ 1 ] = this.direction[ 1 ] * len + this.posStart[ 1 ];
43 | out[ 2 ] = this.direction[ 2 ] * len + this.posStart[ 2 ];
44 | return out;
45 | }
46 |
47 | fromCaster( caster ){
48 | vec3_copy( this.posStart, caster.ray.origin.toArray() );
49 | vec3_copy( this.direction, caster.ray.direction.toArray() );
50 |
51 | const len = ( caster.far == Infinity )? 1000 : caster.far;
52 | vec3_scale( this.vecLength, this.direction, len );
53 | vec3_add( this.posEnd, this.posStart, this.vecLength );
54 | }
55 |
56 | fromScreenProjection( x, y, w, h, projMatrix, camMatrix ){
57 | // http://antongerdelan.net/opengl/raycasting.html
58 | // Normalize Device Coordinate
59 | const nx = x / w * 2 - 1;
60 | const ny = 1 - y / h * 2;
61 |
62 | // inverseWorldMatrix = invert( ProjectionMatrix * ViewMatrix ) OR
63 | // inverseWorldMatrix = localMatrix * invert( ProjectionMatrix )
64 | const invMatrix = [ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 ];
65 | mat4_invert( invMatrix, projMatrix )
66 | mat4_mul( invMatrix, camMatrix, invMatrix );
67 |
68 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69 | // https://stackoverflow.com/questions/20140711/picking-in-3d-with-ray-tracing-using-ninevehgl-or-opengl-i-phone/20143963#20143963
70 | // Clip Cords would be [nx,ny,-1,1];
71 | const clipNear = [ nx, ny, -1, 1 ];
72 | const clipFar = [ nx, ny, 1, 1 ];
73 |
74 | // using 4d Homogeneous Clip Coordinates
75 | vec4_transformMat4( clipNear, clipNear, invMatrix );
76 | vec4_transformMat4( clipFar, clipFar, invMatrix );
77 |
78 | // Normalize by using W component
79 | for( let i=0; i < 3; i++){
80 | clipNear[ i ] /= clipNear[ 3 ];
81 | clipFar [ i ] /= clipFar [ 3 ];
82 | }
83 |
84 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85 | vec3_copy( this.posStart, clipNear ); // Starting Point of the Ray
86 | vec3_copy( this.posEnd, clipFar ); // The absolute end of the ray
87 | vec3_sub( this.vecLength, clipFar, clipNear ); // Vector Length
88 | vec3_norm( this.direction, this.vecLength ); // Normalized Vector Length
89 | return this;
90 | }
91 | // #endregion
92 | }
93 |
94 | export function intersectSphere( ray, origin, radius ){
95 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
96 | const radiusSq = radius * radius;
97 | const rayToCenter = vec3_sub( [0,0,0], origin, ray.posStart );
98 | const tProj = vec3_dot( rayToCenter, ray.direction ); // Project the length to the center onto the Ray
99 |
100 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
101 | // Get length of projection point to center and check if its within the sphere
102 | // Opposite^2 = hyptenuse^2 - adjacent^2
103 | const oppLenSq = vec3_sqrLen( rayToCenter ) - ( tProj * tProj );
104 | return !( oppLenSq > radiusSq );
105 | }
106 |
107 | /** T returned is scale to vector length, not direction */
108 | export function intersectPlane( ray, planePos, planeNorm ){
109 | // ((planePos - rayOrigin) dot planeNorm) / ( rayVecLen dot planeNorm )
110 | // pos = t * rayVecLen + rayOrigin;
111 | const denom = vec3_dot( ray.vecLength, planeNorm ); // Dot product of ray Length and plane normal
112 | if( denom <= 0.000001 && denom >= -0.000001 ) return null; // abs(denom) < epsilon, using && instead to not perform absolute.
113 |
114 | const t = vec3_dot( vec3_sub( [0,0,0], planePos, ray.posStart ), planeNorm ) / denom;
115 | return ( t >= 0 )? t : null;
116 | }
117 |
118 | export function intersectTri( ray, v0, v1, v2, out, cullFace=true ){
119 | const v0v1 = vec3_sub( [0,0,0], v1, v0 );
120 | const v0v2 = vec3_sub( [0,0,0], v2, v0 );
121 | const pvec = vec3_cross( [0,0,0], ray.direction, v0v2 );
122 | const det = vec3_dot( v0v1, pvec );
123 |
124 | if( cullFace && det < 0.000001 ) return false;
125 |
126 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
127 | const idet = 1 / det;
128 | const tvec = vec3_sub( [0,0,0], ray.posStart, v0 );
129 | const u = vec3_dot( tvec, pvec ) * idet;
130 |
131 | if( u < 0 || u > 1 ) return false;
132 |
133 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134 | const qvec = vec3_cross( [0,0,0], tvec, v0v1, );
135 | const v = vec3_dot( ray.direction, qvec ) * idet;
136 |
137 | if( v < 0 || u+v > 1 ) return false;
138 |
139 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
140 | if( out ){
141 | const len = vec3_dot( v0v2, qvec ) * idet;
142 | ray.directionAt( len, out );
143 | }
144 |
145 | return true;
146 | }
147 |
148 | export function nearPoint( ray, p, distLimit=0.1 ){
149 | /* closest_point_to_line3D
150 | let dx = bx - ax,
151 | dy = by - ay,
152 | dz = bz - az,
153 | t = ( (px-ax)*dx + (py-ay)*dy + (pz-az)*dz ) / ( dx*dx + dy*dy + dz*dz ) ; */
154 | const v = vec3_sub( [0,0,0], p, ray.posStart );
155 | vec3_mul( v, v, ray.vecLength );
156 |
157 | const t = ( v[0] + v[1] + v[2] ) / vec3_sqrLen( ray.vecLength );
158 |
159 | if( t < 0 || t > 1 ) return null; // Over / Under shoots the Ray Segment
160 | const lenSqr = vec3_sqrLen( ray.posAt( t, v ), p ); // Distance from point to nearest point on ray.
161 |
162 | return ( lenSqr <= (distLimit*distLimit) )? t : null;
163 | }
164 |
165 | export class NearSegmentResult{
166 | segPosition = [0,0,0];
167 | rayPosition = [0,0,0];
168 | distanceSq = 0;
169 | distance = 0;
170 | }
171 |
172 | /** Returns [ T of Segment, T of RayLen ] */
173 | export function nearSegment( ray, p0, p1, results=null){
174 | // http://geomalgorithms.com/a07-_distance.html
175 | const u = vec3_sub( [0,0,0], p1, p0 ),
176 | v = ray.vecLength,
177 | w = vec3_sub( [0,0,0], p0, ray.posStart ),
178 | a = vec3_dot( u, u ), // always >= 0
179 | b = vec3_dot( u, v ),
180 | c = vec3_dot( v, v ), // always >= 0
181 | d = vec3_dot( u, w ),
182 | e = vec3_dot( v, w ),
183 | D = a * c - b * b; // always >= 0
184 |
185 | let tU = 0, // T Of Segment
186 | tV = 0; // T Of Ray
187 |
188 | // Compute the line parameters of the two closest points
189 | if( D < 0.000001 ){ // the lines are almost parallel
190 | tU = 0.0;
191 | tV = ( b > c ? d/b : e/c ); // use the largest denominator
192 | }else{
193 | tU = ( b*e - c*d ) / D;
194 | tV = ( a*e - b*d ) / D;
195 | }
196 |
197 | if( tU < 0 || tU > 1 || tV < 0 || tV > 1 ) return false;
198 |
199 | // Segment Position : u.scale( tU ).add( p0 )
200 | // Ray Position : v.scale( tV ).add( this.origin ) ];
201 | if( results ){
202 | vec3_lerp( results.rayPosition, ray.posStart, ray.posEnd, tV );
203 | vec3_lerp( results.segPosition, p0, p1, tU );
204 | results.distance = vec3_len( results.segPosition, results.rayPosition );
205 | }
206 |
207 | return true;
208 | }
--------------------------------------------------------------------------------
/src/ManipulatorMesh.js:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | Group, Line, Mesh, DoubleSide,
4 | MeshBasicMaterial, LineBasicMaterial,
5 | BufferGeometry, Float32BufferAttribute,
6 | SphereGeometry, TorusGeometry, CylinderGeometry,
7 | } from 'three';
8 | import { ManipulatorMode } from './ManipulatorData.js';
9 |
10 | export class ManipulatorMesh extends Group{
11 | // #region MAIN
12 | axisColors = [ 0x81D773, 0x6DA9EA, 0xF7716A ];
13 | axisLines = [];
14 | axisArcs = [];
15 | axisPoints = [];
16 | axisTris = [];
17 |
18 | grpCtrl = new Group();
19 | meshTracePnt = null;
20 | meshTraceLine = null;
21 |
22 | colSelect = 0xffffff;
23 | colOrigin = 0xffff00;
24 |
25 | constructor( data ){
26 | super();
27 | const PIH = Math.PI * 0.5;
28 | const lineRadius = 0.03;
29 | const arcRadius = data.arcRadius;
30 | const arcThickness = 0.03
31 | const sclDistance = data.sclPointLen;
32 |
33 | this.visible = false;
34 |
35 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 | // MATERIALS
37 | const matBasic = new MeshBasicMaterial( {
38 | depthTest: false,
39 | depthWrite: false,
40 | fog: false,
41 | toneMapped: false,
42 | transparent: true,
43 | side: DoubleSide,
44 | opacity: 1.0,
45 | color: 0xffffff,
46 | } );
47 |
48 | const matLine = new LineBasicMaterial( {
49 | depthTest: false,
50 | depthWrite: false,
51 | fog: false,
52 | toneMapped: false,
53 | transparent: true,
54 | color: 0x909090,
55 | } );
56 |
57 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58 | // GEOMETRY
59 | const geoTrace = new BufferGeometry();
60 | geoTrace.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 100, 0 ], 3 ) );
61 |
62 | const geoTri = new BufferGeometry();
63 | geoTri.setAttribute( 'position', new Float32BufferAttribute( [ 0,0,0, data.midPointLen,0,0, 0,data.midPointLen,0 ], 3 ) );
64 |
65 | const geoSphere = new SphereGeometry( 0.1, 8, 8 );
66 | const geoArc = new TorusGeometry( arcRadius, arcThickness, 3, 10, PIH );
67 | const geoAxisLine = new CylinderGeometry( lineRadius, lineRadius, data.axisLen, 3 );
68 | geoAxisLine.translate( 0, data.axisLen * 0.5, 0 );
69 |
70 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
71 | // AXIS LINES
72 | const yAxisLine = new Mesh( geoAxisLine, matBasic.clone() );
73 | this.grpCtrl.add( yAxisLine );
74 |
75 | const zAxisLine = new Mesh( geoAxisLine, matBasic.clone() );
76 | zAxisLine.rotation.x = PIH;
77 | this.grpCtrl.add( zAxisLine );
78 |
79 | const xAxisLine = new Mesh( geoAxisLine, matBasic.clone() );
80 | xAxisLine.rotation.z = -PIH;
81 | this.grpCtrl.add( xAxisLine );
82 |
83 | this.axisLines.push( xAxisLine, yAxisLine, zAxisLine );
84 |
85 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86 | // AXIS LINES
87 | const zAxisArc = new Mesh( geoArc, matBasic.clone() );
88 | this.grpCtrl.add( zAxisArc );
89 |
90 | const xAxisArc = new Mesh( geoArc, matBasic.clone() );
91 | xAxisArc.rotation.y = -PIH;
92 | this.grpCtrl.add( xAxisArc );
93 |
94 | const yAxisArc = new Mesh( geoArc, matBasic.clone() );
95 | yAxisArc.rotation.x = PIH;
96 | this.grpCtrl.add( yAxisArc );
97 |
98 | this.axisArcs.push( xAxisArc, yAxisArc, zAxisArc );
99 |
100 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
101 | // SCALE SELECTORS
102 | const zAxisPnt = new Mesh( geoSphere, matBasic.clone() );
103 | zAxisPnt.position.z = sclDistance;
104 | this.grpCtrl.add( zAxisPnt );
105 |
106 | const xAxisPnt = new Mesh( geoSphere, matBasic.clone() );
107 | xAxisPnt.position.x = sclDistance;
108 | this.grpCtrl.add( xAxisPnt );
109 |
110 | const yAxisPnt = new Mesh( geoSphere, matBasic.clone() );
111 | yAxisPnt.position.y = sclDistance;
112 | this.grpCtrl.add( yAxisPnt );
113 |
114 | this.axisPoints.push( xAxisPnt, yAxisPnt, zAxisPnt );
115 |
116 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117 | // PLANE SELECTORS
118 | const zAxisTri = new Mesh( geoTri, matBasic.clone() );
119 | this.grpCtrl.add( zAxisTri );
120 |
121 | const yAxisTri = new Mesh( geoTri, matBasic.clone() );
122 | yAxisTri.rotation.x = PIH;
123 | this.grpCtrl.add( yAxisTri );
124 |
125 | const xAxisTri = new Mesh( geoTri, matBasic.clone() );
126 | xAxisTri.rotation.y = -PIH;
127 | this.grpCtrl.add( xAxisTri );
128 |
129 | this.axisTris.push( xAxisTri, yAxisTri, zAxisTri );
130 |
131 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132 |
133 | this.meshTraceLine = new Line( geoTrace, matLine );
134 | this.meshTraceLine.visible = false;
135 | this.add( this.meshTraceLine );
136 |
137 | this.meshTracePnt = new Mesh( geoSphere, matBasic.clone() );
138 | this.meshTracePnt.visible = false;
139 | this.add( this.meshTracePnt );
140 |
141 | this.origin = new Mesh( geoSphere, matBasic.clone() );
142 | this.grpCtrl.add( this.origin );
143 | this.add( this.grpCtrl );
144 | }
145 |
146 | // #endregion
147 |
148 | showGizmo(){ this.grpCtrl.visible = true; }
149 | hideGizmo(){ this.grpCtrl.visible = false; }
150 |
151 | // Update the visible state of the gizmo parts
152 | updateLook( data ){
153 | let itm;
154 | for( itm of this.axisArcs ) itm.visible = data.useRotate;
155 |
156 | this.origin.visible = data.useScale;
157 | for( itm of this.axisPoints ) itm.visible = data.useScale;
158 |
159 | for( itm of this.axisTris ) itm.visible = data.useTranslate;
160 | }
161 |
162 | update( data ){
163 | if( !data.hasUpdated && !data.hasHit ) return;
164 |
165 | this.grpCtrl.scale.fromArray( data.scale );
166 | this.grpCtrl.position.fromArray( data.position );
167 |
168 | if( data.activeAxis === -2 && data.activeMode === ManipulatorMode.Scale ){
169 | this.origin.material.color.setHex( this.colSelect );
170 | }else{
171 | this.origin.material.color.setHex( this.colOrigin );
172 | }
173 |
174 | for( let i=0; i < 3; i++ ){
175 | this.axisLines[ i ].material.color.setHex( this.axisColors[ i ] );
176 | this.axisArcs[ i ].material.color.setHex( this.axisColors[ i ] );
177 | this.axisPoints[ i ].material.color.setHex( this.axisColors[ i ] );
178 | this.axisTris[ i ].material.color.setHex( this.axisColors[ i ] );
179 |
180 | if( i === data.activeAxis ){
181 | switch( data.activeMode ){
182 | case ManipulatorMode.Translate: this.axisLines[ i ].material.color.setHex( this.colSelect ); break;
183 | case ManipulatorMode.Rotate: this.axisArcs[ i ].material.color.setHex( this.colSelect ); break;
184 | case ManipulatorMode.Scale: this.axisPoints[ i ].material.color.setHex( this.colSelect ); break;
185 | }
186 | }
187 |
188 | if( i === data.activePlane ){
189 | this.axisTris[ i ].material.color.setHex( 0xffffff );
190 | }
191 | }
192 |
193 |
194 | if( data.traceLine.isActive ){
195 | const sclPnt = Math.abs( data.scale[2] );
196 | this.meshTracePnt.visible = true;
197 | this.meshTracePnt.scale.set( sclPnt, sclPnt, sclPnt );
198 | this.meshTracePnt.position.fromArray( data.traceLine.origin );
199 |
200 | this.meshTraceLine.visible = true;
201 | this.meshTraceLine.geometry.attributes.position.needsUpdate = true;
202 |
203 | const pntArray = this.meshTraceLine.geometry.attributes.position.array;
204 | pntArray[ 0 ] = data.traceLine.a[ 0 ];
205 | pntArray[ 1 ] = data.traceLine.a[ 1 ];
206 | pntArray[ 2 ] = data.traceLine.a[ 2 ];
207 | pntArray[ 3 ] = data.traceLine.b[ 0 ];
208 | pntArray[ 4 ] = data.traceLine.b[ 1 ];
209 | pntArray[ 5 ] = data.traceLine.b[ 2 ];
210 | }else{
211 | this.meshTraceLine.visible = false;
212 | this.meshTracePnt.visible = false;
213 | }
214 | }
215 | }
--------------------------------------------------------------------------------
/src/Maths.js:
--------------------------------------------------------------------------------
1 | // #region VEC3
2 | export function vec3_copy( out, a ){
3 | out[0] = a[0];
4 | out[1] = a[1];
5 | out[2] = a[2];
6 | return out;
7 | }
8 |
9 | export function vec3_add( out, a, b ){
10 | out[ 0 ] = a[ 0 ] + b[ 0 ];
11 | out[ 1 ] = a[ 1 ] + b[ 1 ];
12 | out[ 2 ] = a[ 2 ] + b[ 2 ];
13 | return out;
14 | }
15 |
16 | export function vec3_sub( out, a, b ){
17 | out[ 0 ] = a[ 0 ] - b[ 0 ];
18 | out[ 1 ] = a[ 1 ] - b[ 1 ];
19 | out[ 2 ] = a[ 2 ] - b[ 2 ];
20 | return out;
21 | }
22 |
23 | export function vec3_cross( out, a, b ){
24 | const ax = a[0], ay = a[1], az = a[2],
25 | bx = b[0], by = b[1], bz = b[2];
26 |
27 | out[ 0 ] = ay * bz - az * by;
28 | out[ 1 ] = az * bx - ax * bz;
29 | out[ 2 ] = ax * by - ay * bx;
30 | return out;
31 | }
32 |
33 | export function vec3_dot( a, b ){ return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; }
34 |
35 | export function vec3_scaleAndAdd( out, add, v, s ){
36 | out[ 0 ] = v[ 0 ] * s + add[ 0 ];
37 | out[ 1 ] = v[ 1 ] * s + add[ 1 ];
38 | out[ 2 ] = v[ 2 ] * s + add[ 2 ];
39 | return out;
40 | }
41 |
42 | export function vec3_scale( out, a, s ){
43 | out[ 0 ] = a[ 0 ] * s;
44 | out[ 1 ] = a[ 1 ] * s;
45 | out[ 2 ] = a[ 2 ] * s;
46 | return out;
47 | }
48 |
49 | export function vec3_norm( out, a){
50 | let mag = Math.sqrt( a[ 0 ]**2 + a[ 1 ]**2 + a[ 2 ]**2 );
51 | if( mag != 0 ){
52 | mag = 1 / mag;
53 | out[ 0 ] = a[ 0 ] * mag;
54 | out[ 1 ] = a[ 1 ] * mag;
55 | out[ 2 ] = a[ 2 ] * mag;
56 | }
57 | return out;
58 | }
59 |
60 | export function vec3_transformQuat( out, v, q ){
61 | const qx = q[0], qy = q[1], qz = q[2], qw = q[3],
62 | vx = v[0], vy = v[1], vz = v[2],
63 | x1 = qy * vz - qz * vy,
64 | y1 = qz * vx - qx * vz,
65 | z1 = qx * vy - qy * vx,
66 | x2 = qw * x1 + qy * z1 - qz * y1,
67 | y2 = qw * y1 + qz * x1 - qx * z1,
68 | z2 = qw * z1 + qx * y1 - qy * x1;
69 | out[ 0 ] = vx + 2 * x2;
70 | out[ 1 ] = vy + 2 * y2;
71 | out[ 2 ] = vz + 2 * z2;
72 | return out;
73 | }
74 |
75 | export function vec3_transformMat4(out, a, m) {
76 | let x = a[0], y = a[1], z = a[2];
77 | let w = ( m[3] * x + m[7] * y + m[11] * z + m[15] ) || 1.0;
78 | out[0] = ( m[0] * x + m[4] * y + m[8] * z + m[12] ) / w;
79 | out[1] = ( m[1] * x + m[5] * y + m[9] * z + m[13] ) / w;
80 | out[2] = ( m[2] * x + m[6] * y + m[10] * z + m[14] ) / w;
81 | return out;
82 | }
83 |
84 | export function vec3_sqrLen( a, b ){
85 | if( b === undefined ) return a[ 0 ]**2 + a[ 1 ]**2 + a[ 2 ]** 2;
86 | return (a[ 0 ]-b[ 0 ]) ** 2 + (a[ 1 ]-b[ 1 ]) ** 2 + (a[ 2 ]-b[ 2 ]) ** 2;
87 | }
88 |
89 | export function vec3_len( a, b ){
90 | if( b === undefined ) return Math.sqrt( a[ 0 ]**2 + a[ 1 ]**2 + a[ 2 ]** 2 );
91 | return Math.sqrt( (a[ 0 ]-b[ 0 ]) ** 2 + (a[ 1 ]-b[ 1 ]) ** 2 + (a[ 2 ]-b[ 2 ]) ** 2 );
92 | }
93 |
94 | export function vec3_mul( out, a, b ){
95 | out[ 0 ] = a[ 0 ] * b[ 0 ];
96 | out[ 1 ] = a[ 1 ] * b[ 1 ];
97 | out[ 2 ] = a[ 2 ] * b[ 2 ];
98 | return out;
99 | }
100 |
101 | export function vec3_lerp( out, a, b, t ) {
102 | const ti = 1 - t; // Linear Interpolation : (1 - t) * v0 + t * v1;
103 | out[ 0 ] = a[ 0 ] * ti + b[ 0 ] * t;
104 | out[ 1 ] = a[ 1 ] * ti + b[ 1 ] * t;
105 | out[ 2 ] = a[ 2 ] * ti + b[ 2 ] * t;
106 | return out;
107 | }
108 | // #endregion
109 |
110 | // #region QUAT
111 | export function quat_copy( out, a ) {
112 | out[ 0 ] = a[ 0 ];
113 | out[ 1 ] = a[ 1 ];
114 | out[ 2 ] = a[ 2 ];
115 | out[ 3 ] = a[ 3 ];
116 | return out;
117 | }
118 |
119 | export function quat_mul( out, a, b ) {
120 | const ax = a[0], ay = a[1], az = a[2], aw = a[3],
121 | bx = b[0], by = b[1], bz = b[2], bw = b[3];
122 | out[ 0 ] = ax * bw + aw * bx + ay * bz - az * by;
123 | out[ 1 ] = ay * bw + aw * by + az * bx - ax * bz;
124 | out[ 2 ] = az * bw + aw * bz + ax * by - ay * bx;
125 | out[ 3 ] = aw * bw - ax * bx - ay * by - az * bz;
126 | return out;
127 | }
128 |
129 | export function quat_normalize( out, q ){
130 | let len = q[0]**2 + q[1]**2 + q[2]**2 + q[3]**2;
131 | if( len > 0 ){
132 | len = 1 / Math.sqrt( len );
133 | out[ 0 ] = q[ 0 ] * len;
134 | out[ 1 ] = q[ 1 ] * len;
135 | out[ 2 ] = q[ 2 ] * len;
136 | out[ 3 ] = q[ 3 ] * len;
137 | }
138 | return out;
139 | }
140 |
141 | export function quat_setAxisAngle( out, axis, rad ){
142 | const half = rad * .5,
143 | s = Math.sin( half );
144 | out[ 0 ] = s * axis[ 0 ];
145 | out[ 1 ] = s * axis[ 1 ];
146 | out[ 2 ] = s * axis[ 2 ];
147 | out[ 3 ] = Math.cos( half );
148 | return out;
149 | }
150 |
151 | export function quat_sqrLen( a, b ){
152 | if( b === undefined ) return a[ 0 ]**2 + a[ 1 ]**2 + a[ 2 ]** 2 + a[ 3 ]** 2;
153 | return (a[ 0 ]-b[ 0 ]) ** 2 + (a[ 1 ]-b[ 1 ]) ** 2 + (a[ 2 ]-b[ 2 ]) ** 2 + (a[ 3 ]-b[ 3 ]) ** 2;
154 | }
155 |
156 | // #endregion
157 |
158 | // #region MAT4
159 |
160 | export function mat4_invert(out, a) {
161 | let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
162 | let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
163 | let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
164 | let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
165 |
166 | let b00 = a00 * a11 - a01 * a10;
167 | let b01 = a00 * a12 - a02 * a10;
168 | let b02 = a00 * a13 - a03 * a10;
169 | let b03 = a01 * a12 - a02 * a11;
170 | let b04 = a01 * a13 - a03 * a11;
171 | let b05 = a02 * a13 - a03 * a12;
172 | let b06 = a20 * a31 - a21 * a30;
173 | let b07 = a20 * a32 - a22 * a30;
174 | let b08 = a20 * a33 - a23 * a30;
175 | let b09 = a21 * a32 - a22 * a31;
176 | let b10 = a21 * a33 - a23 * a31;
177 | let b11 = a22 * a33 - a23 * a32;
178 |
179 | // Calculate the determinant
180 | let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
181 |
182 | if (!det) return null;
183 | det = 1.0 / det;
184 |
185 | out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
186 | out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
187 | out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
188 | out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
189 | out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
190 | out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
191 | out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
192 | out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
193 | out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
194 | out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
195 | out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
196 | out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
197 | out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
198 | out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
199 | out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
200 | out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
201 |
202 | return out;
203 | }
204 |
205 | export function mat4_mul( out, a, b ){
206 | let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
207 | let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
208 | let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
209 | let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
210 |
211 | // Cache only the current line of the second matrix
212 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
213 | out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
214 | out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
215 | out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
216 | out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
217 |
218 | b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
219 | out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
220 | out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
221 | out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
222 | out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
223 |
224 | b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
225 | out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
226 | out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
227 | out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
228 | out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
229 |
230 | b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
231 | out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
232 | out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
233 | out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
234 | out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
235 | return out;
236 | }
237 | // #endregion
238 |
239 | // #region VEC4
240 | export function vec4_transformMat4( out, a, m ){
241 | let x = a[0], y = a[1], z = a[2], w = a[3];
242 | out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
243 | out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
244 | out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
245 | out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
246 | return out;
247 | }
248 | // #endregion
--------------------------------------------------------------------------------
/test/_lib/DynLineMesh.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | // X and Y axis need to be normalized vectors, 90 degrees of eachother.
4 | function planeCircle( vecCenter, xAxis, yAxis, angle, radius, out ){
5 | const sin = Math.sin(angle);
6 | const cos = Math.cos(angle);
7 | out[0] = vecCenter[0] + radius * cos * xAxis[0] + radius * sin * yAxis[0];
8 | out[1] = vecCenter[1] + radius * cos * xAxis[1] + radius * sin * yAxis[1];
9 | out[2] = vecCenter[2] + radius * cos * xAxis[2] + radius * sin * yAxis[2];
10 | return out;
11 | }
12 |
13 |
14 | class DynLineMesh extends THREE.LineSegments{
15 | _defaultColor = 0x00ff00;
16 | _cnt = 0;
17 | _verts = [];
18 | _color = [];
19 | _config = [];
20 | _dirty = false;
21 |
22 | constructor( initSize = 20 ){
23 | super(
24 | _newDynLineMeshGeometry(
25 | new Float32Array( initSize * 2 * 3 ), // Two Points for Each Line
26 | new Float32Array( initSize * 2 * 3 ),
27 | new Float32Array( initSize * 2 * 1 ),
28 | false
29 | ),
30 | newDynLineMeshMaterial() //new THREE.PointsMaterial( { color: 0xffffff, size:8, sizeAttenuation:false } )
31 | );
32 |
33 | this.geometry.setDrawRange( 0, 0 );
34 | this.onBeforeRender = ()=>{ if( this._dirty ) this._updateGeometry(); }
35 | }
36 |
37 | useDepth( b ){ this.material.depthTest = b; return this; }
38 |
39 | reset(){
40 | this._cnt = 0;
41 | this._verts.length = 0;
42 | this._color.length = 0;
43 | this._config.length = 0;
44 | this.geometry.setDrawRange( 0, 0 );
45 | return this;
46 | }
47 |
48 | add( p0, p1, color0=this._defaultColor, color1=null, isDash=false ){
49 | this._verts.push( p0[0], p0[1], p0[2], p1[0], p1[1], p1[2] );
50 | this._color.push( ...glColor( color0 ), ...glColor( (color1 != null) ? color1:color0 ) );
51 |
52 | if( isDash ){
53 | const len = Math.sqrt(
54 | (p1[0] - p0[0]) ** 2 +
55 | (p1[1] - p0[1]) ** 2 +
56 | (p1[2] - p0[2]) ** 2
57 | );
58 | this._config.push( 0, len );
59 | }else{
60 | this._config.push( 0, 0 );
61 | }
62 |
63 | this._cnt++;
64 | this._dirty = true;
65 | return this;
66 | }
67 |
68 | box( v0, v1, col=ln_color, is_dash=false ){
69 | let x1 = v0[0], y1 = v0[1], z1 = v0[2],
70 | x2 = v1[0], y2 = v1[1], z2 = v1[2];
71 |
72 | this.add( [x1,y1,z1], [x1,y1,z2], col, null, is_dash ); // Bottom
73 | this.add( [x1,y1,z2], [x2,y1,z2], col, null, is_dash );
74 | this.add( [x2,y1,z2], [x2,y1,z1], col, null, is_dash );
75 | this.add( [x2,y1,z1], [x1,y1,z1], col, null, is_dash );
76 | this.add( [x1,y2,z1], [x1,y2,z2], col, null, is_dash ); // Top
77 | this.add( [x1,y2,z2], [x2,y2,z2], col, null, is_dash );
78 | this.add( [x2,y2,z2], [x2,y2,z1], col, null, is_dash );
79 | this.add( [x2,y2,z1], [x1,y2,z1], col, null, is_dash );
80 | this.add( [x1,y1,z1], [x1,y2,z1], col, null, is_dash ); // Sides
81 | this.add( [x1,y1,z2], [x1,y2,z2], col, null, is_dash );
82 | this.add( [x2,y1,z2], [x2,y2,z2], col, null, is_dash );
83 | this.add( [x2,y1,z1], [x2,y2,z1], col, null, is_dash );
84 | return this;
85 | }
86 |
87 | circle( origin, xAxis, yAxis, radius, seg, col=this._defaultColor, is_dash=false ){
88 | const prevPos = [0,0,0];
89 | const pos = [0,0,0];
90 | const PI2 = Math.PI * 2;
91 | let rad = 0;
92 |
93 | planeCircle( origin, xAxis, yAxis, 0, radius, prevPos );
94 | for( let i=1; i <= seg; i++ ){
95 | rad = PI2 * ( i / seg );
96 | planeCircle( origin, xAxis, yAxis, rad, radius, pos );
97 | this.add( prevPos, pos, col, null, is_dash );
98 |
99 | prevPos[0] = pos[0];
100 | prevPos[1] = pos[1];
101 | prevPos[2] = pos[2];
102 | }
103 | }
104 |
105 | arc( origin, xAxis, yAxis, radius, seg, arcRad, initRad=0, col=this._defaultColor, is_dash=false ){
106 | const prevPos = [0,0,0];
107 | const pos = [0,0,0];
108 | const PI2 = Math.PI * 2;
109 | let rad = 0;
110 |
111 | planeCircle( origin, xAxis, yAxis, 0, radius, prevPos );
112 | for( let i=1; i <= seg; i++ ){
113 | rad = arcRad * ( i / seg ) + initRad;
114 | planeCircle( origin, xAxis, yAxis, rad, radius, pos );
115 | this.add( prevPos, pos, col, null, is_dash );
116 |
117 | prevPos[0] = pos[0];
118 | prevPos[1] = pos[1];
119 | prevPos[2] = pos[2];
120 | }
121 | }
122 |
123 | _updateGeometry(){
124 | const geo = this.geometry;
125 | const bVerts = geo.attributes.position;
126 | const bColor = geo.attributes.color; //this.geometry.index;
127 | const bConfig = geo.attributes.config;
128 |
129 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130 | if( this._verts.length > bVerts.array.length ||
131 | this._color.length > bColor.array.length ||
132 | this._config.length > bConfig.array.length
133 | ){
134 | if( this.geometry ) this.geometry.dispose();
135 | this.geometry = _newDynLineMeshGeometry( this._verts, this._color, this._config );
136 | this._dirty = false;
137 | return;
138 | }
139 |
140 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
141 | bVerts.array.set( this._verts );
142 | bVerts.count = this._verts.length / 3;
143 | bVerts.needsUpdate = true;
144 |
145 | bColor.array.set( this._color );
146 | bColor.count = this._color.length / 3;
147 | bColor.needsUpdate = true;
148 |
149 | bConfig.array.set( this._config );
150 | bConfig.count = this._config.length / 1;
151 | bConfig.needsUpdate = true;
152 |
153 | geo.setDrawRange( 0, bVerts.count );
154 | geo.computeBoundingBox();
155 | geo.computeBoundingSphere();
156 |
157 | this._dirty = false;
158 | }
159 | }
160 |
161 | //#region SUPPORT
162 | function _newDynLineMeshGeometry( aVerts, aColor, aConfig, doCompute=true ){
163 | //if( !( aVerts instanceof Float32Array) ) aVerts = new Float32Array( aVerts );
164 | //if( !( aColor instanceof Float32Array) ) aColor = new Float32Array( aColor );
165 |
166 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
167 | const bVerts = new THREE.Float32BufferAttribute( aVerts, 3 );
168 | const bColor = new THREE.Float32BufferAttribute( aColor, 3 );
169 | const bConfig = new THREE.Float32BufferAttribute( aConfig, 1 );
170 | bVerts.setUsage( THREE.DynamicDrawUsage );
171 | bColor.setUsage( THREE.DynamicDrawUsage );
172 | bConfig.setUsage( THREE.DynamicDrawUsage );
173 |
174 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
175 | const geo = new THREE.BufferGeometry();
176 | geo.setAttribute( 'position', bVerts );
177 | geo.setAttribute( 'color', bColor );
178 | geo.setAttribute( 'config', bConfig );
179 |
180 | if( doCompute ){
181 | geo.computeBoundingSphere();
182 | geo.computeBoundingBox();
183 | }
184 | return geo;
185 | }
186 |
187 | function glColor( hex, out = null ){
188 | const NORMALIZE_RGB = 1 / 255;
189 | out = out || [0,0,0];
190 |
191 | out[0] = ( hex >> 16 & 255 ) * NORMALIZE_RGB;
192 | out[1] = ( hex >> 8 & 255 ) * NORMALIZE_RGB;
193 | out[2] = ( hex & 255 ) * NORMALIZE_RGB;
194 |
195 | return out;
196 | }
197 | //#endregion
198 |
199 | //#region SHADER
200 |
201 | function newDynLineMeshMaterial(){
202 | return new THREE.RawShaderMaterial({
203 | depthTest : false,
204 | transparent : true,
205 | uniforms : {
206 | dashSeg : { value : 1 / 0.07 },
207 | dashDiv : { value : 0.4 },
208 | },
209 | vertexShader : `#version 300 es
210 | in vec3 position;
211 | in vec3 color;
212 | in float config;
213 |
214 | uniform mat4 modelViewMatrix;
215 | uniform mat4 projectionMatrix;
216 | uniform float u_scale;
217 |
218 | out vec3 fragColor;
219 | out float fragLen;
220 |
221 | void main(){
222 | vec4 wPos = modelViewMatrix * vec4( position, 1.0 );
223 |
224 | fragColor = color;
225 | fragLen = config;
226 |
227 | gl_Position = projectionMatrix * wPos;
228 | }`,
229 | fragmentShader : `#version 300 es
230 | precision mediump float;
231 |
232 | uniform float dashSeg;
233 | uniform float dashDiv;
234 |
235 | in vec3 fragColor;
236 | in float fragLen;
237 | out vec4 outColor;
238 |
239 | void main(){
240 | float alpha = 1.0;
241 | if( fragLen > 0.0 ) alpha = step( dashDiv, fract( fragLen * dashSeg ) );
242 | outColor = vec4( fragColor, alpha );
243 | }`});
244 | }
245 |
246 | //#endregion
247 |
248 | export default DynLineMesh;
--------------------------------------------------------------------------------
/annotatebox/render/ScaleCube.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | export default class ScaleCube{
4 | // #region MAIN
5 | mesh = null;
6 | constructor(){
7 | const geo = genGeometry();
8 | const bGeo = new THREE.BufferGeometry();
9 | bGeo.setIndex( new THREE.BufferAttribute( geo.indices, 1 ) );
10 | bGeo.setAttribute( 'position', new THREE.BufferAttribute( geo.vertices, 4 ) );
11 | bGeo.setAttribute( 'normal', new THREE.BufferAttribute( geo.normals, 3 ) );
12 | bGeo.setAttribute( 'uv', new THREE.BufferAttribute( geo.texcoord, 2 ) );
13 |
14 | const mat = customMaterial();
15 | this.mesh = new THREE.Mesh( bGeo, mat );
16 | }
17 | // #endregion
18 |
19 | // #region METHODS
20 | // xp, yp, zp, xn, yn, zn
21 | setAxesLengths( size ){
22 | const scl = this.mesh.material.uniforms.scale.value;
23 | scl[ 0 ] = size[ 0 ];
24 | scl[ 1 ] = size[ 1 ];
25 | scl[ 2 ] = size[ 2 ];
26 | scl[ 3 ] = size[ 3 ];
27 | scl[ 4 ] = size[ 4 ];
28 | scl[ 5 ] = size[ 5 ];
29 | return this;
30 | }
31 |
32 | selectFace( i ){ this.mesh.material.uniforms.selFace.value = i; return this; }
33 | // #endregion
34 | }
35 |
36 | function genGeometry(){
37 | const x1 = 1,
38 | y1 = 1,
39 | z1 = 1,
40 | x0 = -1,
41 | y0 = -1,
42 | z0 = -1;
43 |
44 | // Starting bottom left corner, then working counter clockwise to create the front face.
45 | // Backface is the first face but in reverse (3,2,1,0)
46 | // keep each quad face built the same way to make index and uv easier to assign
47 | const vert = [
48 | x0, y1, z1, 2, //0 Front
49 | x0, y0, z1, 2, //1
50 | x1, y0, z1, 2, //2
51 | x1, y1, z1, 2, //3
52 |
53 | x1, y1, z0, 5, //4 Back
54 | x1, y0, z0, 5, //5
55 | x0, y0, z0, 5, //6
56 | x0, y1, z0, 5, //7
57 |
58 | x1, y1, z1, 0, //3 Right
59 | x1, y0, z1, 0, //2
60 | x1, y0, z0, 0, //5
61 | x1, y1, z0, 0, //4
62 |
63 | x0, y0, z1, 4, //1 Bottom
64 | x0, y0, z0, 4, //6
65 | x1, y0, z0, 4, //5
66 | x1, y0, z1, 4, //2
67 |
68 | x0, y1, z0, 3, //7 Left
69 | x0, y0, z0, 3, //6
70 | x0, y0, z1, 3, //1
71 | x0, y1, z1, 3, //0
72 |
73 | x0, y1, z0, 1, //7 Top
74 | x0, y1, z1, 1, //0
75 | x1, y1, z1, 1, //3
76 | x1, y1, z0, 1, //4
77 | ];
78 |
79 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
80 | // Build the index of each quad [0,1,2, 2,3,0]
81 | let i;
82 | const idx = [];
83 | for( i=0; i < vert.length / 3; i+=2) idx.push( i, i+1, ( Math.floor( i / 4 ) * 4 ) + ( ( i + 2 ) % 4 ) );
84 |
85 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86 | // Build UV data for each vertex
87 | const uv = [];
88 | for( i=0; i < 6; i++) uv.push( 0,0, 0,1, 1,1, 1,0 );
89 |
90 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
91 | return {
92 | vertices : new Float32Array( vert ),
93 | indices : new Uint16Array( idx ),
94 | texcoord : new Float32Array( uv ),
95 | normals : new Float32Array( [ // Left/Right have their xNormal flipped to render correctly in 3JS, Why does normals need to be mirrored on X?
96 | 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, //Front
97 | 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, //Back
98 | 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, //Left
99 | 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, //Bottom
100 | -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, //Right
101 | 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 //Top
102 | ] ),
103 | };
104 | }
105 |
106 | function customMaterial(){
107 | const mat = new THREE.RawShaderMaterial({
108 | depthTest : true,
109 | transparent : true,
110 | side : THREE.DoubleSide,
111 | // lights : true,
112 |
113 | uniforms : {
114 | alpha : { type :'float', value:0.7, },
115 | scale : { type :'float', value:[1,1,1,1,1,1] },
116 | borderWidth : { type :'float', value:0.02, },
117 | borderColor : { type :'vec3', value:new THREE.Color( 0xb0b0b0 ) },
118 | selColor : { type :'vec3', value:new THREE.Color( 0xffff00 ) },
119 |
120 | selFace : { type :'int', value:-1, },
121 |
122 | color : { type :'vec3', value:new THREE.Color( 0xa0a0a0 ) },
123 | color_x : { type :'vec3', value:new THREE.Color( "#878FA3" ) }, // Each axis gets a Grayscaled Value, used as strength of baseColor
124 | color_y : { type :'vec3', value:new THREE.Color( "#ffffff" ) }, // these don't really need to be modified unless looking to change
125 | color_z : { type :'vec3', value:new THREE.Color( "#CED4E0" ) }, // the overall strength of each axis
126 | },
127 |
128 | extensions : {
129 | derivatives : true
130 | },
131 |
132 | vertexShader : `#version 300 es
133 | in vec4 position;
134 | in vec3 normal;
135 | in vec2 uv;
136 |
137 | uniform mediump float[6] scale;
138 | uniform int selFace;
139 | uniform vec3 color;
140 | uniform vec3 selColor;
141 |
142 | uniform mat4 modelMatrix;
143 | uniform mat4 viewMatrix;
144 | uniform mat4 projectionMatrix;
145 |
146 | out vec3 fragSPos;
147 | out vec3 fragWPos; // World Space Position
148 | out vec3 fragNorm;
149 | out vec3 fragLNorm;
150 | out vec2 fragUV;
151 | out vec3 fragColor;
152 |
153 | void main(){
154 | int faceIdx = int( position.w );
155 | fragColor = (faceIdx != selFace)? color : selColor;
156 |
157 | vec3 pos = position.xyz;
158 | pos.x *= ( pos.x > 0.0 )? scale[ 0 ] : scale[ 3 ];
159 | pos.y *= ( pos.y > 0.0 )? scale[ 1 ] : scale[ 4 ];
160 | pos.z *= ( pos.z > 0.0 )? scale[ 2 ] : scale[ 5 ];
161 |
162 | vec4 wPos = modelMatrix * vec4( pos, 1.0 ); // World Space
163 | vec4 vPos = viewMatrix * wPos; // View Space
164 |
165 | fragSPos = pos;
166 | fragUV = uv;
167 | fragWPos = wPos.xyz;
168 | fragNorm = ( modelMatrix * vec4( normal, 0.0 ) ).xyz;
169 | fragLNorm = normal;
170 |
171 | gl_Position = projectionMatrix * vPos;
172 | }`,
173 |
174 | fragmentShader : `#version 300 es
175 | precision mediump float;
176 |
177 | uniform float[6] scale;
178 | uniform float alpha;
179 | //uniform vec3 color;
180 | uniform vec3 color_x;
181 | uniform vec3 color_y;
182 | uniform vec3 color_z;
183 | uniform float borderWidth;
184 | uniform vec3 borderColor;
185 |
186 | in vec3 fragColor;
187 | in vec3 fragSPos;
188 | in vec3 fragWPos;
189 | in vec3 fragNorm;
190 | in vec3 fragLNorm;
191 | in vec2 fragUV;
192 | out vec4 outColor;
193 |
194 | // #####################################################################
195 | void main(){
196 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
197 | vec3 norm = normalize( fragNorm ); // Normals From Mesh
198 |
199 | // Treating normal as Light Strength, it curves the progression from dark to light
200 | // if left as is, it gives the tint lighting much more strength and also linear progression
201 | norm = norm * norm;
202 |
203 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204 | // From what I understand of how this works is by applying a Lighting Color for Each axis direction.
205 | // Then using the normal direction to blend each axis color together. From kenny's image example, he
206 | // setup the brightest color to come from Y, Second from Z then the darkest color at X.
207 | vec3 out_color;
208 | out_color = mix( fragColor, fragColor * color_x, norm.x );
209 | out_color = mix( out_color, fragColor * color_y, norm.y );
210 | out_color = mix( out_color, fragColor * color_z, norm.z );
211 |
212 | //outColor = vec4( out_color, alpha );
213 |
214 | // Darken Negative
215 | float aMin = abs( min( fragLNorm.x, min( fragLNorm.y, fragLNorm.z ) ) );
216 | out_color = out_color - aMin * 0.3 * out_color ;
217 |
218 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
219 | // Create Borders
220 | // Invert AA Normal : [0,1,0] > [1,0,1]
221 | // if norm.z > 0 then xy
222 | // if norm.y > 0 then xz
223 | // if norm.x > 0 then yz
224 | vec3 negate = abs( fragLNorm - 1.0 );
225 |
226 | // Scaled Length of each of the 6 directions
227 | vec3 scl = fragSPos;
228 | scl.x = ( fragSPos.x > 0.0 )? scale[ 0 ] : scale[ 3 ];
229 | scl.y = ( fragSPos.y > 0.0 )? scale[ 1 ] : scale[ 4 ];
230 | scl.z = ( fragSPos.z > 0.0 )? scale[ 2 ] : scale[ 5 ];
231 | scl -= borderWidth; // Subtract width to simplify smoothstep
232 |
233 | vec3 absPos = abs( fragSPos ); // Absolute Scaled Position
234 | vec3 px = fwidth( absPos );
235 | float mask = 1.0;
236 | mask = min( mask, smoothstep( scl.x * negate.x, scl.x-px.x, absPos.x ) );
237 | mask = min( mask, smoothstep( scl.y * negate.y, scl.y-px.y, absPos.y ) );
238 | mask = min( mask, smoothstep( scl.z * negate.z, scl.z-px.z, absPos.z ) );
239 | mask = 1.0 - mask;
240 |
241 | outColor.rgb = mix( out_color, borderColor, mask );
242 | outColor.a = mix( alpha, 1.0, mask );
243 | }`
244 | });
245 |
246 | return mat;
247 | }
--------------------------------------------------------------------------------