rayzee 8.8.2 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -12
- package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CQLCLpcC.js} +2 -0
- package/dist/rayzee.es.js +3571 -3110
- package/dist/rayzee.umd.js +110 -99
- package/package.json +1 -1
- package/src/EngineDefaults.js +21 -19
- package/src/EngineEvents.js +4 -0
- package/src/Passes/OIDNTemporalHistory.js +25 -7
- package/src/PathTracerApp.js +23 -6
- package/src/Processor/AssetLoader.js +9 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +18 -6
- package/src/RenderSettings.js +11 -8
- package/src/Stages/MotionVector.js +8 -42
- package/src/Stages/NRD.js +30 -21
- package/src/Stages/NormalDepth.js +5 -17
- package/src/Stages/PathTracer.js +6 -2
- package/src/TSL/CameraRay.js +99 -14
- package/src/TSL/DebugKernel.js +2 -2
- package/src/TSL/GenerateKernel.js +2 -2
- package/src/index.js +4 -0
- package/src/managers/CameraManager.js +244 -56
- package/src/managers/DenoisingManager.js +4 -3
- package/src/managers/InteractionManager.js +30 -2
- package/src/managers/UniformManager.js +6 -4
- package/src/managers/VideoRenderManager.js +35 -16
- package/src/managers/ViewCamera.js +77 -0
- package/src/managers/WalkControls.js +262 -0
- package/src/managers/helpers/OutlineHelper.js +22 -2
- package/src/managers/timeline/CameraTrack.js +213 -0
- package/src/managers/timeline/TimelineManager.js +132 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* VideoRenderManager — Drives offline frame-by-frame animation rendering.
|
|
3
3
|
*
|
|
4
|
-
* Seeks the animation to each frame time, accumulates SPP
|
|
5
|
-
* optionally denoises, captures the output canvas, and delivers each frame
|
|
6
|
-
*
|
|
4
|
+
* Seeks the animation clip and the timeline, when given one, to each frame time, accumulates SPP
|
|
5
|
+
* until convergence, optionally denoises, captures the output canvas, and delivers each frame via a
|
|
6
|
+
* callback for encoding.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { EngineEvents } from '../EngineEvents.js';
|
|
@@ -30,6 +30,8 @@ export class VideoRenderManager {
|
|
|
30
30
|
* @param {boolean} [options.enableOIDN=true] - Run OIDN denoiser per frame
|
|
31
31
|
* @param {number} [options.speed=1] - Playback speed multiplier (maps video time to animation time)
|
|
32
32
|
* @param {number} [options.totalFrames] - Override total frame count (for looped animations)
|
|
33
|
+
* @param {import('./timeline/TimelineManager.js').TimelineManager} [options.timeline] - Keyframes to
|
|
34
|
+
* play in video time; without a clip the video lasts as long as they do
|
|
33
35
|
* @param {Function} [options.onFrame] - async (ImageBitmap, frameIndex, totalFrames) => void
|
|
34
36
|
* @param {Function} [options.onProgress] - ({ frame, totalFrames, percent }) => void
|
|
35
37
|
* @param {Function} [options.onComplete] - (success: boolean) => void
|
|
@@ -42,6 +44,7 @@ export class VideoRenderManager {
|
|
|
42
44
|
speed = 1,
|
|
43
45
|
samplesPerFrame = PRODUCTION_RENDER_CONFIG.maxSamples,
|
|
44
46
|
enableOIDN = true,
|
|
47
|
+
timeline = null,
|
|
45
48
|
onFrame,
|
|
46
49
|
onProgress,
|
|
47
50
|
onComplete,
|
|
@@ -49,32 +52,32 @@ export class VideoRenderManager {
|
|
|
49
52
|
|
|
50
53
|
const app = this._app;
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
// Keyframes alone make a video too.
|
|
56
|
+
const clip = app.animationManager?.hasAnimations ? app.animationManager.clips[ clipIndex ] : null;
|
|
57
|
+
if ( ! clip && ! timeline?.animates ) {
|
|
53
58
|
|
|
54
|
-
console.warn( 'VideoRenderManager: No animation clips
|
|
59
|
+
console.warn( app.animationManager?.hasAnimations ? `VideoRenderManager: Invalid clip index ${clipIndex}` : 'VideoRenderManager: No animation clips or keyframes' );
|
|
55
60
|
onComplete?.( false );
|
|
56
61
|
return;
|
|
57
62
|
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
const
|
|
61
|
-
|
|
65
|
+
const effectiveDuration = clip ? clip.duration / ( speed || 1 ) : timeline.duration;
|
|
66
|
+
const totalFrames = options.totalFrames || Math.ceil( effectiveDuration * fps );
|
|
67
|
+
const frameDuration = 1 / fps;
|
|
68
|
+
if ( ! ( totalFrames > 0 ) ) {
|
|
62
69
|
|
|
63
|
-
console.warn(
|
|
70
|
+
console.warn( 'VideoRenderManager: No frames to render' );
|
|
64
71
|
onComplete?.( false );
|
|
65
72
|
return;
|
|
66
73
|
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
const effectiveDuration = clip.duration / ( speed || 1 );
|
|
70
|
-
const totalFrames = options.totalFrames || Math.ceil( effectiveDuration * fps );
|
|
71
|
-
const frameDuration = 1 / fps;
|
|
72
|
-
|
|
73
76
|
this._cancelled = false;
|
|
74
77
|
this._rendering = true;
|
|
75
78
|
|
|
76
79
|
// Save current engine state
|
|
77
|
-
const savedState = this._saveState();
|
|
80
|
+
const savedState = this._saveState( { view: !! timeline } );
|
|
78
81
|
|
|
79
82
|
// Stop the rAF loop — we drive rendering manually
|
|
80
83
|
app.stopAnimation();
|
|
@@ -99,7 +102,16 @@ export class VideoRenderManager {
|
|
|
99
102
|
const animationTime = i * frameDuration * speed;
|
|
100
103
|
|
|
101
104
|
// 1. Seek animation to frame time
|
|
102
|
-
const positions = app.animationManager.seekTo( animationTime, clipIndex );
|
|
105
|
+
const positions = clip ? app.animationManager.seekTo( animationTime, clipIndex ) : null;
|
|
106
|
+
|
|
107
|
+
// After the clip, which moves a followed model camera.
|
|
108
|
+
if ( timeline?.seek( i * frameDuration ) ) {
|
|
109
|
+
|
|
110
|
+
// A moving camera changes what is under the focus point.
|
|
111
|
+
app.cameraManager.resetAutoFocus();
|
|
112
|
+
app.cameraManager.updateAutoFocus();
|
|
113
|
+
|
|
114
|
+
}
|
|
103
115
|
|
|
104
116
|
// 2. Refit BVH with deformed positions (also calls reset())
|
|
105
117
|
if ( positions ) {
|
|
@@ -268,16 +280,21 @@ export class VideoRenderManager {
|
|
|
268
280
|
|
|
269
281
|
/**
|
|
270
282
|
* Save engine state that we'll modify during video render.
|
|
283
|
+
* @param {Object} options
|
|
284
|
+
* @param {boolean} options.view - a timeline will move the camera, and auto-focus with it
|
|
271
285
|
* @private
|
|
272
286
|
*/
|
|
273
|
-
_saveState() {
|
|
287
|
+
_saveState( { view } ) {
|
|
274
288
|
|
|
275
289
|
const app = this._app;
|
|
276
290
|
const effective = app.settings.getEffective();
|
|
291
|
+
const keys = Object.keys( modePresetSettings( PRODUCTION_RENDER_CONFIG ) );
|
|
292
|
+
if ( view ) keys.push( 'focusDistance' );
|
|
277
293
|
|
|
278
294
|
return {
|
|
295
|
+
view: view ? app.cameraManager.captureView() : null,
|
|
279
296
|
// Everything configureForMode( 'production' ) overwrites, with who set it.
|
|
280
|
-
settings:
|
|
297
|
+
settings: keys.map( key => [ key, effective[ key ] ] ),
|
|
281
298
|
renderMode: app.stages.pathTracer?.renderMode?.value,
|
|
282
299
|
controlsEnabled: app.cameraManager.controls?.enabled,
|
|
283
300
|
oidnEnabled: app.denoisingManager?.finalDenoise,
|
|
@@ -321,6 +338,8 @@ export class VideoRenderManager {
|
|
|
321
338
|
|
|
322
339
|
app.pauseRendering = state.pauseRendering ?? false;
|
|
323
340
|
|
|
341
|
+
if ( state.view ) app.cameraManager.applyPose( state.view );
|
|
342
|
+
|
|
324
343
|
// configureForMode( 'production' ) suspended the live-view refresh and nothing here switches
|
|
325
344
|
// back, so the viewport would stay un-denoised until the host changed tab.
|
|
326
345
|
app.denoisingManager?.setCadenceSuspended( false );
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { OrthographicCamera, PerspectiveCamera } from 'three';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The view's camera: a perspective camera that can also project orthographically.
|
|
5
|
+
*
|
|
6
|
+
* It switches in place, so everything holding it (the controls, picking, the gizmo, the overlays
|
|
7
|
+
* and the path tracer) follows. three.js reads the camera's type from the two flags below, and an
|
|
8
|
+
* orthographic camera's frustum from `top`, `bottom`, `left` and `right`.
|
|
9
|
+
*/
|
|
10
|
+
export class ViewCamera extends PerspectiveCamera {
|
|
11
|
+
|
|
12
|
+
constructor( fov, aspect, near, far ) {
|
|
13
|
+
|
|
14
|
+
super( fov, aspect, near, far );
|
|
15
|
+
|
|
16
|
+
this.orthographic = false;
|
|
17
|
+
/** Half the view's height in world units while orthographic, before `zoom`. */
|
|
18
|
+
this.orthoHalfHeight = 1;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get isPerspectiveCamera() {
|
|
23
|
+
|
|
24
|
+
return ! this.orthographic;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// PerspectiveCamera's constructor assigns the flag.
|
|
29
|
+
set isPerspectiveCamera( value ) {}
|
|
30
|
+
|
|
31
|
+
get isOrthographicCamera() {
|
|
32
|
+
|
|
33
|
+
return this.orthographic;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get top() {
|
|
38
|
+
|
|
39
|
+
return this.orthoHalfHeight;
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get bottom() {
|
|
44
|
+
|
|
45
|
+
return - this.orthoHalfHeight;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get right() {
|
|
50
|
+
|
|
51
|
+
return this.orthoHalfHeight * this.aspect;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get left() {
|
|
56
|
+
|
|
57
|
+
return - this.orthoHalfHeight * this.aspect;
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
updateProjectionMatrix() {
|
|
62
|
+
|
|
63
|
+
if ( this.orthographic ) OrthographicCamera.prototype.updateProjectionMatrix.call( this );
|
|
64
|
+
else super.updateProjectionMatrix();
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
copy( source, recursive ) {
|
|
69
|
+
|
|
70
|
+
super.copy( source, recursive );
|
|
71
|
+
this.orthographic = source.orthographic === true;
|
|
72
|
+
this.orthoHalfHeight = source.orthoHalfHeight ?? this.orthoHalfHeight;
|
|
73
|
+
return this;
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { Controls, MathUtils, Quaternion, Vector3 } from 'three';
|
|
2
|
+
|
|
3
|
+
const CROSSING_SECONDS = 8;
|
|
4
|
+
const FAST = 4;
|
|
5
|
+
const SLOW = 0.25;
|
|
6
|
+
const MAX_PITCH = MathUtils.degToRad( 89 );
|
|
7
|
+
const MAX_STEP_SECONDS = 0.25;
|
|
8
|
+
|
|
9
|
+
// Right, forward, up.
|
|
10
|
+
const KEY_DIRECTIONS = {
|
|
11
|
+
KeyD: [ 1, 0, 0 ], ArrowRight: [ 1, 0, 0 ],
|
|
12
|
+
KeyA: [ - 1, 0, 0 ], ArrowLeft: [ - 1, 0, 0 ],
|
|
13
|
+
KeyW: [ 0, 1, 0 ], ArrowUp: [ 0, 1, 0 ],
|
|
14
|
+
KeyS: [ 0, - 1, 0 ], ArrowDown: [ 0, - 1, 0 ],
|
|
15
|
+
KeyE: [ 0, 0, 1 ],
|
|
16
|
+
KeyQ: [ 0, 0, - 1 ],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Focus that types, or opens a list, keeps its keys. A widget that handled a key prevents its default.
|
|
20
|
+
const TYPES_KEYS = 'input, textarea, select, [contenteditable]:not([contenteditable="false"]), '
|
|
21
|
+
+ '[role=textbox], [role=combobox], [role=listbox], [role=menu], [role=menuitem]';
|
|
22
|
+
|
|
23
|
+
const speedFactor = event => event.shiftKey ? FAST : event.altKey ? SLOW : 1;
|
|
24
|
+
|
|
25
|
+
const _changeEvent = { type: 'change' };
|
|
26
|
+
const _startEvent = { type: 'start' };
|
|
27
|
+
const _forward = new Vector3();
|
|
28
|
+
const _right = new Vector3();
|
|
29
|
+
const _move = new Vector3();
|
|
30
|
+
const _axis = new Vector3();
|
|
31
|
+
const _q = new Quaternion();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* First-person navigation: drag to look, W A S D or the arrows to walk level, E and Q to rise
|
|
35
|
+
* and sink, Shift faster, Alt slower.
|
|
36
|
+
*
|
|
37
|
+
* Rides on the orbit controls: moves only while they are enabled, and keeps their target ahead
|
|
38
|
+
* of the camera, since they re-aim the camera at it every frame.
|
|
39
|
+
*/
|
|
40
|
+
export class WalkControls extends Controls {
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {import('three').PerspectiveCamera} camera
|
|
44
|
+
* @param {HTMLElement} domElement
|
|
45
|
+
* @param {import('three/addons/controls/OrbitControls.js').OrbitControls} orbit
|
|
46
|
+
*/
|
|
47
|
+
constructor( camera, domElement, orbit ) {
|
|
48
|
+
|
|
49
|
+
super( camera, domElement );
|
|
50
|
+
|
|
51
|
+
this.orbit = orbit;
|
|
52
|
+
this.enabled = false;
|
|
53
|
+
/** Scene units per second. */
|
|
54
|
+
this.speed = 1 / CROSSING_SECONDS;
|
|
55
|
+
|
|
56
|
+
this._held = new Set();
|
|
57
|
+
this._factor = 1;
|
|
58
|
+
this._lastTime = 0;
|
|
59
|
+
this._drag = null;
|
|
60
|
+
|
|
61
|
+
this._onKeyDown = this._onKeyDown.bind( this );
|
|
62
|
+
this._onKeyUp = this._onKeyUp.bind( this );
|
|
63
|
+
this.release = this.release.bind( this );
|
|
64
|
+
this._onPointerDown = this._onPointerDown.bind( this );
|
|
65
|
+
this._onPointerMove = this._onPointerMove.bind( this );
|
|
66
|
+
this._onPointerUp = this._onPointerUp.bind( this );
|
|
67
|
+
|
|
68
|
+
if ( domElement ) this.connect( domElement );
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get active() {
|
|
73
|
+
|
|
74
|
+
return this.enabled && this.orbit.enabled;
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Cross a scene this long in about eight seconds. */
|
|
79
|
+
fitSpeed( sceneSize ) {
|
|
80
|
+
|
|
81
|
+
this.speed = ( sceneSize > 0 ? sceneSize : 1 ) / CROSSING_SECONDS;
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
connect( element ) {
|
|
86
|
+
|
|
87
|
+
super.connect( element );
|
|
88
|
+
const doc = element.ownerDocument;
|
|
89
|
+
doc?.addEventListener( 'keydown', this._onKeyDown );
|
|
90
|
+
doc?.addEventListener( 'keyup', this._onKeyUp );
|
|
91
|
+
doc?.addEventListener( 'visibilitychange', this.release );
|
|
92
|
+
doc?.defaultView?.addEventListener( 'blur', this.release );
|
|
93
|
+
element.addEventListener( 'pointerdown', this._onPointerDown );
|
|
94
|
+
element.addEventListener( 'pointermove', this._onPointerMove );
|
|
95
|
+
element.addEventListener( 'pointerup', this._onPointerUp );
|
|
96
|
+
element.addEventListener( 'pointercancel', this._onPointerUp );
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
disconnect() {
|
|
101
|
+
|
|
102
|
+
const element = this.domElement;
|
|
103
|
+
if ( ! element ) return;
|
|
104
|
+
const doc = element.ownerDocument;
|
|
105
|
+
doc?.removeEventListener( 'keydown', this._onKeyDown );
|
|
106
|
+
doc?.removeEventListener( 'keyup', this._onKeyUp );
|
|
107
|
+
doc?.removeEventListener( 'visibilitychange', this.release );
|
|
108
|
+
doc?.defaultView?.removeEventListener( 'blur', this.release );
|
|
109
|
+
element.removeEventListener( 'pointerdown', this._onPointerDown );
|
|
110
|
+
element.removeEventListener( 'pointermove', this._onPointerMove );
|
|
111
|
+
element.removeEventListener( 'pointerup', this._onPointerUp );
|
|
112
|
+
element.removeEventListener( 'pointercancel', this._onPointerUp );
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
dispose() {
|
|
117
|
+
|
|
118
|
+
this.disconnect();
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
update( delta = this._elapsed() ) {
|
|
123
|
+
|
|
124
|
+
if ( ! this._held.size || ! this.active ) return false;
|
|
125
|
+
|
|
126
|
+
let right = 0, forward = 0, up = 0;
|
|
127
|
+
for ( const code of this._held ) {
|
|
128
|
+
|
|
129
|
+
const [ r, f, u ] = KEY_DIRECTIONS[ code ];
|
|
130
|
+
right += r;
|
|
131
|
+
forward += f;
|
|
132
|
+
up += u;
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ( ! right && ! forward && ! up ) return false;
|
|
137
|
+
|
|
138
|
+
const camera = this.object;
|
|
139
|
+
// Level axes from the camera's right, which stays level however far it looks down.
|
|
140
|
+
_right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion );
|
|
141
|
+
_forward.crossVectors( camera.up, _right ).normalize();
|
|
142
|
+
_right.crossVectors( _forward, camera.up );
|
|
143
|
+
|
|
144
|
+
_move.set( 0, 0, 0 )
|
|
145
|
+
.addScaledVector( _right, right )
|
|
146
|
+
.addScaledVector( _forward, forward )
|
|
147
|
+
.addScaledVector( camera.up, up )
|
|
148
|
+
.normalize()
|
|
149
|
+
.multiplyScalar( this.speed * this._factor * delta );
|
|
150
|
+
camera.position.add( _move );
|
|
151
|
+
this.orbit.target.add( _move );
|
|
152
|
+
camera.updateMatrixWorld();
|
|
153
|
+
|
|
154
|
+
this.dispatchEvent( _changeEvent );
|
|
155
|
+
return true;
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_elapsed() {
|
|
160
|
+
|
|
161
|
+
const now = performance.now();
|
|
162
|
+
const delta = Math.min( ( now - this._lastTime ) / 1000, MAX_STEP_SECONDS );
|
|
163
|
+
this._lastTime = now;
|
|
164
|
+
return delta;
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Turn by a drag of dx, dy pixels, so the scene follows the pointer. */
|
|
169
|
+
_look( dx, dy ) {
|
|
170
|
+
|
|
171
|
+
const camera = this.object;
|
|
172
|
+
const target = this.orbit.target;
|
|
173
|
+
const distance = camera.position.distanceTo( target ) || 1;
|
|
174
|
+
const radiansPerPixel = MathUtils.degToRad( camera.fov ) / ( this.domElement.clientHeight || 1 );
|
|
175
|
+
|
|
176
|
+
camera.quaternion.premultiply( _q.setFromAxisAngle( camera.up, dx * radiansPerPixel ) );
|
|
177
|
+
|
|
178
|
+
camera.getWorldDirection( _forward );
|
|
179
|
+
const pitch = Math.asin( MathUtils.clamp( _forward.dot( camera.up ), - 1, 1 ) );
|
|
180
|
+
const nextPitch = MathUtils.clamp( pitch + dy * radiansPerPixel, - MAX_PITCH, MAX_PITCH );
|
|
181
|
+
camera.quaternion.multiply( _q.setFromAxisAngle( _axis.set( 1, 0, 0 ), nextPitch - pitch ) );
|
|
182
|
+
camera.updateMatrixWorld();
|
|
183
|
+
|
|
184
|
+
target.copy( camera.position ).addScaledVector( camera.getWorldDirection( _forward ), distance );
|
|
185
|
+
this.dispatchEvent( _changeEvent );
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_onKeyDown( event ) {
|
|
190
|
+
|
|
191
|
+
if ( ! this.active || event.defaultPrevented || event.target?.closest?.( TYPES_KEYS ) ) return;
|
|
192
|
+
|
|
193
|
+
// Browser shortcuts pass through, and macOS drops the key-up of a key pressed while Cmd is down.
|
|
194
|
+
if ( event.metaKey || event.ctrlKey ) {
|
|
195
|
+
|
|
196
|
+
this.release();
|
|
197
|
+
return;
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this._factor = speedFactor( event );
|
|
202
|
+
if ( ! KEY_DIRECTIONS[ event.code ] ) return;
|
|
203
|
+
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
if ( this._held.has( event.code ) ) return;
|
|
206
|
+
this._held.add( event.code );
|
|
207
|
+
|
|
208
|
+
// After the key is held: a woken loop runs its first frame inside this dispatch.
|
|
209
|
+
if ( this._held.size === 1 ) {
|
|
210
|
+
|
|
211
|
+
this._lastTime = performance.now();
|
|
212
|
+
this.dispatchEvent( _startEvent );
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_onKeyUp( event ) {
|
|
219
|
+
|
|
220
|
+
this._factor = speedFactor( event );
|
|
221
|
+
this._held.delete( event.code );
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Let go of every held key. */
|
|
226
|
+
release() {
|
|
227
|
+
|
|
228
|
+
this._held.clear();
|
|
229
|
+
this._factor = 1;
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_onPointerDown( event ) {
|
|
234
|
+
|
|
235
|
+
if ( ! this.active || event.button !== 0 || ! event.isPrimary ) return;
|
|
236
|
+
this._drag = { id: event.pointerId, x: event.clientX, y: event.clientY };
|
|
237
|
+
this.domElement.setPointerCapture?.( event.pointerId );
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_onPointerMove( event ) {
|
|
242
|
+
|
|
243
|
+
const drag = this._drag;
|
|
244
|
+
if ( ! drag || event.pointerId !== drag.id ) return;
|
|
245
|
+
|
|
246
|
+
const dx = event.clientX - drag.x;
|
|
247
|
+
const dy = event.clientY - drag.y;
|
|
248
|
+
drag.x = event.clientX;
|
|
249
|
+
drag.y = event.clientY;
|
|
250
|
+
if ( this.active && ( dx || dy ) ) this._look( dx, dy );
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_onPointerUp( event ) {
|
|
255
|
+
|
|
256
|
+
if ( this._drag?.id !== event.pointerId ) return;
|
|
257
|
+
this._drag = null;
|
|
258
|
+
this.domElement.releasePointerCapture?.( event.pointerId );
|
|
259
|
+
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
}
|
|
@@ -28,12 +28,22 @@ export class OutlineHelper {
|
|
|
28
28
|
|
|
29
29
|
this.layer = 'scene';
|
|
30
30
|
this._enabled = true;
|
|
31
|
+
this._scene = scene;
|
|
32
|
+
this._camera = camera;
|
|
33
|
+
this._build( [] );
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// OutlineNode reads the camera's type once, as it builds its depth test.
|
|
38
|
+
_build( selectedObjects ) {
|
|
39
|
+
|
|
40
|
+
this._orthographic = this._camera.isOrthographicCamera === true;
|
|
31
41
|
|
|
32
42
|
// Outline node (handles its own multi-pass rendering via updateBefore).
|
|
33
43
|
// It sizes its render targets from the drawing buffer of whatever renderer
|
|
34
44
|
// draws it — the view overlay surface — so no size override is needed.
|
|
35
|
-
this._outlineNode = outline(
|
|
36
|
-
selectedObjects
|
|
45
|
+
this._outlineNode = outline( this._scene, this._camera, {
|
|
46
|
+
selectedObjects,
|
|
37
47
|
edgeThickness: uniform( 1.0 ),
|
|
38
48
|
edgeGlow: uniform( 0.0 ),
|
|
39
49
|
} );
|
|
@@ -98,6 +108,16 @@ export class OutlineHelper {
|
|
|
98
108
|
|
|
99
109
|
if ( ! this.visible ) return;
|
|
100
110
|
|
|
111
|
+
if ( this._camera.isOrthographicCamera !== this._orthographic ) {
|
|
112
|
+
|
|
113
|
+
// dispose() empties the list it was given.
|
|
114
|
+
const selected = [ ...this._outlineNode.selectedObjects ];
|
|
115
|
+
this._outlineNode.dispose();
|
|
116
|
+
this._material.dispose();
|
|
117
|
+
this._build( selected );
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
const prevAutoClear = renderer.autoClear;
|
|
102
122
|
renderer.autoClear = false;
|
|
103
123
|
renderer.setRenderTarget( null );
|