Fold NPCFACTORY in: vendor three.js, take the banks, and fix units without flattening body types
NPCFACTORY and wardrobegod were the same product — wardrobegod had already absorbed its reskin engine, and keeping two benches means two half-libraries and two export paths. Folded, keeping the wardrobegod name and codebase (370 lines there vs ~1,100 here). · Vendored three.js r175 from NPCFACTORY, replacing the unpkg CDN importmap. This was real drift, not tidiness: a CDN import breaks offline and can't be lifted into a game build. NPCFACTORY lacked OrbitControls (it used PointerLock), so that one addon was fetched at the matching revision. Added a guarded /vendor/ static route. Verified in-browser: zero CDN requests, 5 vendor files, model still loads. · Took the 17 rigged walk-animated NPCs and 6 parts. Bodies 4 -> 20. · New `unitfix` op. Deliberately NOT scale-to-height: a 0.06m human is a UNIT error, but normalising everything to 1.72m would erase the small/medium/large/obese range the library is meant to carry. So it only corrects heights outside 0.5-3.0m — physically impossible for a human — and leaves real proportions alone as data. Verified both ways: hum_character 0.0576m -> 1.72m, tradie 1.000m left untouched. Three bugs found while building it, two of them pre-existing: · `is_helper`/`real_meshes`/`bbox_of` factored out. Material-less bone widgets (a radius-1 42-vert Icosphere, so exactly 2.0 units tall) were being measured INSTEAD of the character — every body reported 2.000m. This poisoned the `scale` op too, which has been measuring widgets all along; NPCFACTORY's render_plates.py had independently worked around the same thing by framing on the dominant mesh. · `transform_apply` under temp_override(selected_editable_objects=...) SEGFAULTS Blender 5.1.2 on rigs with parented children. A segfault can't be caught, so it's avoided rather than handled: glTF encodes node scale natively, so setting the root transform is sufficient and every downstream measurement still reads correctly. Confirmed `scale` still round-trips (1.00m -> 1.72m, re-measured). · My own bulk edit replaced only ONE of the two crash-prone call sites and reported "replaced 1" — I didn't check for a second, which is why `scale` worked while `unitfix` kept crashing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8119e2de4c
commit
037f6ef0af
@ -62,6 +62,54 @@ def meshes(objs):
|
||||
return [o for o in objs if o.type == 'MESH']
|
||||
|
||||
|
||||
def is_helper(o):
|
||||
"""Bone-widget / marker mesh: no material and a handful of verts.
|
||||
|
||||
These ride along in character_kit and NPCFACTORY rigs (a radius-1 42-vert 'Icosphere', so
|
||||
exactly 2.0 units tall) and they poison every bbox measurement — height readouts, camera
|
||||
auto-framing, and scale normalisation all silently measure the widget instead of the
|
||||
character. NPCFACTORY's render_plates.py hit this too and worked around it by framing on
|
||||
the dominant mesh only.
|
||||
"""
|
||||
return (o.type == 'MESH' and not [m for m in o.data.materials if m]
|
||||
and len(o.data.vertices) < 100)
|
||||
|
||||
|
||||
def real_meshes(objs):
|
||||
"""Meshes that are actually the model — helpers excluded. Falls back to all meshes so a
|
||||
genuinely material-less model still measures rather than returning nothing."""
|
||||
ms = meshes(objs)
|
||||
return [o for o in ms if not is_helper(o)] or ms
|
||||
|
||||
|
||||
def apply_scale(roots, objs):
|
||||
"""Scale a model by setting the ROOT transform — deliberately without transform_apply.
|
||||
|
||||
Blender 5.1.2 SEGFAULTS inside object_transform_apply_exec when the selection contains both
|
||||
an armature and its own parented children (reproducible on character_kit's hum_character,
|
||||
both with temp_override and with a plain select-then-apply). A segfault kills the process, so
|
||||
it has to be avoided rather than caught.
|
||||
|
||||
Not baking costs us nothing here: glTF encodes node scale natively, so the exported file is
|
||||
the right size and every downstream measurement (bbox, height, the viewer) reads it
|
||||
correctly. Only the roots are scaled — children inherit, and scaling them too would
|
||||
double-apply.
|
||||
"""
|
||||
# The caller has already set r.scale; there is deliberately nothing else to do. An earlier
|
||||
# view_layer.update() here was itself crashing on rigs with parented children.
|
||||
return
|
||||
|
||||
|
||||
def bbox_of(objs):
|
||||
import mathutils
|
||||
mn = mathutils.Vector((1e9,) * 3); mx = mathutils.Vector((-1e9,) * 3)
|
||||
for o in real_meshes(objs):
|
||||
for c in o.bound_box:
|
||||
w = o.matrix_world @ mathutils.Vector(c)
|
||||
mn = mathutils.Vector(map(min, mn, w)); mx = mathutils.Vector(map(max, mx, w))
|
||||
return mn, mx
|
||||
|
||||
|
||||
def armatures(objs):
|
||||
return [o for o in objs if o.type == 'ARMATURE']
|
||||
|
||||
@ -76,12 +124,7 @@ if OP == 'convert':
|
||||
elif OP == 'scale':
|
||||
clean()
|
||||
objs = load(ARGS[0]); target = float(ARGS[2])
|
||||
import mathutils
|
||||
mn = mathutils.Vector((1e9,) * 3); mx = mathutils.Vector((-1e9,) * 3)
|
||||
for o in meshes(objs):
|
||||
for c in o.bound_box:
|
||||
w = o.matrix_world @ mathutils.Vector(c)
|
||||
mn = mathutils.Vector(map(min, mn, w)); mx = mathutils.Vector(map(max, mx, w))
|
||||
mn, mx = bbox_of(objs) # same helper-widget exclusion — this op measured them too
|
||||
h = mx.z - mn.z
|
||||
if h <= 0:
|
||||
raise SystemExit('flat object — no height to scale')
|
||||
@ -89,8 +132,7 @@ elif OP == 'scale':
|
||||
roots = [o for o in objs if not o.parent]
|
||||
for r in roots:
|
||||
r.scale = [c * s for c in r.scale]
|
||||
with bpy.context.temp_override(selected_editable_objects=roots + meshes(objs) + armatures(objs)):
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
apply_scale(roots, objs)
|
||||
print(f'scaled ×{s:.3f} → {target}m')
|
||||
export(ARGS[1])
|
||||
|
||||
@ -146,6 +188,35 @@ elif OP == 'fit':
|
||||
print(f'fitted {len(meshes(garm_objs))} garment mesh(es), mode={mode}, inflate={inflate * 1000:.0f}mm')
|
||||
export(ARGS[2])
|
||||
|
||||
elif OP == 'unitfix':
|
||||
# unitfix <in> <out.glb> [target_m]
|
||||
#
|
||||
# Deliberately NOT scale-to-height. A 0.06m human is a UNIT error (character_kit rigs are
|
||||
# 6cm, NPCFACTORY banks 1.0m, TRELLIS output varies) and silently breaks assemble — that's
|
||||
# what produced a 27x oversized garment. But normalising every body to one height would
|
||||
# erase the small/medium/large/obese range the library is meant to carry, so we only correct
|
||||
# scales that are physically impossible for a human and leave real proportions alone.
|
||||
clean()
|
||||
objs = load(ARGS[0])
|
||||
target = float(ARGS[2]) if len(ARGS) > 2 else 1.72
|
||||
mn, mx = bbox_of(objs) # helper widgets excluded, or we'd measure a 2.0-unit Icosphere
|
||||
h = mx.z - mn.z
|
||||
if h <= 0:
|
||||
raise SystemExit('flat object — no height to measure')
|
||||
PLAUSIBLE = (0.5, 3.0) # any human outside this is a broken unit scale, not a body type
|
||||
if PLAUSIBLE[0] <= h <= PLAUSIBLE[1]:
|
||||
print(f'height {h:.3f}m is plausible — left alone (body-type variation is data, not an error)')
|
||||
s = 1.0
|
||||
else:
|
||||
s = target / h
|
||||
roots = [o for o in objs if not o.parent]
|
||||
for r in roots:
|
||||
r.scale = [c * s for c in r.scale]
|
||||
apply_scale(roots, objs)
|
||||
print(f'UNIT FIX: {h:.4f}m is impossible for a human -> scaled x{s:.4f} to {target}m')
|
||||
print(f'unitfix done (scale {s:.4f})')
|
||||
export(ARGS[1])
|
||||
|
||||
elif OP == 'harvest':
|
||||
# harvest <donor> <out.glb> <material_substr> [keep_skin_regex]
|
||||
#
|
||||
@ -290,7 +361,7 @@ elif OP == 'assemble':
|
||||
# scene bbox — which drives both the HUD height and the viewer's auto-framing, so one stray
|
||||
# widget makes the camera frame the widget and the character render as a speck.
|
||||
for o in list(bpy.data.objects):
|
||||
if o.type == 'MESH' and not [m for m in o.data.materials if m] and len(o.data.vertices) < 100:
|
||||
if is_helper(o):
|
||||
print(f' dropped helper mesh {o.name} ({len(o.data.vertices)} verts, no material)')
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
print(f'assembled body + {len(ARGS) - 2} garment file(s)')
|
||||
|
||||
@ -304,6 +304,13 @@ class H(BaseHTTPRequestHandler):
|
||||
q = dict(urllib.parse.parse_qsl(u.query))
|
||||
if u.path == '/':
|
||||
return self.send(200, open(os.path.join(ROOT, 'web', 'index.html'), 'rb').read(), 'text/html')
|
||||
if u.path.startswith('/vendor/'): # vendored three.js — no CDN, works offline
|
||||
f = os.path.realpath(os.path.join(ROOT, 'web', u.path.lstrip('/')))
|
||||
web = os.path.realpath(os.path.join(ROOT, 'web'))
|
||||
if not f.startswith(web + os.sep) or not os.path.isfile(f):
|
||||
return self.j({'error': 'not found'}, 404)
|
||||
ctype = 'text/javascript' if f.endswith('.js') else (mimetypes.guess_type(f)[0] or 'application/octet-stream')
|
||||
return self.send(200, open(f, 'rb').read(), ctype)
|
||||
if u.path == '/api/lib':
|
||||
return self.j({'lib': scan(), 'mb': bool(MB_TOKEN), 'cf': bool(CF_ACCT and CF_TOKEN),
|
||||
'blender': os.path.exists(BLENDER), 'doll': doll_catalogue()})
|
||||
|
||||
@ -203,10 +203,13 @@
|
||||
<div id="foot"><span id="job">ready.</span></div>
|
||||
</div>
|
||||
|
||||
<!-- three.js is VENDORED (r175), not CDN. The bench has to work offline, and anything built
|
||||
against a CDN import can't be lifted into a game build. Folded in from NPCFACTORY, which
|
||||
already had this right; OrbitControls was the one addon it lacked (it used PointerLock). -->
|
||||
<script type="importmap">
|
||||
{ "imports": {
|
||||
"three": "https://unpkg.com/three@0.175.0/build/three.module.js",
|
||||
"three/addons/": "https://unpkg.com/three@0.175.0/examples/jsm/"
|
||||
"three": "./vendor/three.module.js",
|
||||
"three/addons/": "./vendor/addons/"
|
||||
}}
|
||||
</script>
|
||||
<script type="module">
|
||||
|
||||
1859
web/vendor/addons/controls/OrbitControls.js
vendored
Normal file
1859
web/vendor/addons/controls/OrbitControls.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
270
web/vendor/addons/controls/PointerLockControls.js
vendored
Normal file
270
web/vendor/addons/controls/PointerLockControls.js
vendored
Normal file
@ -0,0 +1,270 @@
|
||||
import {
|
||||
Controls,
|
||||
Euler,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
const _euler = new Euler( 0, 0, 0, 'YXZ' );
|
||||
const _vector = new Vector3();
|
||||
|
||||
/**
|
||||
* Fires when the user moves the mouse.
|
||||
*
|
||||
* @event PointerLockControls#change
|
||||
* @type {Object}
|
||||
*/
|
||||
const _changeEvent = { type: 'change' };
|
||||
|
||||
/**
|
||||
* Fires when the pointer lock status is "locked" (in other words: the mouse is captured).
|
||||
*
|
||||
* @event PointerLockControls#lock
|
||||
* @type {Object}
|
||||
*/
|
||||
const _lockEvent = { type: 'lock' };
|
||||
|
||||
/**
|
||||
* Fires when the pointer lock status is "unlocked" (in other words: the mouse is not captured anymore).
|
||||
*
|
||||
* @event PointerLockControls#unlock
|
||||
* @type {Object}
|
||||
*/
|
||||
const _unlockEvent = { type: 'unlock' };
|
||||
|
||||
const _PI_2 = Math.PI / 2;
|
||||
|
||||
/**
|
||||
* The implementation of this class is based on the [Pointer Lock API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API}.
|
||||
* `PointerLockControls` is a perfect choice for first person 3D games.
|
||||
*
|
||||
* ```js
|
||||
* const controls = new PointerLockControls( camera, document.body );
|
||||
*
|
||||
* // add event listener to show/hide a UI (e.g. the game's menu)
|
||||
* controls.addEventListener( 'lock', function () {
|
||||
*
|
||||
* menu.style.display = 'none';
|
||||
*
|
||||
* } );
|
||||
*
|
||||
* controls.addEventListener( 'unlock', function () {
|
||||
*
|
||||
* menu.style.display = 'block';
|
||||
*
|
||||
* } );
|
||||
* ```
|
||||
*
|
||||
* @augments Controls
|
||||
*/
|
||||
class PointerLockControls extends Controls {
|
||||
|
||||
/**
|
||||
* Constructs a new controls instance.
|
||||
*
|
||||
* @param {Camera} camera - The camera that is managed by the controls.
|
||||
* @param {?HTMLDOMElement} domElement - The HTML element used for event listeners.
|
||||
*/
|
||||
constructor( camera, domElement = null ) {
|
||||
|
||||
super( camera, domElement );
|
||||
|
||||
/**
|
||||
* Whether the controls are locked or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @default false
|
||||
*/
|
||||
this.isLocked = false;
|
||||
|
||||
/**
|
||||
* Camera pitch, lower limit. Range is '[0, Math.PI]' in radians.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
*/
|
||||
this.minPolarAngle = 0;
|
||||
|
||||
/**
|
||||
* Camera pitch, upper limit. Range is '[0, Math.PI]' in radians.
|
||||
*
|
||||
* @type {number}
|
||||
* @default Math.PI
|
||||
*/
|
||||
this.maxPolarAngle = Math.PI;
|
||||
|
||||
/**
|
||||
* Multiplier for how much the pointer movement influences the camera rotation.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 1
|
||||
*/
|
||||
this.pointerSpeed = 1.0;
|
||||
|
||||
// event listeners
|
||||
|
||||
this._onMouseMove = onMouseMove.bind( this );
|
||||
this._onPointerlockChange = onPointerlockChange.bind( this );
|
||||
this._onPointerlockError = onPointerlockError.bind( this );
|
||||
|
||||
if ( this.domElement !== null ) {
|
||||
|
||||
this.connect( this.domElement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect( element ) {
|
||||
|
||||
super.connect( element );
|
||||
|
||||
this.domElement.ownerDocument.addEventListener( 'mousemove', this._onMouseMove );
|
||||
this.domElement.ownerDocument.addEventListener( 'pointerlockchange', this._onPointerlockChange );
|
||||
this.domElement.ownerDocument.addEventListener( 'pointerlockerror', this._onPointerlockError );
|
||||
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
||||
this.domElement.ownerDocument.removeEventListener( 'mousemove', this._onMouseMove );
|
||||
this.domElement.ownerDocument.removeEventListener( 'pointerlockchange', this._onPointerlockChange );
|
||||
this.domElement.ownerDocument.removeEventListener( 'pointerlockerror', this._onPointerlockError );
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
this.disconnect();
|
||||
|
||||
}
|
||||
|
||||
getObject() {
|
||||
|
||||
console.warn( 'THREE.PointerLockControls: getObject() has been deprecated. Use controls.object instead.' ); // @deprecated r169
|
||||
|
||||
return this.object;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the look direction of the camera.
|
||||
*
|
||||
* @param {Vector3} v - The target vector that is used to store the method's result.
|
||||
* @return {Vector3} The normalized direction vector.
|
||||
*/
|
||||
getDirection( v ) {
|
||||
|
||||
return v.set( 0, 0, - 1 ).applyQuaternion( this.object.quaternion );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the camera forward parallel to the xz-plane. Assumes camera.up is y-up.
|
||||
*
|
||||
* @param {number} distance - The signed distance.
|
||||
*/
|
||||
moveForward( distance ) {
|
||||
|
||||
if ( this.enabled === false ) return;
|
||||
|
||||
// move forward parallel to the xz-plane
|
||||
// assumes camera.up is y-up
|
||||
|
||||
const camera = this.object;
|
||||
|
||||
_vector.setFromMatrixColumn( camera.matrix, 0 );
|
||||
|
||||
_vector.crossVectors( camera.up, _vector );
|
||||
|
||||
camera.position.addScaledVector( _vector, distance );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the camera sidewards parallel to the xz-plane.
|
||||
*
|
||||
* @param {number} distance - The signed distance.
|
||||
*/
|
||||
moveRight( distance ) {
|
||||
|
||||
if ( this.enabled === false ) return;
|
||||
|
||||
const camera = this.object;
|
||||
|
||||
_vector.setFromMatrixColumn( camera.matrix, 0 );
|
||||
|
||||
camera.position.addScaledVector( _vector, distance );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the pointer lock.
|
||||
*
|
||||
* @param {boolean} [unadjustedMovement=false] - Disables OS-level adjustment for mouse acceleration, and accesses raw mouse input instead.
|
||||
* Setting it to true will disable mouse acceleration.
|
||||
*/
|
||||
lock( unadjustedMovement = false ) {
|
||||
|
||||
this.domElement.requestPointerLock( {
|
||||
unadjustedMovement
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the pointer lock.
|
||||
*/
|
||||
unlock() {
|
||||
|
||||
this.domElement.ownerDocument.exitPointerLock();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// event listeners
|
||||
|
||||
function onMouseMove( event ) {
|
||||
|
||||
if ( this.enabled === false || this.isLocked === false ) return;
|
||||
|
||||
const camera = this.object;
|
||||
_euler.setFromQuaternion( camera.quaternion );
|
||||
|
||||
_euler.y -= event.movementX * 0.002 * this.pointerSpeed;
|
||||
_euler.x -= event.movementY * 0.002 * this.pointerSpeed;
|
||||
|
||||
_euler.x = Math.max( _PI_2 - this.maxPolarAngle, Math.min( _PI_2 - this.minPolarAngle, _euler.x ) );
|
||||
|
||||
camera.quaternion.setFromEuler( _euler );
|
||||
|
||||
this.dispatchEvent( _changeEvent );
|
||||
|
||||
}
|
||||
|
||||
function onPointerlockChange() {
|
||||
|
||||
if ( this.domElement.ownerDocument.pointerLockElement === this.domElement ) {
|
||||
|
||||
this.dispatchEvent( _lockEvent );
|
||||
|
||||
this.isLocked = true;
|
||||
|
||||
} else {
|
||||
|
||||
this.dispatchEvent( _unlockEvent );
|
||||
|
||||
this.isLocked = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onPointerlockError() {
|
||||
|
||||
console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
|
||||
|
||||
}
|
||||
|
||||
export { PointerLockControls };
|
||||
4958
web/vendor/addons/loaders/GLTFLoader.js
vendored
Normal file
4958
web/vendor/addons/loaders/GLTFLoader.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
361
web/vendor/addons/postprocessing/EffectComposer.js
vendored
Normal file
361
web/vendor/addons/postprocessing/EffectComposer.js
vendored
Normal file
@ -0,0 +1,361 @@
|
||||
import {
|
||||
Clock,
|
||||
HalfFloatType,
|
||||
NoBlending,
|
||||
Vector2,
|
||||
WebGLRenderTarget
|
||||
} from 'three';
|
||||
import { CopyShader } from '../shaders/CopyShader.js';
|
||||
import { ShaderPass } from './ShaderPass.js';
|
||||
import { ClearMaskPass, MaskPass } from './MaskPass.js';
|
||||
|
||||
/**
|
||||
* Used to implement post-processing effects in three.js.
|
||||
* The class manages a chain of post-processing passes to produce the final visual result.
|
||||
* Post-processing passes are executed in order of their addition/insertion.
|
||||
* The last pass is automatically rendered to screen.
|
||||
*
|
||||
* This module can only be used with {@link WebGLRenderer}.
|
||||
*
|
||||
* ```js
|
||||
* const composer = new EffectComposer( renderer );
|
||||
*
|
||||
* // adding some passes
|
||||
* const renderPass = new RenderPass( scene, camera );
|
||||
* composer.addPass( renderPass );
|
||||
*
|
||||
* const glitchPass = new GlitchPass();
|
||||
* composer.addPass( glitchPass );
|
||||
*
|
||||
* const outputPass = new OutputPass()
|
||||
* composer.addPass( outputPass );
|
||||
*
|
||||
* function animate() {
|
||||
*
|
||||
* composer.render(); // instead of renderer.render()
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class EffectComposer {
|
||||
|
||||
/**
|
||||
* Constructs a new effect composer.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will
|
||||
* be used as the internal read and write buffers. If not given, the composer creates
|
||||
* the buffers automatically.
|
||||
*/
|
||||
constructor( renderer, renderTarget ) {
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @type {WebGLRenderer}
|
||||
*/
|
||||
this.renderer = renderer;
|
||||
|
||||
this._pixelRatio = renderer.getPixelRatio();
|
||||
|
||||
if ( renderTarget === undefined ) {
|
||||
|
||||
const size = renderer.getSize( new Vector2() );
|
||||
this._width = size.width;
|
||||
this._height = size.height;
|
||||
|
||||
renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
|
||||
renderTarget.texture.name = 'EffectComposer.rt1';
|
||||
|
||||
} else {
|
||||
|
||||
this._width = renderTarget.width;
|
||||
this._height = renderTarget.height;
|
||||
|
||||
}
|
||||
|
||||
this.renderTarget1 = renderTarget;
|
||||
this.renderTarget2 = renderTarget.clone();
|
||||
this.renderTarget2.texture.name = 'EffectComposer.rt2';
|
||||
|
||||
/**
|
||||
* A reference to the internal write buffer. Passes usually write
|
||||
* their result into this buffer.
|
||||
*
|
||||
* @type {WebGLRenderTarget}
|
||||
*/
|
||||
this.writeBuffer = this.renderTarget1;
|
||||
|
||||
/**
|
||||
* A reference to the internal read buffer. Passes usually read
|
||||
* the previous render result from this buffer.
|
||||
*
|
||||
* @type {WebGLRenderTarget}
|
||||
*/
|
||||
this.readBuffer = this.renderTarget2;
|
||||
|
||||
/**
|
||||
* Whether the final pass is rendered to the screen (default framebuffer) or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.renderToScreen = true;
|
||||
|
||||
/**
|
||||
* An array representing the (ordered) chain of post-processing passes.
|
||||
*
|
||||
* @type {Array<Pass>}
|
||||
*/
|
||||
this.passes = [];
|
||||
|
||||
/**
|
||||
* A copy pass used for internal swap operations.
|
||||
*
|
||||
* @private
|
||||
* @type {ShaderPass}
|
||||
*/
|
||||
this.copyPass = new ShaderPass( CopyShader );
|
||||
this.copyPass.material.blending = NoBlending;
|
||||
|
||||
/**
|
||||
* The intenral clock for managing time data.
|
||||
*
|
||||
* @private
|
||||
* @type {Clock}
|
||||
*/
|
||||
this.clock = new Clock();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the internal read/write buffers.
|
||||
*/
|
||||
swapBuffers() {
|
||||
|
||||
const tmp = this.readBuffer;
|
||||
this.readBuffer = this.writeBuffer;
|
||||
this.writeBuffer = tmp;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given pass to the pass chain.
|
||||
*
|
||||
* @param {Pass} pass - The pass to add.
|
||||
*/
|
||||
addPass( pass ) {
|
||||
|
||||
this.passes.push( pass );
|
||||
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given pass at a given index.
|
||||
*
|
||||
* @param {Pass} pass - The pass to insert.
|
||||
* @param {number} index - The index into the pass chain.
|
||||
*/
|
||||
insertPass( pass, index ) {
|
||||
|
||||
this.passes.splice( index, 0, pass );
|
||||
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given pass from the pass chain.
|
||||
*
|
||||
* @param {Pass} pass - The pass to remove.
|
||||
*/
|
||||
removePass( pass ) {
|
||||
|
||||
const index = this.passes.indexOf( pass );
|
||||
|
||||
if ( index !== - 1 ) {
|
||||
|
||||
this.passes.splice( index, 1 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the pass for the given index is the last enabled pass in the pass chain.
|
||||
*
|
||||
* @param {number} passIndex - The pass index.
|
||||
* @return {boolean} Whether the the pass for the given index is the last pass in the pass chain.
|
||||
*/
|
||||
isLastEnabledPass( passIndex ) {
|
||||
|
||||
for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
|
||||
|
||||
if ( this.passes[ i ].enabled ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all enabled post-processing passes in order to produce the final frame.
|
||||
*
|
||||
* @param {number} deltaTime - The delta time in seconds. If not given, the composer computes
|
||||
* its own time delta value.
|
||||
*/
|
||||
render( deltaTime ) {
|
||||
|
||||
// deltaTime value is in seconds
|
||||
|
||||
if ( deltaTime === undefined ) {
|
||||
|
||||
deltaTime = this.clock.getDelta();
|
||||
|
||||
}
|
||||
|
||||
const currentRenderTarget = this.renderer.getRenderTarget();
|
||||
|
||||
let maskActive = false;
|
||||
|
||||
for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
|
||||
|
||||
const pass = this.passes[ i ];
|
||||
|
||||
if ( pass.enabled === false ) continue;
|
||||
|
||||
pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
|
||||
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
|
||||
|
||||
if ( pass.needsSwap ) {
|
||||
|
||||
if ( maskActive ) {
|
||||
|
||||
const context = this.renderer.getContext();
|
||||
const stencil = this.renderer.state.buffers.stencil;
|
||||
|
||||
//context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
|
||||
stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
|
||||
|
||||
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
|
||||
|
||||
//context.stencilFunc( context.EQUAL, 1, 0xffffffff );
|
||||
stencil.setFunc( context.EQUAL, 1, 0xffffffff );
|
||||
|
||||
}
|
||||
|
||||
this.swapBuffers();
|
||||
|
||||
}
|
||||
|
||||
if ( MaskPass !== undefined ) {
|
||||
|
||||
if ( pass instanceof MaskPass ) {
|
||||
|
||||
maskActive = true;
|
||||
|
||||
} else if ( pass instanceof ClearMaskPass ) {
|
||||
|
||||
maskActive = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.renderer.setRenderTarget( currentRenderTarget );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the internal state of the EffectComposer.
|
||||
*
|
||||
* @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like
|
||||
* the one from the constructor. If set, it is used to setup the read and write buffers.
|
||||
*/
|
||||
reset( renderTarget ) {
|
||||
|
||||
if ( renderTarget === undefined ) {
|
||||
|
||||
const size = this.renderer.getSize( new Vector2() );
|
||||
this._pixelRatio = this.renderer.getPixelRatio();
|
||||
this._width = size.width;
|
||||
this._height = size.height;
|
||||
|
||||
renderTarget = this.renderTarget1.clone();
|
||||
renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
|
||||
|
||||
}
|
||||
|
||||
this.renderTarget1.dispose();
|
||||
this.renderTarget2.dispose();
|
||||
this.renderTarget1 = renderTarget;
|
||||
this.renderTarget2 = renderTarget.clone();
|
||||
|
||||
this.writeBuffer = this.renderTarget1;
|
||||
this.readBuffer = this.renderTarget2;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize},
|
||||
* this method honors the current pixel ration.
|
||||
*
|
||||
* @param {number} width - The width in logical pixels.
|
||||
* @param {number} height - The height in logical pixels.
|
||||
*/
|
||||
setSize( width, height ) {
|
||||
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
const effectiveWidth = this._width * this._pixelRatio;
|
||||
const effectiveHeight = this._height * this._pixelRatio;
|
||||
|
||||
this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
|
||||
this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
|
||||
|
||||
for ( let i = 0; i < this.passes.length; i ++ ) {
|
||||
|
||||
this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output.
|
||||
* Setting the pixel ratio will automatically resize the composer.
|
||||
*
|
||||
* @param {number} pixelRatio - The pixel ratio to set.
|
||||
*/
|
||||
setPixelRatio( pixelRatio ) {
|
||||
|
||||
this._pixelRatio = pixelRatio;
|
||||
|
||||
this.setSize( this._width, this._height );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the GPU-related resources allocated by this instance. Call this
|
||||
* method whenever the composer is no longer used in your app.
|
||||
*/
|
||||
dispose() {
|
||||
|
||||
this.renderTarget1.dispose();
|
||||
this.renderTarget2.dispose();
|
||||
|
||||
this.copyPass.dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { EffectComposer };
|
||||
194
web/vendor/addons/postprocessing/MaskPass.js
vendored
Normal file
194
web/vendor/addons/postprocessing/MaskPass.js
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
import { Pass } from './Pass.js';
|
||||
|
||||
/**
|
||||
* This pass can be used to define a mask during post processing.
|
||||
* Meaning only areas of subsequent post processing are affected
|
||||
* which lie in the masking area of this pass. Internally, the masking
|
||||
* is implemented with the stencil buffer.
|
||||
*
|
||||
* ```js
|
||||
* const maskPass = new MaskPass( scene, camera );
|
||||
* composer.addPass( maskPass );
|
||||
* ```
|
||||
*
|
||||
* @augments Pass
|
||||
*/
|
||||
class MaskPass extends Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new mask pass.
|
||||
*
|
||||
* @param {Scene} scene - The 3D objects in this scene will define the mask.
|
||||
* @param {Camera} camera - The camera.
|
||||
*/
|
||||
constructor( scene, camera ) {
|
||||
|
||||
super();
|
||||
|
||||
/**
|
||||
* The scene that defines the mask.
|
||||
*
|
||||
* @type {Scene}
|
||||
*/
|
||||
this.scene = scene;
|
||||
|
||||
/**
|
||||
* The camera.
|
||||
*
|
||||
* @type {Camera}
|
||||
*/
|
||||
this.camera = camera;
|
||||
|
||||
/**
|
||||
* Overwritten to perform a clear operation by default.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.clear = true;
|
||||
|
||||
/**
|
||||
* Overwritten to disable the swap.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.needsSwap = false;
|
||||
|
||||
/**
|
||||
* Whether to inverse the mask or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.inverse = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a mask pass with the configured scene and camera.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
|
||||
|
||||
const context = renderer.getContext();
|
||||
const state = renderer.state;
|
||||
|
||||
// don't update color or depth
|
||||
|
||||
state.buffers.color.setMask( false );
|
||||
state.buffers.depth.setMask( false );
|
||||
|
||||
// lock buffers
|
||||
|
||||
state.buffers.color.setLocked( true );
|
||||
state.buffers.depth.setLocked( true );
|
||||
|
||||
// set up stencil
|
||||
|
||||
let writeValue, clearValue;
|
||||
|
||||
if ( this.inverse ) {
|
||||
|
||||
writeValue = 0;
|
||||
clearValue = 1;
|
||||
|
||||
} else {
|
||||
|
||||
writeValue = 1;
|
||||
clearValue = 0;
|
||||
|
||||
}
|
||||
|
||||
state.buffers.stencil.setTest( true );
|
||||
state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE );
|
||||
state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff );
|
||||
state.buffers.stencil.setClear( clearValue );
|
||||
state.buffers.stencil.setLocked( true );
|
||||
|
||||
// draw into the stencil buffer
|
||||
|
||||
renderer.setRenderTarget( readBuffer );
|
||||
if ( this.clear ) renderer.clear();
|
||||
renderer.render( this.scene, this.camera );
|
||||
|
||||
renderer.setRenderTarget( writeBuffer );
|
||||
if ( this.clear ) renderer.clear();
|
||||
renderer.render( this.scene, this.camera );
|
||||
|
||||
// unlock color and depth buffer and make them writable for subsequent rendering/clearing
|
||||
|
||||
state.buffers.color.setLocked( false );
|
||||
state.buffers.depth.setLocked( false );
|
||||
|
||||
state.buffers.color.setMask( true );
|
||||
state.buffers.depth.setMask( true );
|
||||
|
||||
// only render where stencil is set to 1
|
||||
|
||||
state.buffers.stencil.setLocked( false );
|
||||
state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
|
||||
state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP );
|
||||
state.buffers.stencil.setLocked( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This pass can be used to clear a mask previously defined with {@link MaskPass}.
|
||||
*
|
||||
* ```js
|
||||
* const clearPass = new ClearMaskPass();
|
||||
* composer.addPass( clearPass );
|
||||
* ```
|
||||
*
|
||||
* @augments Pass
|
||||
*/
|
||||
class ClearMaskPass extends Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new clear mask pass.
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
/**
|
||||
* Overwritten to disable the swap.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.needsSwap = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the clear of the currently defined mask.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
|
||||
|
||||
renderer.state.buffers.stencil.setLocked( false );
|
||||
renderer.state.buffers.stencil.setTest( false );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { MaskPass, ClearMaskPass };
|
||||
138
web/vendor/addons/postprocessing/OutputPass.js
vendored
Normal file
138
web/vendor/addons/postprocessing/OutputPass.js
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
import {
|
||||
ColorManagement,
|
||||
RawShaderMaterial,
|
||||
UniformsUtils,
|
||||
LinearToneMapping,
|
||||
ReinhardToneMapping,
|
||||
CineonToneMapping,
|
||||
AgXToneMapping,
|
||||
ACESFilmicToneMapping,
|
||||
NeutralToneMapping,
|
||||
CustomToneMapping,
|
||||
SRGBTransfer
|
||||
} from 'three';
|
||||
import { Pass, FullScreenQuad } from './Pass.js';
|
||||
import { OutputShader } from '../shaders/OutputShader.js';
|
||||
|
||||
/**
|
||||
* This pass is responsible for including tone mapping and color space conversion
|
||||
* into your pass chain. In most cases, this pass should be included at the end
|
||||
* of each pass chain. If a pass requires sRGB input (e.g. like FXAA), the pass
|
||||
* must follow `OutputPass` in the pass chain.
|
||||
*
|
||||
* The tone mapping and color space settings are extracted from the renderer.
|
||||
*
|
||||
* ```js
|
||||
* const outputPass = new OutputPass();
|
||||
* composer.addPass( outputPass );
|
||||
* ```
|
||||
*
|
||||
* @augments Pass
|
||||
*/
|
||||
class OutputPass extends Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new output pass.
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
/**
|
||||
* The pass uniforms.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.uniforms = UniformsUtils.clone( OutputShader.uniforms );
|
||||
|
||||
/**
|
||||
* The pass material.
|
||||
*
|
||||
* @type {RawShaderMaterial}
|
||||
*/
|
||||
this.material = new RawShaderMaterial( {
|
||||
name: OutputShader.name,
|
||||
uniforms: this.uniforms,
|
||||
vertexShader: OutputShader.vertexShader,
|
||||
fragmentShader: OutputShader.fragmentShader
|
||||
} );
|
||||
|
||||
// internals
|
||||
|
||||
this._fsQuad = new FullScreenQuad( this.material );
|
||||
|
||||
this._outputColorSpace = null;
|
||||
this._toneMapping = null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the output pass.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) {
|
||||
|
||||
this.uniforms[ 'tDiffuse' ].value = readBuffer.texture;
|
||||
this.uniforms[ 'toneMappingExposure' ].value = renderer.toneMappingExposure;
|
||||
|
||||
// rebuild defines if required
|
||||
|
||||
if ( this._outputColorSpace !== renderer.outputColorSpace || this._toneMapping !== renderer.toneMapping ) {
|
||||
|
||||
this._outputColorSpace = renderer.outputColorSpace;
|
||||
this._toneMapping = renderer.toneMapping;
|
||||
|
||||
this.material.defines = {};
|
||||
|
||||
if ( ColorManagement.getTransfer( this._outputColorSpace ) === SRGBTransfer ) this.material.defines.SRGB_TRANSFER = '';
|
||||
|
||||
if ( this._toneMapping === LinearToneMapping ) this.material.defines.LINEAR_TONE_MAPPING = '';
|
||||
else if ( this._toneMapping === ReinhardToneMapping ) this.material.defines.REINHARD_TONE_MAPPING = '';
|
||||
else if ( this._toneMapping === CineonToneMapping ) this.material.defines.CINEON_TONE_MAPPING = '';
|
||||
else if ( this._toneMapping === ACESFilmicToneMapping ) this.material.defines.ACES_FILMIC_TONE_MAPPING = '';
|
||||
else if ( this._toneMapping === AgXToneMapping ) this.material.defines.AGX_TONE_MAPPING = '';
|
||||
else if ( this._toneMapping === NeutralToneMapping ) this.material.defines.NEUTRAL_TONE_MAPPING = '';
|
||||
else if ( this._toneMapping === CustomToneMapping ) this.material.defines.CUSTOM_TONE_MAPPING = '';
|
||||
|
||||
this.material.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if ( this.renderToScreen === true ) {
|
||||
|
||||
renderer.setRenderTarget( null );
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
} else {
|
||||
|
||||
renderer.setRenderTarget( writeBuffer );
|
||||
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the GPU-related resources allocated by this instance. Call this
|
||||
* method whenever the pass is no longer used in your app.
|
||||
*/
|
||||
dispose() {
|
||||
|
||||
this.material.dispose();
|
||||
this._fsQuad.dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { OutputPass };
|
||||
189
web/vendor/addons/postprocessing/Pass.js
vendored
Normal file
189
web/vendor/addons/postprocessing/Pass.js
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
Float32BufferAttribute,
|
||||
OrthographicCamera,
|
||||
Mesh
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* Abstract base class for all post processing passes.
|
||||
*
|
||||
* This module is only relevant for post processing with {@link WebGLRenderer}.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
class Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new pass.
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
/**
|
||||
* This flag can be used for type testing.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @default true
|
||||
*/
|
||||
this.isPass = true;
|
||||
|
||||
/**
|
||||
* If set to `true`, the pass is processed by the composer.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.enabled = true;
|
||||
|
||||
/**
|
||||
* If set to `true`, the pass indicates to swap read and write buffer after rendering.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.needsSwap = true;
|
||||
|
||||
/**
|
||||
* If set to `true`, the pass clears its buffer before rendering
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.clear = false;
|
||||
|
||||
/**
|
||||
* If set to `true`, the result of the pass is rendered to screen. The last pass in the composers
|
||||
* pass chain gets automatically rendered to screen, no matter how this property is configured.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.renderToScreen = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the pass.
|
||||
*
|
||||
* @abstract
|
||||
* @param {number} width - The width to set.
|
||||
* @param {number} height - The width to set.
|
||||
*/
|
||||
setSize( /* width, height */ ) {}
|
||||
|
||||
/**
|
||||
* This method holds the render logic of a pass. It must be implemented in all derived classes.
|
||||
*
|
||||
* @abstract
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
|
||||
|
||||
console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the GPU-related resources allocated by this instance. Call this
|
||||
* method whenever the pass is no longer used in your app.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
dispose() {}
|
||||
|
||||
}
|
||||
|
||||
// Helper for passes that need to fill the viewport with a single quad.
|
||||
|
||||
const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
|
||||
|
||||
// https://github.com/mrdoob/three.js/pull/21358
|
||||
|
||||
class FullscreenTriangleGeometry extends BufferGeometry {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) );
|
||||
this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _geometry = new FullscreenTriangleGeometry();
|
||||
|
||||
|
||||
/**
|
||||
* This module is a helper for passes which need to render a full
|
||||
* screen effect which is quite common in context of post processing.
|
||||
*
|
||||
* The intended usage is to reuse a single full screen quad for rendering
|
||||
* subsequent passes by just reassigning the `material` reference.
|
||||
*
|
||||
* This module can only be used with {@link WebGLRenderer}.
|
||||
*
|
||||
* @augments Mesh
|
||||
*/
|
||||
class FullScreenQuad {
|
||||
|
||||
/**
|
||||
* Constructs a new full screen quad.
|
||||
*
|
||||
* @param {?Material} material - The material to render te full screen quad with.
|
||||
*/
|
||||
constructor( material ) {
|
||||
|
||||
this._mesh = new Mesh( _geometry, material );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the GPU-related resources allocated by this instance. Call this
|
||||
* method whenever the instance is no longer used in your app.
|
||||
*/
|
||||
dispose() {
|
||||
|
||||
this._mesh.geometry.dispose();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the full screen quad.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
*/
|
||||
render( renderer ) {
|
||||
|
||||
renderer.render( this._mesh, _camera );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The quad's material.
|
||||
*
|
||||
* @type {?Material}
|
||||
*/
|
||||
get material() {
|
||||
|
||||
return this._mesh.material;
|
||||
|
||||
}
|
||||
|
||||
set material( value ) {
|
||||
|
||||
this._mesh.material = value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Pass, FullScreenQuad };
|
||||
182
web/vendor/addons/postprocessing/RenderPass.js
vendored
Normal file
182
web/vendor/addons/postprocessing/RenderPass.js
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
import {
|
||||
Color
|
||||
} from 'three';
|
||||
import { Pass } from './Pass.js';
|
||||
|
||||
/**
|
||||
* This class represents a render pass. It takes a camera and a scene and produces
|
||||
* a beauty pass for subsequent post processing effects.
|
||||
*
|
||||
* ```js
|
||||
* const renderPass = new RenderPass( scene, camera );
|
||||
* composer.addPass( renderPass );
|
||||
* ```
|
||||
*
|
||||
* @augments Pass
|
||||
*/
|
||||
class RenderPass extends Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new render pass.
|
||||
*
|
||||
* @param {Scene} scene - The scene to render.
|
||||
* @param {Camera} camera - The camera.
|
||||
* @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used
|
||||
* for all objects in the scene.
|
||||
* @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass.
|
||||
* @param {?number} [clearAlpha=null] - The clear alpha of the render pass.
|
||||
*/
|
||||
constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) {
|
||||
|
||||
super();
|
||||
|
||||
/**
|
||||
* The scene to render.
|
||||
*
|
||||
* @type {Scene}
|
||||
*/
|
||||
this.scene = scene;
|
||||
|
||||
/**
|
||||
* The camera.
|
||||
*
|
||||
* @type {Camera}
|
||||
*/
|
||||
this.camera = camera;
|
||||
|
||||
/**
|
||||
* The override material. If set, this material is used
|
||||
* for all objects in the scene.
|
||||
*
|
||||
* @type {?Material}
|
||||
* @default null
|
||||
*/
|
||||
this.overrideMaterial = overrideMaterial;
|
||||
|
||||
/**
|
||||
* The clear color of the render pass.
|
||||
*
|
||||
* @type {?(number|Color|string)}
|
||||
* @default null
|
||||
*/
|
||||
this.clearColor = clearColor;
|
||||
|
||||
/**
|
||||
* The clear alpha of the render pass.
|
||||
*
|
||||
* @type {?number}
|
||||
* @default null
|
||||
*/
|
||||
this.clearAlpha = clearAlpha;
|
||||
|
||||
/**
|
||||
* Overwritten to perform a clear operation by default.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.clear = true;
|
||||
|
||||
/**
|
||||
* If set to `true`, only the depth can be cleared when `clear` is to `false`.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.clearDepth = false;
|
||||
|
||||
/**
|
||||
* Overwritten to disable the swap.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.needsSwap = false;
|
||||
this._oldClearColor = new Color();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a beauty pass with the configured scene and camera.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
|
||||
|
||||
const oldAutoClear = renderer.autoClear;
|
||||
renderer.autoClear = false;
|
||||
|
||||
let oldClearAlpha, oldOverrideMaterial;
|
||||
|
||||
if ( this.overrideMaterial !== null ) {
|
||||
|
||||
oldOverrideMaterial = this.scene.overrideMaterial;
|
||||
|
||||
this.scene.overrideMaterial = this.overrideMaterial;
|
||||
|
||||
}
|
||||
|
||||
if ( this.clearColor !== null ) {
|
||||
|
||||
renderer.getClearColor( this._oldClearColor );
|
||||
renderer.setClearColor( this.clearColor, renderer.getClearAlpha() );
|
||||
|
||||
}
|
||||
|
||||
if ( this.clearAlpha !== null ) {
|
||||
|
||||
oldClearAlpha = renderer.getClearAlpha();
|
||||
renderer.setClearAlpha( this.clearAlpha );
|
||||
|
||||
}
|
||||
|
||||
if ( this.clearDepth == true ) {
|
||||
|
||||
renderer.clearDepth();
|
||||
|
||||
}
|
||||
|
||||
renderer.setRenderTarget( this.renderToScreen ? null : readBuffer );
|
||||
|
||||
if ( this.clear === true ) {
|
||||
|
||||
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
|
||||
renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
|
||||
|
||||
}
|
||||
|
||||
renderer.render( this.scene, this.camera );
|
||||
|
||||
// restore
|
||||
|
||||
if ( this.clearColor !== null ) {
|
||||
|
||||
renderer.setClearColor( this._oldClearColor );
|
||||
|
||||
}
|
||||
|
||||
if ( this.clearAlpha !== null ) {
|
||||
|
||||
renderer.setClearAlpha( oldClearAlpha );
|
||||
|
||||
}
|
||||
|
||||
if ( this.overrideMaterial !== null ) {
|
||||
|
||||
this.scene.overrideMaterial = oldOverrideMaterial;
|
||||
|
||||
}
|
||||
|
||||
renderer.autoClear = oldAutoClear;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { RenderPass };
|
||||
134
web/vendor/addons/postprocessing/ShaderPass.js
vendored
Normal file
134
web/vendor/addons/postprocessing/ShaderPass.js
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
import {
|
||||
ShaderMaterial,
|
||||
UniformsUtils
|
||||
} from 'three';
|
||||
import { Pass, FullScreenQuad } from './Pass.js';
|
||||
|
||||
/**
|
||||
* This pass can be used to create a post processing effect
|
||||
* with a raw GLSL shader object. Useful for implementing custom
|
||||
* effects.
|
||||
*
|
||||
* ```js
|
||||
* const fxaaPass = new ShaderPass( FXAAShader );
|
||||
* composer.addPass( fxaaPass );
|
||||
* ```
|
||||
*
|
||||
* @augments Pass
|
||||
*/
|
||||
class ShaderPass extends Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new shader pass.
|
||||
*
|
||||
* @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as
|
||||
* defines and uniforms. It's also valid to pass a custom shader material.
|
||||
* @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample
|
||||
* the read buffer.
|
||||
*/
|
||||
constructor( shader, textureID = 'tDiffuse' ) {
|
||||
|
||||
super();
|
||||
|
||||
/**
|
||||
* The name of the texture uniform that should sample the read buffer.
|
||||
*
|
||||
* @type {string}
|
||||
* @default 'tDiffuse'
|
||||
*/
|
||||
this.textureID = textureID;
|
||||
|
||||
/**
|
||||
* The pass uniforms.
|
||||
*
|
||||
* @type {?Object}
|
||||
*/
|
||||
this.uniforms = null;
|
||||
|
||||
/**
|
||||
* The pass material.
|
||||
*
|
||||
* @type {?ShaderMaterial}
|
||||
*/
|
||||
this.material = null;
|
||||
|
||||
if ( shader instanceof ShaderMaterial ) {
|
||||
|
||||
this.uniforms = shader.uniforms;
|
||||
|
||||
this.material = shader;
|
||||
|
||||
} else if ( shader ) {
|
||||
|
||||
this.uniforms = UniformsUtils.clone( shader.uniforms );
|
||||
|
||||
this.material = new ShaderMaterial( {
|
||||
|
||||
name: ( shader.name !== undefined ) ? shader.name : 'unspecified',
|
||||
defines: Object.assign( {}, shader.defines ),
|
||||
uniforms: this.uniforms,
|
||||
vertexShader: shader.vertexShader,
|
||||
fragmentShader: shader.fragmentShader
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
// internals
|
||||
|
||||
this._fsQuad = new FullScreenQuad( this.material );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the shader pass.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
|
||||
|
||||
if ( this.uniforms[ this.textureID ] ) {
|
||||
|
||||
this.uniforms[ this.textureID ].value = readBuffer.texture;
|
||||
|
||||
}
|
||||
|
||||
this._fsQuad.material = this.material;
|
||||
|
||||
if ( this.renderToScreen ) {
|
||||
|
||||
renderer.setRenderTarget( null );
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
} else {
|
||||
|
||||
renderer.setRenderTarget( writeBuffer );
|
||||
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
|
||||
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the GPU-related resources allocated by this instance. Call this
|
||||
* method whenever the pass is no longer used in your app.
|
||||
*/
|
||||
dispose() {
|
||||
|
||||
this.material.dispose();
|
||||
|
||||
this._fsQuad.dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { ShaderPass };
|
||||
491
web/vendor/addons/postprocessing/UnrealBloomPass.js
vendored
Normal file
491
web/vendor/addons/postprocessing/UnrealBloomPass.js
vendored
Normal file
@ -0,0 +1,491 @@
|
||||
import {
|
||||
AdditiveBlending,
|
||||
Color,
|
||||
HalfFloatType,
|
||||
MeshBasicMaterial,
|
||||
ShaderMaterial,
|
||||
UniformsUtils,
|
||||
Vector2,
|
||||
Vector3,
|
||||
WebGLRenderTarget
|
||||
} from 'three';
|
||||
import { Pass, FullScreenQuad } from './Pass.js';
|
||||
import { CopyShader } from '../shaders/CopyShader.js';
|
||||
import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js';
|
||||
|
||||
/**
|
||||
* This pass is inspired by the bloom pass of Unreal Engine. It creates a
|
||||
* mip map chain of bloom textures and blurs them with different radii. Because
|
||||
* of the weighted combination of mips, and because larger blurs are done on
|
||||
* higher mips, this effect provides good quality and performance.
|
||||
*
|
||||
* When using this pass, tone mapping must be enabled in the renderer settings.
|
||||
*
|
||||
* Reference:
|
||||
* - [Bloom in Unreal Engine]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/}
|
||||
*
|
||||
* ```js
|
||||
* const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight );
|
||||
* const bloomPass = new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 );
|
||||
* composer.addPass( bloomPass );
|
||||
* ```
|
||||
*
|
||||
* @augments Pass
|
||||
*/
|
||||
class UnrealBloomPass extends Pass {
|
||||
|
||||
/**
|
||||
* Constructs a new Unreal Bloom pass.
|
||||
*
|
||||
* @param {Vector2} [resolution] - The effect's resolution.
|
||||
* @param {number} [strength=1] - The Bloom strength.
|
||||
* @param {number} radius - The Bloom radius.
|
||||
* @param {number} threshold - The luminance threshold limits which bright areas contribute to the Bloom effect.
|
||||
*/
|
||||
constructor( resolution, strength = 1, radius, threshold ) {
|
||||
|
||||
super();
|
||||
|
||||
/**
|
||||
* The Bloom strength.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 1
|
||||
*/
|
||||
this.strength = strength;
|
||||
|
||||
/**
|
||||
* The Bloom radius.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.radius = radius;
|
||||
|
||||
/**
|
||||
* The luminance threshold limits which bright areas contribute to the Bloom effect.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.threshold = threshold;
|
||||
|
||||
/**
|
||||
* The effect's resolution.
|
||||
*
|
||||
* @type {Vector2}
|
||||
* @default (256,256)
|
||||
*/
|
||||
this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 );
|
||||
|
||||
/**
|
||||
* The effect's clear color
|
||||
*
|
||||
* @type {Color}
|
||||
* @default (0,0,0)
|
||||
*/
|
||||
this.clearColor = new Color( 0, 0, 0 );
|
||||
|
||||
/**
|
||||
* Overwritten to disable the swap.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.needsSwap = false;
|
||||
|
||||
// internals
|
||||
|
||||
// render targets
|
||||
this.renderTargetsHorizontal = [];
|
||||
this.renderTargetsVertical = [];
|
||||
this.nMips = 5;
|
||||
let resx = Math.round( this.resolution.x / 2 );
|
||||
let resy = Math.round( this.resolution.y / 2 );
|
||||
|
||||
this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
|
||||
this.renderTargetBright.texture.name = 'UnrealBloomPass.bright';
|
||||
this.renderTargetBright.texture.generateMipmaps = false;
|
||||
|
||||
for ( let i = 0; i < this.nMips; i ++ ) {
|
||||
|
||||
const renderTargetHorizontal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
|
||||
|
||||
renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i;
|
||||
renderTargetHorizontal.texture.generateMipmaps = false;
|
||||
|
||||
this.renderTargetsHorizontal.push( renderTargetHorizontal );
|
||||
|
||||
const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } );
|
||||
|
||||
renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i;
|
||||
renderTargetVertical.texture.generateMipmaps = false;
|
||||
|
||||
this.renderTargetsVertical.push( renderTargetVertical );
|
||||
|
||||
resx = Math.round( resx / 2 );
|
||||
|
||||
resy = Math.round( resy / 2 );
|
||||
|
||||
}
|
||||
|
||||
// luminosity high pass material
|
||||
|
||||
const highPassShader = LuminosityHighPassShader;
|
||||
this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms );
|
||||
|
||||
this.highPassUniforms[ 'luminosityThreshold' ].value = threshold;
|
||||
this.highPassUniforms[ 'smoothWidth' ].value = 0.01;
|
||||
|
||||
this.materialHighPassFilter = new ShaderMaterial( {
|
||||
uniforms: this.highPassUniforms,
|
||||
vertexShader: highPassShader.vertexShader,
|
||||
fragmentShader: highPassShader.fragmentShader
|
||||
} );
|
||||
|
||||
// gaussian blur materials
|
||||
|
||||
this.separableBlurMaterials = [];
|
||||
const kernelSizeArray = [ 3, 5, 7, 9, 11 ];
|
||||
resx = Math.round( this.resolution.x / 2 );
|
||||
resy = Math.round( this.resolution.y / 2 );
|
||||
|
||||
for ( let i = 0; i < this.nMips; i ++ ) {
|
||||
|
||||
this.separableBlurMaterials.push( this._getSeparableBlurMaterial( kernelSizeArray[ i ] ) );
|
||||
|
||||
this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy );
|
||||
|
||||
resx = Math.round( resx / 2 );
|
||||
|
||||
resy = Math.round( resy / 2 );
|
||||
|
||||
}
|
||||
|
||||
// composite material
|
||||
|
||||
this.compositeMaterial = this._getCompositeMaterial( this.nMips );
|
||||
this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture;
|
||||
this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture;
|
||||
this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture;
|
||||
this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture;
|
||||
this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture;
|
||||
this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength;
|
||||
this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1;
|
||||
|
||||
const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ];
|
||||
this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors;
|
||||
this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ];
|
||||
this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors;
|
||||
|
||||
// blend material
|
||||
|
||||
this.copyUniforms = UniformsUtils.clone( CopyShader.uniforms );
|
||||
|
||||
this.blendMaterial = new ShaderMaterial( {
|
||||
uniforms: this.copyUniforms,
|
||||
vertexShader: CopyShader.vertexShader,
|
||||
fragmentShader: CopyShader.fragmentShader,
|
||||
blending: AdditiveBlending,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
transparent: true
|
||||
} );
|
||||
|
||||
this._oldClearColor = new Color();
|
||||
this._oldClearAlpha = 1;
|
||||
|
||||
this._basic = new MeshBasicMaterial();
|
||||
|
||||
this._fsQuad = new FullScreenQuad( null );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the GPU-related resources allocated by this instance. Call this
|
||||
* method whenever the pass is no longer used in your app.
|
||||
*/
|
||||
dispose() {
|
||||
|
||||
for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) {
|
||||
|
||||
this.renderTargetsHorizontal[ i ].dispose();
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) {
|
||||
|
||||
this.renderTargetsVertical[ i ].dispose();
|
||||
|
||||
}
|
||||
|
||||
this.renderTargetBright.dispose();
|
||||
|
||||
//
|
||||
|
||||
for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) {
|
||||
|
||||
this.separableBlurMaterials[ i ].dispose();
|
||||
|
||||
}
|
||||
|
||||
this.compositeMaterial.dispose();
|
||||
this.blendMaterial.dispose();
|
||||
this._basic.dispose();
|
||||
|
||||
//
|
||||
|
||||
this._fsQuad.dispose();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the pass.
|
||||
*
|
||||
* @param {number} width - The width to set.
|
||||
* @param {number} height - The width to set.
|
||||
*/
|
||||
setSize( width, height ) {
|
||||
|
||||
let resx = Math.round( width / 2 );
|
||||
let resy = Math.round( height / 2 );
|
||||
|
||||
this.renderTargetBright.setSize( resx, resy );
|
||||
|
||||
for ( let i = 0; i < this.nMips; i ++ ) {
|
||||
|
||||
this.renderTargetsHorizontal[ i ].setSize( resx, resy );
|
||||
this.renderTargetsVertical[ i ].setSize( resx, resy );
|
||||
|
||||
this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy );
|
||||
|
||||
resx = Math.round( resx / 2 );
|
||||
resy = Math.round( resy / 2 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the Bloom pass.
|
||||
*
|
||||
* @param {WebGLRenderer} renderer - The renderer.
|
||||
* @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering
|
||||
* destination for the pass.
|
||||
* @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the
|
||||
* previous pass from this buffer.
|
||||
* @param {number} deltaTime - The delta time in seconds.
|
||||
* @param {boolean} maskActive - Whether masking is active or not.
|
||||
*/
|
||||
render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
|
||||
|
||||
renderer.getClearColor( this._oldClearColor );
|
||||
this._oldClearAlpha = renderer.getClearAlpha();
|
||||
const oldAutoClear = renderer.autoClear;
|
||||
renderer.autoClear = false;
|
||||
|
||||
renderer.setClearColor( this.clearColor, 0 );
|
||||
|
||||
if ( maskActive ) renderer.state.buffers.stencil.setTest( false );
|
||||
|
||||
// Render input to screen
|
||||
|
||||
if ( this.renderToScreen ) {
|
||||
|
||||
this._fsQuad.material = this._basic;
|
||||
this._basic.map = readBuffer.texture;
|
||||
|
||||
renderer.setRenderTarget( null );
|
||||
renderer.clear();
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
}
|
||||
|
||||
// 1. Extract Bright Areas
|
||||
|
||||
this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture;
|
||||
this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold;
|
||||
this._fsQuad.material = this.materialHighPassFilter;
|
||||
|
||||
renderer.setRenderTarget( this.renderTargetBright );
|
||||
renderer.clear();
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
// 2. Blur All the mips progressively
|
||||
|
||||
let inputRenderTarget = this.renderTargetBright;
|
||||
|
||||
for ( let i = 0; i < this.nMips; i ++ ) {
|
||||
|
||||
this._fsQuad.material = this.separableBlurMaterials[ i ];
|
||||
|
||||
this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture;
|
||||
this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX;
|
||||
renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] );
|
||||
renderer.clear();
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture;
|
||||
this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY;
|
||||
renderer.setRenderTarget( this.renderTargetsVertical[ i ] );
|
||||
renderer.clear();
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
inputRenderTarget = this.renderTargetsVertical[ i ];
|
||||
|
||||
}
|
||||
|
||||
// Composite All the mips
|
||||
|
||||
this._fsQuad.material = this.compositeMaterial;
|
||||
this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength;
|
||||
this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius;
|
||||
this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors;
|
||||
|
||||
renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] );
|
||||
renderer.clear();
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
// Blend it additively over the input texture
|
||||
|
||||
this._fsQuad.material = this.blendMaterial;
|
||||
this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture;
|
||||
|
||||
if ( maskActive ) renderer.state.buffers.stencil.setTest( true );
|
||||
|
||||
if ( this.renderToScreen ) {
|
||||
|
||||
renderer.setRenderTarget( null );
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
} else {
|
||||
|
||||
renderer.setRenderTarget( readBuffer );
|
||||
this._fsQuad.render( renderer );
|
||||
|
||||
}
|
||||
|
||||
// Restore renderer settings
|
||||
|
||||
renderer.setClearColor( this._oldClearColor, this._oldClearAlpha );
|
||||
renderer.autoClear = oldAutoClear;
|
||||
|
||||
}
|
||||
|
||||
// internals
|
||||
|
||||
_getSeparableBlurMaterial( kernelRadius ) {
|
||||
|
||||
const coefficients = [];
|
||||
|
||||
for ( let i = 0; i < kernelRadius; i ++ ) {
|
||||
|
||||
coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius );
|
||||
|
||||
}
|
||||
|
||||
return new ShaderMaterial( {
|
||||
|
||||
defines: {
|
||||
'KERNEL_RADIUS': kernelRadius
|
||||
},
|
||||
|
||||
uniforms: {
|
||||
'colorTexture': { value: null },
|
||||
'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size
|
||||
'direction': { value: new Vector2( 0.5, 0.5 ) },
|
||||
'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients
|
||||
},
|
||||
|
||||
vertexShader:
|
||||
`varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
}`,
|
||||
|
||||
fragmentShader:
|
||||
`#include <common>
|
||||
varying vec2 vUv;
|
||||
uniform sampler2D colorTexture;
|
||||
uniform vec2 invSize;
|
||||
uniform vec2 direction;
|
||||
uniform float gaussianCoefficients[KERNEL_RADIUS];
|
||||
|
||||
void main() {
|
||||
float weightSum = gaussianCoefficients[0];
|
||||
vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum;
|
||||
for( int i = 1; i < KERNEL_RADIUS; i ++ ) {
|
||||
float x = float(i);
|
||||
float w = gaussianCoefficients[i];
|
||||
vec2 uvOffset = direction * invSize * x;
|
||||
vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb;
|
||||
vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb;
|
||||
diffuseSum += (sample1 + sample2) * w;
|
||||
weightSum += 2.0 * w;
|
||||
}
|
||||
gl_FragColor = vec4(diffuseSum/weightSum, 1.0);
|
||||
}`
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
_getCompositeMaterial( nMips ) {
|
||||
|
||||
return new ShaderMaterial( {
|
||||
|
||||
defines: {
|
||||
'NUM_MIPS': nMips
|
||||
},
|
||||
|
||||
uniforms: {
|
||||
'blurTexture1': { value: null },
|
||||
'blurTexture2': { value: null },
|
||||
'blurTexture3': { value: null },
|
||||
'blurTexture4': { value: null },
|
||||
'blurTexture5': { value: null },
|
||||
'bloomStrength': { value: 1.0 },
|
||||
'bloomFactors': { value: null },
|
||||
'bloomTintColors': { value: null },
|
||||
'bloomRadius': { value: 0.0 }
|
||||
},
|
||||
|
||||
vertexShader:
|
||||
`varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
}`,
|
||||
|
||||
fragmentShader:
|
||||
`varying vec2 vUv;
|
||||
uniform sampler2D blurTexture1;
|
||||
uniform sampler2D blurTexture2;
|
||||
uniform sampler2D blurTexture3;
|
||||
uniform sampler2D blurTexture4;
|
||||
uniform sampler2D blurTexture5;
|
||||
uniform float bloomStrength;
|
||||
uniform float bloomRadius;
|
||||
uniform float bloomFactors[NUM_MIPS];
|
||||
uniform vec3 bloomTintColors[NUM_MIPS];
|
||||
|
||||
float lerpBloomFactor(const in float factor) {
|
||||
float mirrorFactor = 1.2 - factor;
|
||||
return mix(factor, mirrorFactor, bloomRadius);
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) +
|
||||
lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) +
|
||||
lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) +
|
||||
lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) +
|
||||
lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );
|
||||
}`
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 );
|
||||
UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 );
|
||||
|
||||
export { UnrealBloomPass };
|
||||
49
web/vendor/addons/shaders/CopyShader.js
vendored
Normal file
49
web/vendor/addons/shaders/CopyShader.js
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/** @module CopyShader */
|
||||
|
||||
/**
|
||||
* Full-screen copy shader pass.
|
||||
*
|
||||
* @constant
|
||||
* @type {ShaderMaterial~Shader}
|
||||
*/
|
||||
const CopyShader = {
|
||||
|
||||
name: 'CopyShader',
|
||||
|
||||
uniforms: {
|
||||
|
||||
'tDiffuse': { value: null },
|
||||
'opacity': { value: 1.0 }
|
||||
|
||||
},
|
||||
|
||||
vertexShader: /* glsl */`
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
|
||||
}`,
|
||||
|
||||
fragmentShader: /* glsl */`
|
||||
|
||||
uniform float opacity;
|
||||
|
||||
uniform sampler2D tDiffuse;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
vec4 texel = texture2D( tDiffuse, vUv );
|
||||
gl_FragColor = opacity * texel;
|
||||
|
||||
|
||||
}`
|
||||
|
||||
};
|
||||
|
||||
export { CopyShader };
|
||||
65
web/vendor/addons/shaders/LuminosityHighPassShader.js
vendored
Normal file
65
web/vendor/addons/shaders/LuminosityHighPassShader.js
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
Color
|
||||
} from 'three';
|
||||
|
||||
/** @module LuminosityHighPassShader */
|
||||
|
||||
/**
|
||||
* Luminosity high pass shader.
|
||||
*
|
||||
* @constant
|
||||
* @type {ShaderMaterial~Shader}
|
||||
*/
|
||||
const LuminosityHighPassShader = {
|
||||
|
||||
name: 'LuminosityHighPassShader',
|
||||
|
||||
uniforms: {
|
||||
|
||||
'tDiffuse': { value: null },
|
||||
'luminosityThreshold': { value: 1.0 },
|
||||
'smoothWidth': { value: 1.0 },
|
||||
'defaultColor': { value: new Color( 0x000000 ) },
|
||||
'defaultOpacity': { value: 0.0 }
|
||||
|
||||
},
|
||||
|
||||
vertexShader: /* glsl */`
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
vUv = uv;
|
||||
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
|
||||
}`,
|
||||
|
||||
fragmentShader: /* glsl */`
|
||||
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform vec3 defaultColor;
|
||||
uniform float defaultOpacity;
|
||||
uniform float luminosityThreshold;
|
||||
uniform float smoothWidth;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
vec4 texel = texture2D( tDiffuse, vUv );
|
||||
|
||||
float v = luminance( texel.xyz );
|
||||
|
||||
vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );
|
||||
|
||||
float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );
|
||||
|
||||
gl_FragColor = mix( outputColor, texel, alpha );
|
||||
|
||||
}`
|
||||
|
||||
};
|
||||
|
||||
export { LuminosityHighPassShader };
|
||||
100
web/vendor/addons/shaders/OutputShader.js
vendored
Normal file
100
web/vendor/addons/shaders/OutputShader.js
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
/** @module OutputShader */
|
||||
|
||||
/**
|
||||
* Performs tone mapping and color space conversion for
|
||||
* FX workflows.
|
||||
*
|
||||
* Used by {@link OutputPass}.
|
||||
*
|
||||
* @constant
|
||||
* @type {ShaderMaterial~Shader}
|
||||
*/
|
||||
const OutputShader = {
|
||||
|
||||
name: 'OutputShader',
|
||||
|
||||
uniforms: {
|
||||
|
||||
'tDiffuse': { value: null },
|
||||
'toneMappingExposure': { value: 1 }
|
||||
|
||||
},
|
||||
|
||||
vertexShader: /* glsl */`
|
||||
precision highp float;
|
||||
|
||||
uniform mat4 modelViewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
attribute vec3 position;
|
||||
attribute vec2 uv;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
|
||||
}`,
|
||||
|
||||
fragmentShader: /* glsl */`
|
||||
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tDiffuse;
|
||||
|
||||
#include <tonemapping_pars_fragment>
|
||||
#include <colorspace_pars_fragment>
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
gl_FragColor = texture2D( tDiffuse, vUv );
|
||||
|
||||
// tone mapping
|
||||
|
||||
#ifdef LINEAR_TONE_MAPPING
|
||||
|
||||
gl_FragColor.rgb = LinearToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#elif defined( REINHARD_TONE_MAPPING )
|
||||
|
||||
gl_FragColor.rgb = ReinhardToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#elif defined( CINEON_TONE_MAPPING )
|
||||
|
||||
gl_FragColor.rgb = CineonToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#elif defined( ACES_FILMIC_TONE_MAPPING )
|
||||
|
||||
gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#elif defined( AGX_TONE_MAPPING )
|
||||
|
||||
gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#elif defined( NEUTRAL_TONE_MAPPING )
|
||||
|
||||
gl_FragColor.rgb = NeutralToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#elif defined( CUSTOM_TONE_MAPPING )
|
||||
|
||||
gl_FragColor.rgb = CustomToneMapping( gl_FragColor.rgb );
|
||||
|
||||
#endif
|
||||
|
||||
// color space
|
||||
|
||||
#ifdef SRGB_TRANSFER
|
||||
|
||||
gl_FragColor = sRGBTransferOETF( gl_FragColor );
|
||||
|
||||
#endif
|
||||
|
||||
}`
|
||||
|
||||
};
|
||||
|
||||
export { OutputShader };
|
||||
53
web/vendor/addons/shaders/VignetteShader.js
vendored
Normal file
53
web/vendor/addons/shaders/VignetteShader.js
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/** @module VignetteShader */
|
||||
|
||||
/**
|
||||
* Based on [PaintEffect postprocess from ro.me]{@link http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js}.
|
||||
*
|
||||
* @constant
|
||||
* @type {ShaderMaterial~Shader}
|
||||
*/
|
||||
const VignetteShader = {
|
||||
|
||||
name: 'VignetteShader',
|
||||
|
||||
uniforms: {
|
||||
|
||||
'tDiffuse': { value: null },
|
||||
'offset': { value: 1.0 },
|
||||
'darkness': { value: 1.0 }
|
||||
|
||||
},
|
||||
|
||||
vertexShader: /* glsl */`
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
|
||||
}`,
|
||||
|
||||
fragmentShader: /* glsl */`
|
||||
|
||||
uniform float offset;
|
||||
uniform float darkness;
|
||||
|
||||
uniform sampler2D tDiffuse;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
// Eskil's vignette
|
||||
|
||||
vec4 texel = texture2D( tDiffuse, vUv );
|
||||
vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset );
|
||||
gl_FragColor = vec4( mix( texel.rgb, vec3( 1.0 - darkness ), dot( uv, uv ) ), texel.a );
|
||||
|
||||
}`
|
||||
|
||||
};
|
||||
|
||||
export { VignetteShader };
|
||||
1432
web/vendor/addons/utils/BufferGeometryUtils.js
vendored
Normal file
1432
web/vendor/addons/utils/BufferGeometryUtils.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
490
web/vendor/addons/utils/SkeletonUtils.js
vendored
Normal file
490
web/vendor/addons/utils/SkeletonUtils.js
vendored
Normal file
@ -0,0 +1,490 @@
|
||||
import {
|
||||
AnimationClip,
|
||||
AnimationMixer,
|
||||
Matrix4,
|
||||
Quaternion,
|
||||
QuaternionKeyframeTrack,
|
||||
SkeletonHelper,
|
||||
Vector3,
|
||||
VectorKeyframeTrack
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* @module SkeletonUtils
|
||||
* @three_import import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
|
||||
*/
|
||||
|
||||
function getBoneName( bone, options ) {
|
||||
|
||||
if ( options.getBoneName !== undefined ) {
|
||||
|
||||
return options.getBoneName( bone );
|
||||
|
||||
}
|
||||
|
||||
return options.names[ bone.name ];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retargets the skeleton from the given source 3D object to the
|
||||
* target 3D object.
|
||||
*
|
||||
* @param {Object3D} target - The target 3D object.
|
||||
* @param {Object3D} source - The source 3D object.
|
||||
* @param {module:SkeletonUtils~RetargetOptions} options - The options.
|
||||
*/
|
||||
function retarget( target, source, options = {} ) {
|
||||
|
||||
const quat = new Quaternion(),
|
||||
scale = new Vector3(),
|
||||
relativeMatrix = new Matrix4(),
|
||||
globalMatrix = new Matrix4();
|
||||
|
||||
options.preserveBoneMatrix = options.preserveBoneMatrix !== undefined ? options.preserveBoneMatrix : true;
|
||||
options.preserveBonePositions = options.preserveBonePositions !== undefined ? options.preserveBonePositions : true;
|
||||
options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
|
||||
options.hip = options.hip !== undefined ? options.hip : 'hip';
|
||||
options.hipInfluence = options.hipInfluence !== undefined ? options.hipInfluence : new Vector3( 1, 1, 1 );
|
||||
options.scale = options.scale !== undefined ? options.scale : 1;
|
||||
options.names = options.names || {};
|
||||
|
||||
const sourceBones = source.isObject3D ? source.skeleton.bones : getBones( source ),
|
||||
bones = target.isObject3D ? target.skeleton.bones : getBones( target );
|
||||
|
||||
let bone, name, boneTo,
|
||||
bonesPosition;
|
||||
|
||||
// reset bones
|
||||
|
||||
if ( target.isObject3D ) {
|
||||
|
||||
target.skeleton.pose();
|
||||
|
||||
} else {
|
||||
|
||||
options.useTargetMatrix = true;
|
||||
options.preserveBoneMatrix = false;
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBonePositions ) {
|
||||
|
||||
bonesPosition = [];
|
||||
|
||||
for ( let i = 0; i < bones.length; i ++ ) {
|
||||
|
||||
bonesPosition.push( bones[ i ].position.clone() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBoneMatrix ) {
|
||||
|
||||
// reset matrix
|
||||
|
||||
target.updateMatrixWorld();
|
||||
|
||||
target.matrixWorld.identity();
|
||||
|
||||
// reset children matrix
|
||||
|
||||
for ( let i = 0; i < target.children.length; ++ i ) {
|
||||
|
||||
target.children[ i ].updateMatrixWorld( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = getBoneName( bone, options );
|
||||
|
||||
boneTo = getBoneByName( name, sourceBones );
|
||||
|
||||
globalMatrix.copy( bone.matrixWorld );
|
||||
|
||||
if ( boneTo ) {
|
||||
|
||||
boneTo.updateMatrixWorld();
|
||||
|
||||
if ( options.useTargetMatrix ) {
|
||||
|
||||
relativeMatrix.copy( boneTo.matrixWorld );
|
||||
|
||||
} else {
|
||||
|
||||
relativeMatrix.copy( target.matrixWorld ).invert();
|
||||
relativeMatrix.multiply( boneTo.matrixWorld );
|
||||
|
||||
}
|
||||
|
||||
// ignore scale to extract rotation
|
||||
|
||||
scale.setFromMatrixScale( relativeMatrix );
|
||||
relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) );
|
||||
|
||||
// apply to global matrix
|
||||
|
||||
globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
|
||||
|
||||
if ( target.isObject3D ) {
|
||||
|
||||
if ( options.localOffsets ) {
|
||||
|
||||
if ( options.localOffsets[ bone.name ] ) {
|
||||
|
||||
globalMatrix.multiply( options.localOffsets[ bone.name ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
globalMatrix.copyPosition( relativeMatrix );
|
||||
|
||||
}
|
||||
|
||||
if ( name === options.hip ) {
|
||||
|
||||
globalMatrix.elements[ 12 ] *= options.scale * options.hipInfluence.x;
|
||||
globalMatrix.elements[ 13 ] *= options.scale * options.hipInfluence.y;
|
||||
globalMatrix.elements[ 14 ] *= options.scale * options.hipInfluence.z;
|
||||
|
||||
if ( options.hipPosition !== undefined ) {
|
||||
|
||||
globalMatrix.elements[ 12 ] += options.hipPosition.x * options.scale;
|
||||
globalMatrix.elements[ 13 ] += options.hipPosition.y * options.scale;
|
||||
globalMatrix.elements[ 14 ] += options.hipPosition.z * options.scale;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( bone.parent ) {
|
||||
|
||||
bone.matrix.copy( bone.parent.matrixWorld ).invert();
|
||||
bone.matrix.multiply( globalMatrix );
|
||||
|
||||
} else {
|
||||
|
||||
bone.matrix.copy( globalMatrix );
|
||||
|
||||
}
|
||||
|
||||
bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
|
||||
|
||||
bone.updateMatrixWorld();
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBonePositions ) {
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = getBoneName( bone, options ) || bone.name;
|
||||
|
||||
if ( name !== options.hip ) {
|
||||
|
||||
bone.position.copy( bonesPosition[ i ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveBoneMatrix ) {
|
||||
|
||||
// restore matrix
|
||||
|
||||
target.updateMatrixWorld( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retargets the animation clip of the source object to the
|
||||
* target 3D object.
|
||||
*
|
||||
* @param {Object3D} target - The target 3D object.
|
||||
* @param {Object3D} source - The source 3D object.
|
||||
* @param {AnimationClip} clip - The animation clip.
|
||||
* @param {module:SkeletonUtils~RetargetOptions} options - The options.
|
||||
* @return {AnimationClip} The retargeted animation clip.
|
||||
*/
|
||||
function retargetClip( target, source, clip, options = {} ) {
|
||||
|
||||
options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
|
||||
|
||||
// Calculate the fps from the source clip based on the track with the most frames, unless fps is already provided.
|
||||
options.fps = options.fps !== undefined ? options.fps : ( Math.max( ...clip.tracks.map( track => track.times.length ) ) / clip.duration );
|
||||
options.names = options.names || [];
|
||||
|
||||
if ( ! source.isObject3D ) {
|
||||
|
||||
source = getHelperFromSkeleton( source );
|
||||
|
||||
}
|
||||
|
||||
const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
|
||||
delta = clip.duration / ( numFrames - 1 ),
|
||||
convertedTracks = [],
|
||||
mixer = new AnimationMixer( source ),
|
||||
bones = getBones( target.skeleton ),
|
||||
boneDatas = [];
|
||||
|
||||
let positionOffset,
|
||||
bone, boneTo, boneData,
|
||||
name;
|
||||
|
||||
mixer.clipAction( clip ).play();
|
||||
|
||||
// trim
|
||||
|
||||
let start = 0, end = numFrames;
|
||||
|
||||
if ( options.trim !== undefined ) {
|
||||
|
||||
start = Math.round( options.trim[ 0 ] * options.fps );
|
||||
end = Math.min( Math.round( options.trim[ 1 ] * options.fps ), numFrames ) - start;
|
||||
|
||||
mixer.update( options.trim[ 0 ] );
|
||||
|
||||
} else {
|
||||
|
||||
mixer.update( 0 );
|
||||
|
||||
}
|
||||
|
||||
source.updateMatrixWorld();
|
||||
|
||||
//
|
||||
|
||||
for ( let frame = 0; frame < end; ++ frame ) {
|
||||
|
||||
const time = frame * delta;
|
||||
|
||||
retarget( target, source, options );
|
||||
|
||||
for ( let j = 0; j < bones.length; ++ j ) {
|
||||
|
||||
bone = bones[ j ];
|
||||
name = getBoneName( bone, options ) || bone.name;
|
||||
boneTo = getBoneByName( name, source.skeleton );
|
||||
|
||||
if ( boneTo ) {
|
||||
|
||||
boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
|
||||
|
||||
if ( options.hip === name ) {
|
||||
|
||||
if ( ! boneData.pos ) {
|
||||
|
||||
boneData.pos = {
|
||||
times: new Float32Array( end ),
|
||||
values: new Float32Array( end * 3 )
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if ( options.useFirstFramePosition ) {
|
||||
|
||||
if ( frame === 0 ) {
|
||||
|
||||
positionOffset = bone.position.clone();
|
||||
|
||||
}
|
||||
|
||||
bone.position.sub( positionOffset );
|
||||
|
||||
}
|
||||
|
||||
boneData.pos.times[ frame ] = time;
|
||||
|
||||
bone.position.toArray( boneData.pos.values, frame * 3 );
|
||||
|
||||
}
|
||||
|
||||
if ( ! boneData.quat ) {
|
||||
|
||||
boneData.quat = {
|
||||
times: new Float32Array( end ),
|
||||
values: new Float32Array( end * 4 )
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
boneData.quat.times[ frame ] = time;
|
||||
|
||||
bone.quaternion.toArray( boneData.quat.values, frame * 4 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( frame === end - 2 ) {
|
||||
|
||||
// last mixer update before final loop iteration
|
||||
// make sure we do not go over or equal to clip duration
|
||||
mixer.update( delta - 0.0000001 );
|
||||
|
||||
} else {
|
||||
|
||||
mixer.update( delta );
|
||||
|
||||
}
|
||||
|
||||
source.updateMatrixWorld();
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < boneDatas.length; ++ i ) {
|
||||
|
||||
boneData = boneDatas[ i ];
|
||||
|
||||
if ( boneData ) {
|
||||
|
||||
if ( boneData.pos ) {
|
||||
|
||||
convertedTracks.push( new VectorKeyframeTrack(
|
||||
'.bones[' + boneData.bone.name + '].position',
|
||||
boneData.pos.times,
|
||||
boneData.pos.values
|
||||
) );
|
||||
|
||||
}
|
||||
|
||||
convertedTracks.push( new QuaternionKeyframeTrack(
|
||||
'.bones[' + boneData.bone.name + '].quaternion',
|
||||
boneData.quat.times,
|
||||
boneData.quat.values
|
||||
) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mixer.uncacheAction( clip );
|
||||
|
||||
return new AnimationClip( clip.name, - 1, convertedTracks );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the given 3D object and its descendants, ensuring that any `SkinnedMesh` instances are
|
||||
* correctly associated with their bones. Bones are also cloned, and must be descendants of the
|
||||
* object passed to this method. Other data, like geometries and materials, are reused by reference.
|
||||
*
|
||||
* @param {Object3D} source - The 3D object to clone.
|
||||
* @return {Object3D} The cloned 3D object.
|
||||
*/
|
||||
function clone( source ) {
|
||||
|
||||
const sourceLookup = new Map();
|
||||
const cloneLookup = new Map();
|
||||
|
||||
const clone = source.clone();
|
||||
|
||||
parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
|
||||
|
||||
sourceLookup.set( clonedNode, sourceNode );
|
||||
cloneLookup.set( sourceNode, clonedNode );
|
||||
|
||||
} );
|
||||
|
||||
clone.traverse( function ( node ) {
|
||||
|
||||
if ( ! node.isSkinnedMesh ) return;
|
||||
|
||||
const clonedMesh = node;
|
||||
const sourceMesh = sourceLookup.get( node );
|
||||
const sourceBones = sourceMesh.skeleton.bones;
|
||||
|
||||
clonedMesh.skeleton = sourceMesh.skeleton.clone();
|
||||
clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
|
||||
|
||||
clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
|
||||
|
||||
return cloneLookup.get( bone );
|
||||
|
||||
} );
|
||||
|
||||
clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
|
||||
|
||||
} );
|
||||
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
// internal helper
|
||||
|
||||
function getBoneByName( name, skeleton ) {
|
||||
|
||||
for ( let i = 0, bones = getBones( skeleton ); i < bones.length; i ++ ) {
|
||||
|
||||
if ( name === bones[ i ].name )
|
||||
|
||||
return bones[ i ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getBones( skeleton ) {
|
||||
|
||||
return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getHelperFromSkeleton( skeleton ) {
|
||||
|
||||
const source = new SkeletonHelper( skeleton.bones[ 0 ] );
|
||||
source.skeleton = skeleton;
|
||||
|
||||
return source;
|
||||
|
||||
}
|
||||
|
||||
function parallelTraverse( a, b, callback ) {
|
||||
|
||||
callback( a, b );
|
||||
|
||||
for ( let i = 0; i < a.children.length; i ++ ) {
|
||||
|
||||
parallelTraverse( a.children[ i ], b.children[ i ], callback );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retarget options of `SkeletonUtils`.
|
||||
*
|
||||
* @typedef {Object} module:SkeletonUtils~RetargetOptions
|
||||
* @property {boolean} [useFirstFramePosition=false] - Whether to use the position of the first frame or not.
|
||||
* @property {number} [fps] - The FPS of the clip.
|
||||
* @property {Object<string,string>} [names] - A dictionary for mapping target to source bone names.
|
||||
* @property {function(string):string} [getBoneName] - A function for mapping bone names. Alternative to `names`.
|
||||
* @property {Array<number>} [trim] - Whether to trim the clip or not. If set the array should hold two values for the start and end.
|
||||
* @property {boolean} [preserveBoneMatrix=true] - Whether to preserve bone matrices or not.
|
||||
* @property {boolean} [preserveBonePositions=true] - Whether to preserve bone positions or not.
|
||||
* @property {boolean} [useTargetMatrix=false] - Whether to use the target matrix or not.
|
||||
* @property {string} [hip='hip'] - The name of the source's hip bone.
|
||||
* @property {Vector3} [hipInfluence=(1,1,1)] - The hip influence.
|
||||
* @property {number} [scale=1] - The scale.
|
||||
**/
|
||||
|
||||
export {
|
||||
retarget,
|
||||
retargetClip,
|
||||
clone,
|
||||
};
|
||||
57362
web/vendor/three.core.js
vendored
Normal file
57362
web/vendor/three.core.js
vendored
Normal file
File diff suppressed because one or more lines are too long
17990
web/vendor/three.module.js
vendored
Normal file
17990
web/vendor/three.module.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user